Decorator on Property not called - python

Consider this decorator:
from functools import wraps
def non_null(func):
#wraps(func)
def wrapper(*args, **kwargs):
print("Test")
assert all(not a is None for a in args)
return func(*args, *kwargs)
return wrapper
Around a property setter of a class function:
#non_null
#foo.setter
def foo(self, argument):
print("Setting")
self._a = argument
If the non_null decorator is used, neither the function nor decorator gets called.

It looks like the #foo.setter have to be the outmost decorator:
#foo.setter
#non_null
def foo(self, argument):
print("Setting")
self._a = argument
I could find no reference for that in the documentation, but examining the content of a class makes clear that if the property decorator in not the outmost one, you only get a simple method instead of a property.

Related

Applying class decorator to staticmethod: takes 1 positional argument but 2 were given

I am trying to apply a class decorator to each of its methods. But staticmethod and classmethod are not working for me. I got this:
import functools
import re
def decor(cls):
def decorator(f):
if isinstance(f, type):
for attr in f.__dict__:
if callable(getattr(f, attr)) and not re.match(r"__\w*__", attr):
setattr(f, attr, decorator(getattr(f, attr)))
return f
#functools.wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
return decorator(cls)
#decor
class MyClass:
def printer1(self, string):
print(string)
#classmethod
def printer2(cls, string):
print(string)
#staticmethod
def printer3(string):
print(string)
MyClass().printer1("foo") # this works
MyClass().printer2("foo") # this does not work
MyClass.printer2("foo") # this works
MyClass().printer3("foo") # this does not work
MyClass.printer3("foo") # this works
# the error is the following:
TypeError: printer() takes 1 positional argument but 2 were given
Basically every time I instantiate the class, I get the error when calling a staticmethod and a classmethod.
You don't seem to understand the working principle of staticmethod and classmethod. This is a link about the working principle of classmethod. The working principle of the staticmethod is similar to that of the classmethod. The difference is that when accessing the staticmethod, the class will not be bound to the function, but simply return the function.
There are two problems:
The decorator also wraps the class and returns a function instead of a class, which prevents staticmethod and classmethod from working properly.
For staticmethod and classmethod, getattr will get the result returned after the descriptor triggers the __get__ method, not necessarily the function itself. And here you only use wrapper to wrap, and do not restore them to the corresponding special methods.
Here is a working example:
def decorator(cls_or_func):
if isinstance(cls_or_func, type):
cls = cls_or_func
for name, attr in cls.__dict__.items():
if isinstance(attr, (staticmethod, classmethod)):
setattr(cls, name, type(attr)(decorator(attr.__func__)))
elif callable(attr) and not re.match(r"__\w*__", name):
setattr(cls, name, decorator(attr))
return cls
func = cls_or_func
#functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

Class decorators for methods in classes

How do class decorators for methods in classes work? Here is a sample of what I've done through some experimenting:
from functools import wraps
class PrintLog(object):
def __call__(self, func):
#wraps(func)
def wrapped(*args):
print('I am a log')
return func(*args)
return wrapped
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog()
def baz(self) -> None:
print('inside baz')
bar = foo('2')
print('running bar.baz()')
bar.baz()
And this works perfectly fine. However, I was under the impression that decorators do not need to be called with (), but when I remove the brackets from #PrintLog(), I get this error:
def baz(self) -> None:
TypeError: PrintLog() takes no arguments
Is there something I am missing/do not understand? I've also tried passing in a throwaway arg with __init__(), and it works.
class PrintLog(object):
def __init__(self, useless):
print(useless)
def __call__(self, func):
#wraps(func)
def wrapped(*args):
print('I am a log')
return func(*args)
return wrapped
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog("useless arg that I'm passing to __init__")
def baz(self) -> None:
print('inside baz')
Again, this works, but I don't want to pass any argument to the decorator.
tl;dr: This question in python 3.x.
Help appreciated!
Class decorators accept the function as a subject within the __init__ method (hence the log message), so your decorator code should look like:
class PrintLog(object):
def __init__(self, function):
self.function = function
def __call__(self):
#wraps(self.function)
def wrapped(*args):
print('I am a log')
return self.function(*args)
return wrapped
Sorry if this doesn’t work, I’m answering on my mobile device.
EDIT:
Okay so this is probably not what you want, but this is the way to do it:
from functools import update_wrapper, partial, wraps
class PrintLog(object):
def __init__(self, func):
update_wrapper(self, func)
self.func = func
def __get__(self, obj, objtype):
"""Support instance methods."""
return partial(self.__call__, obj)
def __call__(self, obj, *args, **kwargs):
#wraps(self.func)
def wrapped(*args):
print('I am a log')
return self.func(*args)
return wrapped(obj, *args)
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog
def baz(self) -> None:
print('inside baz')
bar = foo('2')
print('running bar.baz()')
bar.baz()
The decorator has to have the __get__ method defined because you're applying the decorator to an instance method. How would a descriptor have the context of the foo instance?
Ref: Decorating Python class methods - how do I pass the instance to the decorator?
There is a big picture you're missing.
#decorator
def foo(...):
function_definition
is almost identical (except for some internal mangling) to
temp = foo
foo = decorator(temp)
It doesn't matter what the decorator is, as long as it can act like a function.
Your example is equivalent to:
baz = PrintLog("useless thing")(<saved defn of baz>)
Since PrintLog is a class, PrintLog(...) creates an instance of PrintLog. That instance has a __call__ method, so it can act like a function.
Some decorators are designed to take arguments. Some decorators are designed not to take arguments. Some, like #lru_cache, are pieces of Python magic which look to see if the "argument" is a function (so the decorator is being used directly) or a number/None, so that it returns a function that then becomes the decorator.

Python3 impossible to pass #property as decorator argument

I've implemented decorator that can receive extra arguments and want to use it with class methods. I want to pass #property as decorator argument, but instead of #property result I got this:
<property object at 0x7f50f5195230>
This is my decorator:
class Decorator(object):
def __init__(self, some_arg):
self.func = None
self.some_arg = some_arg
def __get__(self, instance, owner):
import functools
return functools.partial(self.__call__, instance)
def __call__(self, func):
self.func = func
def wrapper(*args, **kwargs):
return self._process_sync(*args, **kwargs)
return wrapper
def _process_sync(self, *args, **kwargs):
try:
print(self.some_arg)
return self.func(*args, **kwargs)
except Exception as e:
print(e)
return None
My test class:
class Test(object):
#property
def some_data(self):
return {'key': 'value'}
#Decorator(some_data)
def some_method(self):
print('method output')
return None
Usage:
test = Test()
test.some_method()
Two questions:
How to correctly pass property to receive #property result instead of <property object at 0x7f50f5195230>
Does it possible to pass class properties/methods to the decorator if they are below in code?
A property object is a descriptor. To get a value out of it, you need to call its __get__ method with an appropriate instance. Figuring out when to do that in your current code is not easy, since your Decorator object has a bunch of different roles. It's both a decorator factory (getting initialized with an argument in the #Decorator(x) line), and the decorator itself (getting called with the function to be decorated). You've given it a __get__ method, but I don't expect that to ever get used, since the instance of Decorator never gets assigned to a class variable (only the wrapper function that gets returned from __call__).
Anyway, here's a modified version where the Decorator handles almost all parts of the descriptor protocol itself:
class Decorator:
def __init__(self, arg):
self.arg = arg # this might be a descriptor, like a property or unbound method
def __call__(self, func):
self.func = func
return self # we still want to be the descriptor in the class
def __get__(self, instance, owner):
try:
arg = self.arg.__get__(instance, owner) # try to bind the arg to the instance
except AttributeError: # if it doesn't work, self.arg is not a descriptor, that's OK
arg = self.arg
def wrapper(*args, **kwargs): # this is our version of a bound method object
print(arg) # do something with the bound arg here
return self.func.__get__(instance, owner)(*args, **kwargs)
return wrapper

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

Decorators for Instance Methods

Wrapping a class's method in a "boilerplate" Python decorator will treat that method as a regular function and make it lose its __self__ attribute that refers to the class instance object. Can this be avoided?
Take the following class:
class MyClass(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
def meth(self):
pass
If meth() is undecorated, MyClass().meth.__self__ refers to an instance method and enables something like setattr(my_class_object.meth.__self__, 'a', 5).
But when wrapping anything in a decorator, only the function object is passed; the object to which it is actually bound is not passed on along with it. (See this answer.)
import functools
def decorate(method):
#functools.wraps(method)
def wrapper(*args, **kwargs):
# Do something to method.__self__ such as setattr
print(hasattr(method, '__self__'))
result = method(*args, **kwargs)
return result
return wrapper
class MyClass(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
#decorate
def meth(self):
pass
MyClass().meth()
# False <--------
Can this be overriden?
Your main misunderstanding here is order of operation.
When the decorate() decorator is called, meth() is not a method yet - it is still a function - it is only when the class block is over that meth is transformed into a method by the metaclass descriptors! - that's why it doesn't have __self__ (yet).
In other words, to decorate methods you have to ignore the fact that they are methods and treat them as normal functions - because that's what they are when the decorator is called.
In fact, the original meth function will never turn into a method - instead the function wrapper you returned from the decorator will be part of the class and will be the one that will get the __self__ attribute later.
If you decorate method of the class, first argument is always self object (you can access it with args[0]):
import functools
def decorate(method):
#functools.wraps(method)
def wrapper(*args, **kwargs):
print(hasattr(args[0], 'a'))
result = method(*args, **kwargs)
return result
return wrapper
class MyClass(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
#decorate
def meth(self):
pass
MyClass().meth()
Prints:
True
Edit:
You can specify also self in your wrapper function (based on comments):
import functools
def decorate(method):
#functools.wraps(method)
def wrapper(self, *args, **kwargs):
print(hasattr(self, 'a'))
result = method(self, *args, **kwargs)
return result
return wrapper
class MyClass(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
#decorate
def meth(self):
pass
MyClass().meth()
Prints also:
True
Let me clarify the process of decorating:
When you decorate meth with decorate in class MyClass, you are doing:
class MyClass(object):
... omit
meth = decorate(meth) # the meth in "()" is the original function.
As you can see, decorate takes method which is a function as parameter and return wrapper which is another funtion. And now the original meth in MyClass is replaced by new one wrapper. So when you call myclass_instance.meth(), you are calling the new wrapper function.
There isn't any black magic, so self can be definitely passed into wrapper, and it is safe to accept self using wrapper(self, *args, **kwargs).

Categories