admin管理员组

文章数量:1277385

I'm creating a decorator to save log/results , but this is introduced to abstract class:

from functools import wraps

load_dotenv()

class Evaluator(ABC):
    def __init__(self, evaluation_config, framework_config, **kwargs):

        self.evaluation_config = evaluation_config
        self.framework_config = framework_config

        self.logger = WandbLogger()

    @abstractmethod
    def _create_metrics(self) -> List[Metric]:
        pass

    @abstractmethod
    def load_evaluation_data(self, data_path):
        pass

    @staticmethod
    def log_results(func):
        """Decorator to log evaluation results."""
        print("this is called")
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            print("this is called too!")
            result = func(*args, **kwargs)
            self.logger.log(evaluation_result=result, config=self.framework_config)
            return result

        return wrapper

    @log_results  
    @abstractmethod
    def __call__(self, *args, **kwargs):
        pass

One of the child-class:


class ScoreEvaluator(Evaluator):
    def __init__(self, evaluation_config, framework_config, **kwargs):
        super().__init__(evaluation_config, framework_config, **kwargs)

        self.metric_cls = self._create_metrics()

    def _create_metrics(self):
        metrics = []
        evaluator_classes = Metric.__subclasses__()

        for cls in evaluator_classes:
            metrics.append(cls())
        return metrics

    def load_evaluation_data(self, data_path):


        return evaluation_data

    def evaluate_criterion(self, predictions: List[float], golds: List[float]):

        results = {}
        for metric in self.metric_cls:
            results[metric.__class__.__name__] = metricpute(predictions, golds)

        return results

    async def __call__(self, *args, **kwargs):

        evaluation_data_path = os.getenv("EVALUATION_DATA_PATH")
        evaluation_data = self.load_evaluation_data(evaluation_data_path)
        evaluation_data = {key: value for key, value in evaluation_data.items()}

        prediction_dict = {
            key: await self.score_generator(
                question=[item["question"] for item in evaluation_data[key]],
                essay=[item["essay"] for item in evaluation_data[key]],
                image=(
                    [item["image"] for item in evaluation_data[key]]
                    if TaskName.TASK_1.value == key
                    else None
                ),
                task_name=key,
                do_sample=False,
                temperature=0,
            )
            for key in evaluation_data.keys()
        }
        print(prediction_dict)

        evaluation_results = {}
        for key, predictions in prediction_dict.items():
            predictions = pd.DataFrame(predictions)
            golds = pd.DataFrame([item["human_rate"] for item in evaluation_data[key]])
            evaluation_results[key] = {
                criterion: self.evaluate_criterion(
                    predictions=predictions[criterion].tolist(), golds=golds[criterion].tolist()
                )
                for criterion in golds.columns
            }

        # self.logger.log(evaluation_result=evaluation_results, config=self.framework_config)

        return evaluation_results

There is a Factory to create all the Evaluator


class EvaluatorFactory:
    def __init__(self, framework_config, evaluation_config, **kwargs):
        self.framework_config = framework_config
        self.evaluation_config = evaluation_config
        self.kwargs = kwargs

        self.evaluators = self._create_evaluators()

    def _create_evaluators(self):
        evaluators = []
        evaluator_classes = Evaluator.__subclasses__()

        print(evaluators)
        for cls in evaluator_classes:
            evaluators.append(cls(framework_config=self.framework_config,
                                  evaluation_config=self.evaluation_config,
                                  **self.kwargs))
        return evaluators

    async def evaluate(self):
        evaluation_results = {}
        for evaluator in self.evaluators:
            results = await evaluator()
            evaluation_results[evaluator.__class__.__name__] = results

        return evaluation_results

async def main():
    evaluator_factory = EvaluatorFactory(framework_config=config.framework_config,
                                         evaluation_config=config.evaluation_config)

    result = await evaluator_factory.evaluate()
    print(result)
    return result

When I run, I noticed that, it just printed:

this is called

`` without printing the print("this is called too!")

I'm so confused. Thank you!


Edit 1

This is how I use it:

    async def evaluate(self):
        evaluation_results = {}
        for evaluator in self.evaluators:
            results = await evaluator()
            evaluation_results[evaluator.__class__.__name__] = results

        return evaluation_results

This is override function in the child-class:

    async def __call__(self, *args, **kwargs):
        prediction = self.predict(arg, kwargs)
        ...
        return evaluation_results

I feel like this is quite not logic to implement, but the idea is to force every child-class run the wrapper decorator


Edit 2

I think this can be done with MetaClass but I still got error (:


def log_results(func):
    """Decorator to log evaluation results."""
    print("this is called")

    @wraps(func)
    def wrapper(self, *args, **kwargs):
        print("this is called too!")
        print(self)
        result = func(self, *args, **kwargs)
        self.logger.log(evaluation_result=result, config=self.framework_config)
        return result
    return wrapper

# class EvaluatorMeta(ABCMeta):
#     def __init__(cls, name, bases, dct):
#         """Modify the class after it is created."""
#         super().__init__(name, bases, dct)
#         if "__call__" in dct:
#             setattr(cls, "__call__", log_results(getattr(cls, "__call__")))  # Apply decorator

class EvaluatorMeta(ABCMeta):
    def __init__(cls, name, bases, dct):
        """Only apply `log_results` when a child class of Evaluator is instantiated."""
        super().__init__(name, bases, dct)  # Call ABCMeta's __init__
        print(cls.__base__)
        if cls.__base__ is not ABC:  # Only modify child classes, not `Evaluator` itself
            setattr(cls, "__call__", log_results(getattr(cls, "__call__")))
            # cls.__call__ = log_results(cls.__call__)

class Evaluator(ABC, metaclass=EvaluatorMeta)
 ...

The issue is the wrapper use a property self.logger, which is initialized in the abstract class, and all the sub-classes want to use this property.

No the log_result should be add inside as the method, because result = func -> this func is a call func which need a self.

OMG

I'm creating a decorator to save log/results , but this is introduced to abstract class:

from functools import wraps

load_dotenv()

class Evaluator(ABC):
    def __init__(self, evaluation_config, framework_config, **kwargs):

        self.evaluation_config = evaluation_config
        self.framework_config = framework_config

        self.logger = WandbLogger()

    @abstractmethod
    def _create_metrics(self) -> List[Metric]:
        pass

    @abstractmethod
    def load_evaluation_data(self, data_path):
        pass

    @staticmethod
    def log_results(func):
        """Decorator to log evaluation results."""
        print("this is called")
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            print("this is called too!")
            result = func(*args, **kwargs)
            self.logger.log(evaluation_result=result, config=self.framework_config)
            return result

        return wrapper

    @log_results  
    @abstractmethod
    def __call__(self, *args, **kwargs):
        pass

One of the child-class:


class ScoreEvaluator(Evaluator):
    def __init__(self, evaluation_config, framework_config, **kwargs):
        super().__init__(evaluation_config, framework_config, **kwargs)

        self.metric_cls = self._create_metrics()

    def _create_metrics(self):
        metrics = []
        evaluator_classes = Metric.__subclasses__()

        for cls in evaluator_classes:
            metrics.append(cls())
        return metrics

    def load_evaluation_data(self, data_path):


        return evaluation_data

    def evaluate_criterion(self, predictions: List[float], golds: List[float]):

        results = {}
        for metric in self.metric_cls:
            results[metric.__class__.__name__] = metricpute(predictions, golds)

        return results

    async def __call__(self, *args, **kwargs):

        evaluation_data_path = os.getenv("EVALUATION_DATA_PATH")
        evaluation_data = self.load_evaluation_data(evaluation_data_path)
        evaluation_data = {key: value for key, value in evaluation_data.items()}

        prediction_dict = {
            key: await self.score_generator(
                question=[item["question"] for item in evaluation_data[key]],
                essay=[item["essay"] for item in evaluation_data[key]],
                image=(
                    [item["image"] for item in evaluation_data[key]]
                    if TaskName.TASK_1.value == key
                    else None
                ),
                task_name=key,
                do_sample=False,
                temperature=0,
            )
            for key in evaluation_data.keys()
        }
        print(prediction_dict)

        evaluation_results = {}
        for key, predictions in prediction_dict.items():
            predictions = pd.DataFrame(predictions)
            golds = pd.DataFrame([item["human_rate"] for item in evaluation_data[key]])
            evaluation_results[key] = {
                criterion: self.evaluate_criterion(
                    predictions=predictions[criterion].tolist(), golds=golds[criterion].tolist()
                )
                for criterion in golds.columns
            }

        # self.logger.log(evaluation_result=evaluation_results, config=self.framework_config)

        return evaluation_results

There is a Factory to create all the Evaluator


class EvaluatorFactory:
    def __init__(self, framework_config, evaluation_config, **kwargs):
        self.framework_config = framework_config
        self.evaluation_config = evaluation_config
        self.kwargs = kwargs

        self.evaluators = self._create_evaluators()

    def _create_evaluators(self):
        evaluators = []
        evaluator_classes = Evaluator.__subclasses__()

        print(evaluators)
        for cls in evaluator_classes:
            evaluators.append(cls(framework_config=self.framework_config,
                                  evaluation_config=self.evaluation_config,
                                  **self.kwargs))
        return evaluators

    async def evaluate(self):
        evaluation_results = {}
        for evaluator in self.evaluators:
            results = await evaluator()
            evaluation_results[evaluator.__class__.__name__] = results

        return evaluation_results

async def main():
    evaluator_factory = EvaluatorFactory(framework_config=config.framework_config,
                                         evaluation_config=config.evaluation_config)

    result = await evaluator_factory.evaluate()
    print(result)
    return result

When I run, I noticed that, it just printed:

this is called

`` without printing the print("this is called too!")

I'm so confused. Thank you!


Edit 1

This is how I use it:

    async def evaluate(self):
        evaluation_results = {}
        for evaluator in self.evaluators:
            results = await evaluator()
            evaluation_results[evaluator.__class__.__name__] = results

        return evaluation_results

This is override function in the child-class:

    async def __call__(self, *args, **kwargs):
        prediction = self.predict(arg, kwargs)
        ...
        return evaluation_results

I feel like this is quite not logic to implement, but the idea is to force every child-class run the wrapper decorator


Edit 2

I think this can be done with MetaClass but I still got error (:


def log_results(func):
    """Decorator to log evaluation results."""
    print("this is called")

    @wraps(func)
    def wrapper(self, *args, **kwargs):
        print("this is called too!")
        print(self)
        result = func(self, *args, **kwargs)
        self.logger.log(evaluation_result=result, config=self.framework_config)
        return result
    return wrapper

# class EvaluatorMeta(ABCMeta):
#     def __init__(cls, name, bases, dct):
#         """Modify the class after it is created."""
#         super().__init__(name, bases, dct)
#         if "__call__" in dct:
#             setattr(cls, "__call__", log_results(getattr(cls, "__call__")))  # Apply decorator

class EvaluatorMeta(ABCMeta):
    def __init__(cls, name, bases, dct):
        """Only apply `log_results` when a child class of Evaluator is instantiated."""
        super().__init__(name, bases, dct)  # Call ABCMeta's __init__
        print(cls.__base__)
        if cls.__base__ is not ABC:  # Only modify child classes, not `Evaluator` itself
            setattr(cls, "__call__", log_results(getattr(cls, "__call__")))
            # cls.__call__ = log_results(cls.__call__)

class Evaluator(ABC, metaclass=EvaluatorMeta)
 ...

The issue is the wrapper use a property self.logger, which is initialized in the abstract class, and all the sub-classes want to use this property.

No the log_result should be add inside as the method, because result = func -> this func is a call func which need a self.

OMG

Share Improve this question edited Feb 24 at 15:31 Dinosaur asked Feb 24 at 5:07 DinosaurDinosaur 254 bronze badges 8
  • 1 show how you run it. At this code you only assign decorator but this doesn't run code inside wrapper - it only return wrapper – furas Commented Feb 24 at 5:18
  • it prints it only when you execute __call__ - but this needs to use Evaluator to create other class with __call__ which will use super().__call__(self, *args, **kwargs), And later create instance of this class and use () on this instance to execute __call__ – furas Commented Feb 24 at 5:32
  • Hi @furas , I edited the question. I'm so confused, if I call wrapper() it needs self as parameter, but decorater must be class-level not instance level right? Then maybe cannot do this? – Dinosaur Commented Feb 24 at 5:56
  • Wait... Do I have to add super().__call__()? in the child-class :? – Dinosaur Commented Feb 24 at 6:01
  • 1 in ScoreEvaluator(Evaluator) you carete new __call__ which replaces Evaluator.__call__ - so it can't run code in wrapper. If you will use super().__call_() inside ScoreEvaluator.__call__ then it will execute code in Evaluator.__call__ and it will run wrapper. OR maybe use decorator directly on ScoreEvaluator.__call__ – furas Commented Feb 24 at 20:16
 |  Show 3 more comments

1 Answer 1

Reset to default 0

Okay I did it,

Basically, the approach in edit 2 is correct, I just fet to put async in the wrapper function.

Here is how I do it:

def log_results(func):
    """Decorator to log evaluation results."""

    @wraps(func)
    async def wrapper(self, *args, **kwargs):
        result = await func(self, *args, **kwargs)
        logger.log(evaluation_result=result, config=self.framework_config)
        return result

    return wrapper

class EvaluatorMeta(ABCMeta):
    def __init__(cls, name, bases, dct):
        """Only apply `log_results` when a child class of Evaluator is instantiated."""
        super().__init__(name, bases, dct)
        if cls.__base__ is not ABC:  # Only modify child classes, not `Evaluator` itself
            setattr(cls, "__call__", log_results(getattr(cls, "__call__")))

class Evaluator(ABC, metaclass=EvaluatorMeta):
    def __init__(self, evaluation_config, framework_config, **kwargs):

By adding metaclass, and set attribute decorator to the __call__ function of the child-class (not the parent class), it will automatically register for any class inherit from the parent class.

本文标签: pythonMy wrapper decorator which wraps an abstract method is not invokedStack Overflow