How can the class instance be accessed from a decorated method at runtime?

北慕城南 提交于 2020-03-06 09:36:06

问题


I am trying to write a python decorator, that allows tracking an integer property in a class (which is responsible for a model run). By tracking I mean adding every change to a local database my_db.db. This works well with the given code below, as you can also be seen in the screenshot of the database entries.

The problem I have is that I cannot differentiate between the integer properties of two instances of the MockModel. I created the field instance_id in order to differentiate between several instances and wanted to add the unique python id as a instance reference (id(python_object)). This does not work, because the decorator is defined beforehand and does not take into account the instances created at runtime.

Code:

import functools
import peewee as pw

db = pw.SqliteDatabase('my_db.db')

class BaseModel(pw.Model):
    class Meta:
        database = db

class IntProperty(BaseModel):
    name = pw.CharField()
    value = pw.IntegerField()
    instance_id = pw.CharField(null=True)

def track_int_property():

    def add_to_db(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):

            # Run Setter normally
            output = func(*args, **kwargs)

            # Add IntProperty to db
            IntProperty.create(name=func.__name__,
                               value=args[1],
                               instance_id='default') # How is it possible to pass the instance id id(instance) to the decorator?
            return output
        return wrapper
    return add_to_db




class MockModel(object):

    def __init__(self, start_age):
        self.__model_age = start_age
        self.model_age = self.__model_age  # initialize since you want first value for tracking

    @property
    def model_age(self):
        return self.__model_age

    @model_age.setter
    @track_int_property()
    def model_age(self, value):
        self.__model_age = value

    def age(self):
        self.model_age += 1

# Run
db.connect()
db.create_tables([IntProperty])

mm = MockModel(2)
mm2 = MockModel(4)
for i in range(10):
    mm.age()
    mm2.age()

Screenshot of Database

The problem: The model_age cannot be differentiated between mm and mm2. I therefore would like to have a unique id instead of 'default' for the column instance_id.

Things I have tried:

  1. Write an additional class Decorator that changes the __init__ function and adds a unique_id-field when the object is created. This did not work because I could not access the class instance from within the track_int_property decorator. Is there a way to access the class instance with this approach?

  2. Write an additional class Decorator that adds another method-decorator on top of track_int_property at run-time. Similar to the answer in this question.

  3. Have an additional class Decorator for MockModel that overwrites getattribute and adds a decorator with an id-argument every time the model_age.setter is called. The setter was marked beforehand with a modified version of the track_int_property. See code below, inspired from this blog:

    def class_decorator_int_tracker(Cls):
        class NewCls(object):
            def __init__(self, *args, **kwargs):
                self.oInstance = Cls(*args, **kwargs)

            def __getattribute__(self, s):
                """
                source:
                https://www.codementor.io/@sheena/advanced-use-python-decorators-class-function-du107nxsv
                """
                try:
                    x = super(NewCls, self).__getattribute__(s)
                except AttributeError:
                    pass
                else:
                    return x
                x = self.oInstance.__getattribute__(s)
                if type(x) == type(self.__init__) and hasattr(x, "int_property_marker"): # it is an instance method
                    return track_int_property(id(self))(x) # decorating here. Problem: This line is never reached, because the int_property_marker is not found
                else:
                    return x

        NewCls.__name__ = Cls.__name__  # Overwrite old class
        return NewCls

Modified track_int_property: takes input argument and passes it to the Database Entry:

def track_int_property(unique_id_argument):

        def add_to_db(func):

            @functools.wraps(func)
            def wrapper(*args, **kwargs):

                # Run Setter normally
                output = func(*args, **kwargs)

                # Add IntProperty to db
                IntProperty.create(name=func.__name__,
                                   value=args[1],
                                   instance_id=unique_id_argument) 
                return output
            return wrapper
        return add_to_db

New mark_int_property that marks the methods that need to be decorated at runtime:

def mark_int_property():

    def mark(func):
        # Add marker, to know which methods should be decorated at runtime
        func.int_property_marker = True

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            outputs = func(*args, **kwargs)
            return outputs
        return wrapper
    return mark

Modified MockModel:

@class_decorator_int_tracker
class MockModel(object):
    ...

    @model_age.setter
    @mark_int_property()  # Only mark the int_property and attach the main decorator (track_int_property) at runtime
    def model_age(self, value):
        self.__model_age = value
    ...

This approach did not work, because the @model_age.setter somehow overwrites the model_age, such that the attached marker disappeared and the track_int_property-decorator is never attached. I have tried the new class decorator with other functions, that are not properties. There, this approach worked without problems.

Do you have any idea how to achieve this? I think the main problem is that I cannot access the class instance and its properties (accessible through self) from within the decorator.

来源:https://stackoverflow.com/questions/60531297/how-can-the-class-instance-be-accessed-from-a-decorated-method-at-runtime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!