Decorate all function in a class by using metaclass - python

Lets say my class has many function, and I want to apply my decorator on each one of them. I have researched for a while, and find https://stackoverflow.com/a/6307917/18859252. By using metaclass, I can decorate all function in one line.
Here is my code (framework)
class myMetaClass(type):
def __new__(cls, name, bases, local):
for attr in local:
value = local[attr]
if callable(value) and attr != '__init__':
local[attr] = log_decorator(local['Variable'], value)
return super().__new__(cls, name, bases, local)
class log_decorator():
def __init__(self, Variable, func):
self.Variable = Variable
self.func = func
def __call__(self, *args, **kargs):
start_time = time.time()
self.func(*args, **kargs)
end_time = time.time()
class Test(metaclass = myMetaClass):
Variable = Some_Class
check_test = Some_Class
def __init__(self, **args):
self.connect = Some_Class(**args)
def A(self, a, b):
self.connect.abc
pass
then use like this
def Flow():
test = Test(**args)
test.A(a, b)
But here is problem, it show exception like:
TypeError:A() missing 1 required positional argument: 'self'
I have no idea about this problem. I'd be very grateful if anyone has an answer or if there is a better way.

The piece you are missing (and the bit I don't fully understand, but has to do with functions or methods as descriptors and how python will attach an instance as the self parameter) is that log_decorator() is an instance of that class and not a function or method (even though you have defined a __call__() method which makes it callable.)
Here is some code which just slightly changes the syntax needed, but gives you the results you want:
import functools
class log_decorator:
def __init__(self, Variable): # Note that the only parameter is Variable
self.Variable = Variable
def __call__(self, func):
#functools.wraps(func)
def decorated(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
return decorated
class myMetaClass(type):
def __new__(cls, name, bases, local):
for attr in local:
value = local[attr]
if callable(value) and attr != '__init__':
# Note the change in syntax vvv
local[attr] = log_decorator(local['Variable'])(value)
return super().__new__(cls, name, bases, local)

Related

Passing instance variables to a decorator

I found this useful decorator that allows you to pass in some optional arguments
def mlflow_experiment(
_func=None,
*,
experiment_name=None
):
def experiment_decorator(func):
#functools.wraps(func)
def experiment_wrapper(self, *args, **kwargs):
nonlocal experiment_name
experiment_id = (
mlflow.set_experiment(experiment_name)
if experiment_name is not None
else None
)
...
value = func(self, *args, **kwargs)
return value
return experiment_wrapper
if _func is None:
return experiment_decorator
else:
return experiment_decorator(_func)
So in a use case like this where I just pass in a string to experiment_name, the code works flawlessly.
#mlflow_experiment(autolog=True, experiment_name = 'blarg')
def train_mlflow(self, maxevals=50, model_id=0):
...
I've always had a hard time figuring out scope in decorators but I wasn't surprised that using passing an instance variable defined in __init__ does NOT work.
class LGBOptimizerMlfow:
def __init__(self, arg):
self.arg = arg
#mlflow_experiment(autolog=True, experiment_name = self.arg)
def train_mlflow(self, maxevals=50, model_id=0):
...
>>> `NameError: name 'self' is not defined`
Just to see if scoping was an issue, I declared the variable outside the class and it worked.
And just for the heck of it I decided to declare a global variable inside the class which also works but its less than ideal, especially if I want to pass it into the class or a method as a optional argument.
class LGBOptimizerMlfow:
global arg
arg = 'hi'
#mlflow_experiment(autolog=True, experiment_name = arg)
def train_mlflow(self, maxevals=50, model_id=0):
...
Any help to revise the code so that the decorator accepts an instance variable would be lovely.
Thank you!
Decorators are called while the class is being defined, and self is simply a parameter used for each instance method, not something the class itself provides. So self is not defined at the time you need it to be for use as an argument to your decorator.
You need to modify experiment_wrapper to take a name directly from its self argument, rather than from an argument to mflow_experiment. Something like
def mlflow_experiment(
_func=None,
*,
experiment_name=None,
tracking_uri=None,
autolog=False,
run_name=None,
tags=None,
):
def experiment_decorator(func):
#functools.wraps(func)
def experiment_wrapper(self, *args, **kwargs):
nonlocal tracking_uri
experiment_name = getattr(self, 'experiment_name', None)
experiment_id = (
mlflow.set_experiment(experiment_name)
if experiment_name is not None
else None
)
...
with mlflow.start_run(experiment_id=experiment_id
, run_name=run_name
, tags=tags):
value = func(self, *args, **kwargs)
return value
return experiment_wrapper
if _func is None:
return experiment_decorator
else:
return experiment_decorator(_func)
Then you need to make sure that each instance has an experiment name (or None) associated with it.
class LGBOptimizerMlfow:
def __init__(self, arg, experiment_name=None):
self.arg = arg
self.experiment_name = experiment_name
#mlflow_experiment(autolog=True, experiment_name = self.arg)
def train_mlflow(self, maxevals=50, model_id=0):
...
Another alternative is to make experiment_name an argument to train_mflow, making it easier to create different names with the same method. (This may be closer to what you were intending.)
class LGBOptimizerMlfow:
def __init__(self, arg):
self.arg = arg
#mlflow_experiment(autolog=True)
def train_mlflow(self, maxevals=50, model_id=0, experiment_name=None):
if experiment_name is None:
self.experiment_name = self.arg
...
The definition of the decorator remains the same as shown above.

Extending behavior of the property decorator

I would like to extend the behavior of the builtin #property decorator. The desired usage is shown in the code below:
class A:
def __init__(self):
self.xy = 42
#my_property(some_arg="some_value")
def x(self):
return self.xy
print(A().x) # should print 42
First of all, the decorator should retain the property behavior so that no () is needed after the x. Next, I would like to be able to access the arguments a programmer passes to my decorator.
I started off with this:
class my_property(property):
def __init__(self, fn):
super().__init__(fn)
TypeError: __init__() got an unexpected keyword argument 'some_arg'
After adding **kwargs:
class my_property(property):
def __init__(self, fn, **kwargs):
super().__init__(fn)
TypeError: __init__() missing 1 required positional argument: 'fn'
OK, let's do *args instead:
class my_property(property):
def __init__(self, *args, **kwargs):
super().__init__(*args)
TypeError: 'my_property' object is not callable
Let's make it callable:
class my_property(property):
def __init__(self, *args, **kwargs):
super().__init__(*args)
def __call__(self, *args, **kwargs):
pass
No errors, but prints None instead of 42
And now I am lost. I have not even yet managed to access `some_arg="some_value" and the property behavior seems to be already gone. What is wrong and how to fix it?
It's not clear how you intent to use some_arg, but to pass a parameter to a decorator you need to have "two layers" of decorators
#my_decorator(arg)
def foo():
return
under the hood this translates to my_decorator(arg)(foo) (i.e. my_decorator(arg) must return another decorator that is called with foo). The inner decorator in this case should be your custom implementation of property
def my_property(some_arg):
class inner(object):
def __init__(self, func):
print(some_arg) # do something with some_arg
self.func = func
def __get__(self, obj, type_=None):
return self.func(obj)
return inner
Now you can use it like this:
class MyClass:
def __init__(self, x):
self.x = x
#my_property('test!')
def foo(self):
return self.x
obj = MyClass(42) # > test!
obj.foo # > 42
Read more about descriptors here

How to fix this TypeError for missing 'self' argument

Getting TypeError on those methods where i used self as a param like missing 1 required positional argument: 'self'. If the method have more than 1 arguments including self it shows similar error of missing another positional argument.
I started by making a singleton object that checks its own references to remain its singularity. I used __new__ to do that.
core.py
import pyglet.window
class Core(object):
def __new__(cls, *args, **kwargs):
if not cls:
cls = object.__new__(cls, *args, **kwargs)
return cls
def __init__(self):
self._instance = None
self._window = None
def __repr__(self):
return '<%s object at %s>' % (Core.__name__, hex(id(Core)))
def scene(self, width, height):
self._window = pyglet.window.Window(width=width, height=height)
script.py
from core import Core
c = Core()
print(c.__repr__())
c.scene(640, 480)
errors:
print(c.__repr__())
TypeError: __repr__() missing 1 required positional argument: 'self'
c.scene(640, 480)
TypeError: scene() missing 1 required positional argument: 'height'
This method is not the only case, so i want a understanding what is going on and how to fix this.
Thank you for your time.
Edit:
It is not problem with only the__repr__. I want the other methods like scene(self, width, height) or any methods you could possibly create having self as one param. The second error for showing what error this code gives with other methods
I need the Core class to make a singleton object, so i can use other files to reference to this Core._window thing. I think this edited description can clarify what i wanted earlier. Sorry for convenience.
Oops, __new__ receives the class and is supposed to return the newly created object! As your code returns the class itself, thing go wrong. You should have:
class Core(object):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
return obj
...
And anyway __new__ is an advanced configuration, with is mainly used for immutable objects that cannot be configured in __init__. In your example you should just remove it:
class Core(object):
def __init__(self):
self._instance = None
self._window = None
def __repr__(self):
return '<%s object at %s>' % (Core.__name__, hex(id(Core)))
That is enough for common objects. You can indeed use __new__ to build singletons, and even use it to build hierachies of singletons, meaning that every subclass of Core will be a singleton:
class Core(object):
_obj = None
def __new__(cls, *args, **kwargs):
if cls._obj is None or not isinstance(cls._obj, cls):
cls._obj = object.__new__(cls, *args, **kwargs)
return cls._obj
def __init__(self):
self._instance = None
self._window = None
def __repr__(self):
return '<%s object at %s>' % (Core.__name__, hex(id(self)))
Please note that my class __repr__ uses id(self) to identify the object and not the class.
Demo:
>>> c = Core()
>>> d = Core()
>>> c is d
True
>>> class Child(Core):
pass
>>> ch = Child()
>>> ch is c
False
>>> ch2 = Child()
>>> ch2 is ch
True
The correct way is use repr like this:
repr(c)
Your code almost works. Take a look here:
class Core(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls, *args, **kwargs)
return cls._instance
def __init__(self):
pass
def __repr__(self):
return '<%s object at %s>' % (Core.__name__, hex(id(self)))
core = Core()
print(id(core))
core1 = Core()
print(id(core1))
print(repr(core))
print(repr(core1))

Python: Arbitrary Class Modifier

While I managed to construct a manageable answer to my question:
class A(object):
def __init__(self, B, *args, **kwargs):
''' Modifies input class B, rendering it a super class of class A'''
_parent = {}
method_list = [func for func in dir(self) if callable(getattr(self, func))]
for att in B.__dir__():
_parent[att] = B.__getattribute__(att)
if att not in method_list:
try:
self.__setattr__(att, B.__getattribute__(
att))
except:
pass
B.__init__(self, *args, **kwargs)
self.__parent__ = _parent
#add self variables here
def func(self):
#modify inherited func here
self.__parent__['func']()
#modify inherited func here
I do not know if it always works and I would like to know if someone else has a better solution to this (rather trivial for other languages) question. Also, this solution is only applicable in Python3 and above (otherwise inspect module is needed for the replacement of the callable part)

Automatically decorate superclass functions in subclass?

I have a subclass that adds graphics capabilities to a superclass that implements the algorithms. So, in addition to a few extra initialization functions, this subclass will only need to refresh the graphics after the execution of each algorithm-computing function in the superclass.
Classes:
class graph(algorithms):
... #initialization and refresh decorators
#refreshgraph
def algorithm1(self, *args, **kwargs):
return algorithms.algorithm1(self, *args, **kwargs)
#refreshgraph
def algorithm2(self, *args, **kwargs):
return algorithms.algorithm2(self, *args, **kwargs)
... #and so on
Is there an pythonic way to automatically decorate all the non-private methods defined in the superclass, such that if I add a new algorithm there I don't need to explicitly mention it in my subclass? I would also like to be able to explicitly exclude some of the superclass' methods.
The subclass always gets all the methods from the parent class(es) by default. If you wish to make emulate the behavior other languages use for privacy (eg the 'private' or 'protected' modifiers in C#) you have two options:
1) Python convention (and it's just a convention) is that methods with a single leading underscore in their names are not designed for access from outside the defining class.
2) Names with a double leading underscore are mangled in the bytecode so they aren't visible to other code under their own names. ParentClass.__private is visible inside ParentClass, but can only be accessed from outside ParentClass as ParentClass._ParentClass__private. (Great explanations here). Nothing in Python is truly private ;)
To override an inherited method just define the same name in a derived class. To call the parent class method inside the derived class you can do it as you did in your example, or using super:
def algorithm2(self, *args, **kwargs):
super(graph, self).algorithm2(self, *args, **kwargs)
# do other derived stuff here....
self.refresh()
This is ugly, but I think it does what you want, but without inheritance:
class DoAfter(object):
def __init__(self, obj, func):
self.obj = obj
self.func = func
def __getattribute__(self, attr, *a, **kw):
obj = object.__getattribute__(self, 'obj')
if attr in dir(obj):
x = getattr(obj, attr)
if callable(x):
def b(*a, **kw):
retval = x(*a, **kw)
self.func()
return retval
return b
else:
return x
else:
return object.__getattribute__(self, attr)
Use it like this:
>>> class A(object):
... def __init__(self):
... self.a = 1
...
... def boo(self, c):
... self.a += c
... return self.a
>>> def do_something():
... print 'a'
>>> a = A()
>>> print a.boo(1)
2
>>> print a.boo(2)
4
>>> b = DoAfter(a, do_something)
>>> print b.boo(1)
a
5
>>> print b.boo(2)
a
7
A increments a counter each time A.boo is called. DoAfter wraps A, so that any method in the instance a can be called as if it were a member of b. Note that every method is wrapped this way, so do_something() is called whenever a method is accessed.
This is barely tested, not recommended, and probably a bad idea. But, I think it does what you asked for.
EDIT: to do this with inheritance:
class graph(algorithms):
def refreshgraph(self):
print 'refreshgraph'
def __getattribute__(self, attr):
if attr in dir(algorithms):
x = algorithms.__getattribute__(self, attr)
if callable(x):
def wrapped(*a, **kw):
retval = x(*a, **kw)
self.refreshgraph()
return retval
return wrapped
else:
return x
else:
return object.__getattribute__(self, attr)

Categories