Comments (4)
I would guess that this is (mostly) expected behavior.
You can read this for more details, but basically, as you mentioned the fact that FunctionWrapper
is a descriptor (has a __get__
method) is what is causing the behavior you observe.
The descriptor protocol is used to return methods instead of just bare functions when you do something like
some_var = instance.method
It is true that python 2 and 3 have different behavior when an instance method is retrieved from a class:
python3:
In [1]: class Test:
...: def a(self):
...: pass
...:
In [2]: Test.a
Out[2]: <function __main__.Test.a>
python2:
In [1]: class Test:
...: def a(self):
...: pass
...:
In [2]: Test.a
Out[2]: <function __main__.Test.a>
Where python2 returns an unboundmethod, python3 simply returns the function. Howevever, there could be some reason that one might want to still decorate things when they are accessed on the class. An example of doing something like this would be creating a hybrid property that has one name, but different behavior on the class thant it has for instances of the class.
from wrapt.
You might have cut and paste the wrong Python 2 example.
>>> class Test:
... def a(self):
... pass
...
>>> Test.a
<unbound method Test.a>
As to the answer that was in SO, the Python 2 was wrong. It had:
>>> class X(object):
def g():pass
>>> X.g
<unbound method X.g>
>>> class B:
def __get__(self, *args):
print(args)
>>> X.b = B()
>>> X.b
<__main__.B instance at 0x029C0440>
This will not work equivalent to the Python 3 case as the descriptor protocol only works for new style classes. Thus B
should have derived from object
. When that is done, the result would be the same as Python 3. The FunctionWrapper
in wrapt
ultimately derives from object
to ensure binding always works.
As to comparing Python 2 and Python 3 behaviour, using:
from __future__ import print_function
class X(object):
def im(self): pass
@staticmethod
def sm(): pass
@classmethod
def cm(cls): pass
def dump(m):
print('name', m.__name__)
print('type', type(m))
try:
print('__self__', m.__self__)
except AttributeError:
print('__self__', '<UNDEFINED>')
print()
dump(X.im)
dump(X().im)
dump(X.sm)
dump(X().sm)
dump(X.cm)
dump(X().cm)
One gets.
$ python2.7 bind.py
name im
type <type 'instancemethod'>
__self__ None
name im
type <type 'instancemethod'>
__self__ <__main__.X object at 0x102c500d0>
name sm
type <type 'function'>
__self__ <UNDEFINED>
name sm
type <type 'function'>
__self__ <UNDEFINED>
name cm
type <type 'instancemethod'>
__self__ <class '__main__.X'>
name cm
type <type 'instancemethod'>
__self__ <class '__main__.X'>
$ python3.4 bind.py
name im
type <class 'function'>
__self__ <UNDEFINED>
name im
type <class 'method'>
__self__ <__main__.X object at 0x10063fb00>
name sm
type <class 'function'>
__self__ <UNDEFINED>
name sm
type <class 'function'>
__self__ <UNDEFINED>
name cm
type <class 'method'>
__self__ <class '__main__.X'>
name cm
type <class 'method'>
__self__ <class '__main__.X'>
So Python 3 is indeed different in that it doesn't have an unbound method class.
The wrapt
module, even when the method is being bound to None
when the method is accessed via the class, will bind it and so return BoundFunctionWrapper
. This is necessary so that the desired affect is still achieved when a second binding is performed by an explicit/implicit call of __get__()
. There are some strange cases where this can occur which I can't remember the exact scenario for.
from __future__ import print_function
from wrapt import FunctionWrapper
class A(FunctionWrapper):
A = None
def __init__(self, f):
super(A, self).__init__(f, self.wrapper)
def wrapper(self, wrapped, instance, args, kwargs):
print('wrapper', wrapped, instance, args, kwargs)
return wrapped(*args, **kwargs)
@A
def f(*args, **kwargs):
pass
print(type(f))
A.A = f
m = A.A
print(type(m))
print(m.__self__)
b = m.__get__(A, None)
b()
Technically it should have returned a FunctionWrapper
rather than BoundFunctionWrapper
, but it was already getting quite messy so I didn't want to have a special UnboundFunctionWrapper
type and instead rely on fact that for BoundFunctionWrapper
the __self__
attribute would be None
.
<class '__main__.A'>
<type 'BoundFunctionWrapper'>
None
wrapper <bound method ?.f of <class '__main__.A'>> <class '__main__.A'> () {}
from wrapt.
Related to this, it is worth point out that accessing a method directly via a class to monkey patch it with a wrapper and then replace the original should preferably be avoided due to the fact that you get an unbound method back which can complicate things.
When monkey patching after a class has been defined, rather than saying:
A.m = wrapper(A.m)
it is better to instead use:
wrapt.wrap_object(B, 'm', wrapper)
For example:
from __future__ import print_function
import wrapt
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
print(wrapped, instance, args, kwargs)
return wrapped(*args, **kwargs)
class A(object):
def m(self):
pass
# BAD: Don't do this.
A.m = wrapper(A.m)
a = A()
a.m()
class B(object):
def m(self):
pass
# GOOD: This is the preferred way when monkey patching method of existing class definition.
wrapt.wrap_object(B, 'm', wrapper)
b = B()
b.m()
In simple terms the wrap_object()
function will access the original method via __dict__
using vars()
to ensure that it gets the true original and not a bound version of the function. In practice, it is actually a bit more complicated than that, with the comment in the code for resolve_path()
used by wrapt_object()
having the comment:
# We can't just always use getattr() because in doing
# that on a class it will cause binding to occur which
# will complicate things later and cause some things not
# to work. For the case of a class we therefore access
# the __dict__ directly. To cope though with the wrong
# class being given to us, or a method being moved into
# a base class, we need to walk the class heirarchy to
# work out exactly which __dict__ the method was defined
# in, as accessing it from __dict__ will fail if it was
# not actually on the class given. Fallback to using
# getattr() if we can't find it. If it truly doesn't
# exist, then that will fail.
The requirement for this was learn't the hard way with the more naive approach blowing up users code in some obscure way. :-)
from wrapt.
Closing this as no further followup and believe everything is working as expected.
from wrapt.
Related Issues (20)
- Methods of proxied objects are bound to the original object, not the proxy HOT 4
- PySide6 behaves differently between decorated and non-decorated function HOT 16
- Idea: Sticky/Viral ObjectProxy HOT 3
- ObjectProxy does not play well with GenericAlias, such as isinstance(proxy, Dict) HOT 12
- RFE: is it possible to start making github releases?🤔 HOT 2
- Publish cp312 wheel HOT 6
- Release 1.14.1 Py3.11 wheels HOT 7
- __doc__ property HOT 9
- Request to be able to import both py and c wrappers HOT 8
- Unable to install wrapt 1.14.1 via poetry HOT 2
- Accessing ObjectProxy __dict__ HOT 5
- Update to setup.cfg potentially required HOT 4
- Documentation isn't building? HOT 6
- Best way to associate some data with `ObjectProxy`? HOT 3
- Accessing a class attribute that is a wrapt wrapped function will try and bind the function. HOT 4
- pydevd error when debugging with wrapt HOT 28
- Add type hint annotations for user-facing code HOT 2
- classmethod tests fail with Python 3.13 (Python reverted to pre-3.9 behavior) HOT 2
- 1.16.0: pytest fails
- How to use adapter factory to change signature depending on instance HOT 9
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from wrapt.