Giter VIP home page Giter VIP logo

Comments (4)

colonelpanic8 avatar colonelpanic8 commented on June 4, 2024

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.

GrahamDumpleton avatar GrahamDumpleton commented on June 4, 2024

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.

GrahamDumpleton avatar GrahamDumpleton commented on June 4, 2024

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.

GrahamDumpleton avatar GrahamDumpleton commented on June 4, 2024

Closing this as no further followup and believe everything is working as expected.

from wrapt.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.