问题
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:
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 thetrack_int_property
decorator. Is there a way to access the class instance with this approach?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.Have an additional class Decorator for
MockModel
that overwrites getattribute and adds a decorator with an id-argument every time themodel_age.setter
is called. The setter was marked beforehand with a modified version of thetrack_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