Atttaching an attribute to a method in Python - python

I have a class:
class A:
def foo(self):
print("A.foo")
I want a method of the class to have an attribute so I can do something like A.foo.b.
I tried creating a decorator which returns a callable class:
class AttrStorage:
def __init__(self, b, method):
self.b = b
self.method = method
def __call__(self, *args, **kwargs):
return self.method(*args, **kwargs)
def add_attr(b):
def wrapper(method):
return AttrStorage(b, method)
return wrapper
So I could use it like this:
class A:
#add_attr(1)
def foo(self):
print("A.foo")
Fortunately A.foo.b worked, but when I used the method, it gave an error:
>>> A.foo.b
1
>>> A().foo()
TypeError: foo() missing 1 required positional argument: 'self'
Is this possible in python? If so, how do I do it?

Related

Python decorator in a class: missing required positional arguments:

class Something:
def __init__(self, ...):
...
def update(self):
...
def add_update(self, func):
def fct(*args, **kwargs):
self.update()
func(*args, **kwargs)
return fct
#add_update
def method(self, some_parameter):
...
So basically I have this class, and I want to call the function "update()" automatically before I call a method from the class. But I get this error:
TypeError: add_update() missing 1 required positional argument: 'func'
I don't really understand what's wrong here, also I saw some tutorials on the internet where they did something similar and it was working. Can someone explain me what's wrong here and how do I fix it?
What is a decorator? It's syntactic sugar for more verbose syntax.
#decorator
def my_fun():
...
is the same as
my_fun = decorator(my_fun)
So in your specific case
method = add_update(method)
Do you see a problem? Add update expects two parameters (self, and func), but here it gets only one (and actually you pass method as self).
To solve this you need to create decorator outside the class that gets only function, and that functions first parameter will be self:
def add_update(func):
def wrapper(*args):
self = args[0]
...
when you use #add_update, It is equivalent to add_update(method), At this time add_update is still a function.
You can make the following modifications to make it work
class Something:
def __init__(self):
...
def update(self):
...
def add_update(f):
def fct(self, *args, **kwargs):
self.update()
return f(self, *args, **kwargs)
return fct
#add_update
def method(self, some_parameter):
...
You have to pass the argument func and define the class inside the decorator function.
class Something:
def __init__(self,..):
...
def update(self):
...
def add_update( func):
def fct(*args, **kwargs):
args[0].update() #or Something.update()
func(*args, **kwargs)
return fct
#add_update
def method(self, some_parameter):
...

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

Member function decorator and self argument

The following minimal example of a decorator on a member function:
def wrap_function(func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
return wrapper
class Foo:
#wrap_function
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
outputs:
(<__main__.Foo object at 0x7fb294939898>, 'hi')
{}
So self is one of the args.
However when using a wrapper class:
class WrappedFunction:
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
print(args)
print(kwargs)
def wrap_function(func):
return WrappedFunction(func)
class Foo:
#wrap_function
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
the output is:
('hi',)
{}
So the self, that references the Foo object, is not accessible in the body of __call__ of the WrappedFunction object.
How can I make it accessible there?
You're losing the reference to your bounded instance by wrapping the function logic (but not the instance) and redirecting it to a class instance - at that point, the class instance's own self applies instead of the wrapped instance method as it gets lost in the intermediary decorator (wrap_function()).
You either have to wrap the call to the wrapped function and pass *args/**kwargs to it, or just make a proper wrapper class instead of adding an intermediary wrapper:
class WrappedFunction(object):
def __call__(self, func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
# NOTE: `WrappedFunction` instance is available in `self`
return wrapper
class Foo:
#WrappedFunction() # wrap directly, without an intermediary
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
# (<__main__.Foo object at 0x000001A2216CDBA8>, 'hi')
# {}
Sadly, but this might be the only solution as you need it in the __call__ function.
Would suggest checking this out: What is the difference between __init__ and __call__ in Python?
def wrap_function(func):
def wrapper(*args, **kwargs):
x = WrappedFunction(func)
x(*args, **kwargs)
return wrapper

How to pass an argument to a method decorator

I have a method decorator like this.
class MyClass:
def __init__(self):
self.start = 0
class Decorator:
def __init__(self, f):
self.f = f
self.msg = msg
def __get__(self, instance, _):
def wrapper(test):
print(self.msg)
print(instance.start)
self.f(instance, test)
return self.f
return wrapper
#Decorator
def p1(self, sent):
print(sent)
c = MyClass()
c.p1('test')
This works fine. However, If I want to pass an argument to the decorator, the method is no longer passed as an argument, and I get this error:
TypeError: init() missing 1 required positional argument: 'f'
class MyClass:
def __init__(self):
self.start = 0
class Decorator:
def __init__(self, f, msg):
self.f = f
self.msg = msg
def __get__(self, instance, _):
def wrapper(test):
print(self.msg)
print(instance.start)
self.f(instance, test)
return self.f
return wrapper
#Decorator(msg='p1')
def p1(self, sent):
print(sent)
#Decorator(msg='p2')
def p2(self, sent):
print(sent)
How do I pass an argument to the decorator class, and why is it overriding the method?
The descriptor protocol doesn't serve much of a purpose here. You can simply pass the function itself to __call__ and return the wrapper function without losing access to the instance:
class MyClass:
def __init__(self):
self.start = 0
class Decorator:
def __init__(self, msg):
self.msg = msg
def __call__(self, f):
def wrapper(instance, *args, **kwargs):
print(self.msg)
# access any other instance attributes
return f(instance, *args, **kwargs)
return wrapper
#Decorator(msg='p1')
def p1(self, sent):
print(sent)
>>> c = MyClass()
>>> c.p1('test')
p1
test
A decorator will be called.
In your case you receive the function as a parameter in the __call__ method
class MyClass:
def __init__(self):
self.start = 0
class Decorator:
def __init__(self, msg):
self.msg = msg
def __call__(self, f):
self.f = f
return self
def __get__(self, instance, _):
def wrapper(test):
print(self.msg)
self.f(instance, test)
return self.f
return wrapper
#Decorator(msg='p1')
def p1(self, sent):
print(sent)
#Decorator(msg='p2')
def p2(self, sent):
print(sent)
Your first example works because calling the Class creates an instance and the function is the parameter.
But in your second example you call the Class manually to set the msg parameter, so you the decoration process calls what's left, i.e.: the instance and that goes to the __call__ method.
When you call a decorator with arguments, the function you call isn't actually working as a decorator itself. Rather, it's a decorator factory (a function or other callable that returns something that will act as the decorator). Usually you solve this by adding an extra level of nested functions. Since you're defining your decorator with a class, that's a bit awkward to do directly (though you probably could make it work). But there doesn't really seem to be any need for your decorator to be a class, as long as you handle self in the wrapper function (it will be the instance of MyClass now, rather than an instance of a Decorator class):
class MyClass:
def __init__(self):
self.start = 0
def decorator_factory(msg):
def decorator(f):
def wrapper(self, test): # you might want to replace test with *args and **kwargs
print(msg)
print(self.start)
return f(self, test)
return wrapper
return decorator
#decorator_factory(msg='p1')
def p1(self, sent):
print(sent)
#decorator_factory(msg='p2')
def p2(self, sent):
print(sent)
I named the decorator factory the way I did to be explicit about the different levels of nested functions, but you should of course use something that's actually meaningful for your use case as the top level name. You might also want to move it out of the class namespace, since it will be available to call on all instances of MyClass (with possibly silly results, since it's not intended to be a method).

Python class method decorator

I write a decorator for class method
def decor(method):
def wrapped(self, *args, **kwargs):
return method(self, *args, **kwargs)
# [*]
return wrapped
I would like use this like:
class A(metaclass=mymetaclass):
#decor
def meth(self):
pass
How I can in decorator add method/variable to class which has decorated method? I need it do near [*].
Inside wrapped I could write self.__class__, but what to do here?
I cannot imagine a way to meet such a requirement, because decor function only receives a function object that knows nothing about a containing class.
The only workaround that I can imagine is to use a parameterized decorator and pass it the class being decorated
def decor(cls):
def wrapper(method):
def wrapped(self, *args, **kwargs):
return self.method(*args, **kwargs)
print method # only a function object here
return wrapped
print cls # here we get the class and can manipulate it
return wrapper
class A
#decor(A)
def method(self):
pass
Alternatively, you could decorate the class itself:
def cdecor(cls):
print 'Decorating', cls # here we get the class and can manipulate it
return cls
#cdecor
class B:
def meth(self):
pass
gives:
Decorating __main__.B
It looks like you just wanted to decorate one of a classes functions, not specifically an #classmethod. Here's a simple way that I did it when I wanted to call a classes save function when the function returned a successful result:
def save_on_success(func):
""" A decorator that calls a class object's save method when successful """
def inner(self, *args, **kwargs):
result = func(self, *args, **kwargs)
if result:
self.save()
return result
return inner
Here is an example of how it was used:
class Test:
def save(self):
print('saving')
#save_on_success
def test(self, var, result=True):
print('testing, var={}'.format(var))
return result
Testing to make sure it works as expected:
>>> x = Test()
>>> print(x.test('test True (should save)', result=True))
testing, var=test True (should save)
saving
True
>>> print(x.test('test False (should not save)', result=False))
testing, var=test False (should not save)
False
It looks like it is not directly possible, according to this response :
Get Python function's owning class from decorator
What you could do instead is providing a decorator for your class, something like that :
class InsertMethod(object):
def __init__(self, methodToInsert):
self.methodToInsert = methodToInsert
def __call__(self, classObject):
def wrapper(*args, **kwargs):
setattr(classObject, self.methodToInsert.__name__, self.methodToInsert)
return classObject(*args, **kwargs)
return wrapper
def IWillBeInserted(self):
print "Success"
#InsertMethod(IWillBeInserted)
class Something(object):
def __init__(self):
pass
def action(self):
self.IWillBeInserted()
a = Something()
a.action()
Actually, you may decorate the class itself:
def class_decorator(class_):
class_.attribute = 'value'
class_.method = decorate(class_.method)
return class_
#class_decorator
class MyClass:
def method(self):
pass
I'm a little late to the party, but late is better than never eh? :)
We can do this by decorating our class method with a decorator which is itself a class object, say B, and then hook into the moment when Python calls B.__get__ so to fetch the method. In that __get__ call, which will be passed both the owner class and the newly generated instance of that class, you can elect to either insert your method/variable into the original owner class, or into the newly defined instance.
class B(object):
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
return self.f(*args, **kwargs)
def __get__(self, instance, owner):
instance.inserted = True
# owner.inserted = True
def wrapper(*args, **kwargs):
return self(instance, *args, **kwargs)
return wrapper
class A:
#B
def method(self):
pass
if __name__ == "__main__":
a = A()
a.method()
b = A()
print(hasattr(a, 'inserted'))
print(hasattr(b, 'inserted'))
In this example, we're wrapping def method(self) with #B. As written, the inserted attribute inserted will only persist in the a object because it's being applied to the instance. If we were to create a second object b as shown, the inserted attribute is not included. IE, hasattr(a, 'inserted') prints True and hasattr(b, 'inserted') prints False. If however we apply inserted to the owner class (as shown in the commented out line) instead, the inserted attribute will persist into all future A() objects. IE hasattr(a, 'inserted') prints True and hasattr(b, 'inserted') prints True, because b was created after a.method() was called.

Categories