Closed huzecong closed 3 years ago
From memory __set_name__
is one of a few rather special methods in the Python object model which you cannot override through inheritance. The only way of intercepting updates to it is to use:
@property
def __name__(self):
return self.__wrapped__.__name__
@__name__.setter
def __name__(self, value):
self.__wrapped__.__name__ = value
At least this is what wrapt
itself had to do for it to work.
BTW, what exactly are you trying to do? Missing context of why you would want to change the name.
Hmm, I'm not sure I understand. __set_name__
doesn't change the __name__
of the object, it's part of the descriptor protocol (since PEP 487, which was implemented since Python 3.6). __set_name__
is called during class construction (as part of type.__new__
) to make the descriptor aware of its containing class and attribute name.
The reason I need __set_name__
is because I'm implementing something similar to Python 3.8's cached_property
(code). I want to store the cached value as an attribute on the instance, and to avoid name clashes, I need to know the attribute name of the descriptor.
Is what you are expecting:
import wrapt
@wrapt.decorator
def decorator(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)
class descriptor_wrapper:
def __init__(self, descriptor):
self.__wrapped__ = descriptor
def __set_name__(self, owner, name):
print("__set_name__", self, owner, name)
if hasattr(self.__wrapped__, "__set_name__"):
self.__wrapped__.__set_name__(owner, name)
def __get__(self, instance, owner=None):
print("__get__", self, instance, owner)
return self.__wrapped__.__get__(instance, owner)
class FunctionWrapper(wrapt.FunctionWrapper):
def __set_name__(self, owner, name):
print("FunctionWrapper:__set_name__", self, owner, name)
if hasattr(self.__wrapped__, "__set_name__"):
self.__wrapped__.__set_name__(owner, name)
def custom_decorator(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)
def custom_wrapper(func):
return FunctionWrapper(func, custom_decorator)
class MyClass:
@descriptor_wrapper
def this_works(self):
pass
#@decorator
@custom_wrapper
@descriptor_wrapper
def this_doesnt_work(self):
print('this_doesnt_work', self)
a = MyClass()
a.this_doesnt_work()
Ah hah, this is it! Would it make sense to add a __set_name__
to ObjectProxy
? I can put up a PR if you agree this is the right thing to do.
Alternatively, is there anyway to supply a custom FunctionWrapper
class into wrapt.decorator
? I think this would have more benefits, e.g. custom __reduce__
methods to make decorated functions pickle-able.
Have made changes to add __set_name__()
propagation to FunctionWrapper
, but not ObjectProxy
.
You can try develop
branch if you want to verify it works for you.
pip install -U https://github.com/GrahamDumpleton/wrapt/archive/refs/heads/develop.zip
The __set_name__
support when using wrapt.decorator
is included in 1.13.0.
BTW, if interested in playing with pickling further and want an easy way of overriding the builtin FunctionWrapper
to add the pickle methods, in 1.13.0 you can do:
class MyBoundFunctionWrapper(wrapt.BoundFunctionWrapper):
pass
class MyFunctionWrapper(wrapt.FunctionWrapper):
__bound_function_wrapper__ = MyBoundFunctionWrapper
@wrapt.decorator(proxy=MyFunctionWrapper)
def wrapper(wrapped, instance, args, kwargs):
return wrapped(args, kwargs)
Version 1.13.0 is available on PyPi at the moment as release candidates.
Anyway, going to close out this issue at this point.
wrapt
version: 1.12.1I was using
wrapt.decorator
in combination with descriptors. However, I found that__set_name__
was not called on the descriptor when it's being decorated:The output is:
But it is expected that
__set_name__
be called for both descriptors.On the other hand, lookup on the decorated descriptor seems to work:
but it just doesn't get called during class construction.