GrahamDumpleton / wrapt

A Python module for decorators, wrappers and monkey patching.
BSD 2-Clause "Simplified" License
2.06k stars 231 forks source link

I'm having trouble changing the signature of unbound methods. #148

Closed benfogelson closed 4 years ago

benfogelson commented 4 years ago

Below is a MWE of my attempt to change the signature of MyClass.func from (self, a, b, c) to (self, x, y, z). As you can see from the outputs, the change_sig decorator works on functions and also works on the bound method MyClass().func, but fails on the unbound method. Is there some sort of magic going on behind the scenes when I assign a callable attribute to a class that keeps this from working?

MWE:

import wrapt
import inspect

def change_sig(func):
    adapter = '(self, x, y, z)'
    @wrapt.decorator(adapter=adapter)
    def wrapper(wrapped, instance, args, kwargs):
        pass
    wrapped_func = wrapper(func)
    return wrapped_func

@change_sig
def func(self, a, b, c):
    pass

class MyClass:
    @change_sig
    def func(self, a, b, c):
        pass

print('inspect.signature(func):', inspect.signature(func))
print('inspect.signature(MyClass.func):', inspect.signature(MyClass.func))
print('inspect.signature(MyClass().func):', inspect.signature(MyClass().func))

print('inspect.signature(change_sig(MyClass.func)):', inspect.signature(change_sig(MyClass.func)))
MyClass.wrapped_func = change_sig(MyClass.func)
print('inspect.signature(MyClass.wrapped_func):', inspect.signature(MyClass.wrapped_func))

Outputs:

inspect.signature(func): (self, x, y, z)
inspect.signature(MyClass.func): (self, a, b, c)
inspect.signature(MyClass().func): (x, y, z)
inspect.signature(change_sig(MyClass.func)): (self, x, y, z)
inspect.signature(MyClass.wrapped_func): (self, a, b, c)

I also posted about this on stackoverflow (https://stackoverflow.com/questions/60199457/how-can-i-use-wrapt-decorator-adapters-to-change-the-signature-of-unbound-method) but figured it would be good to ask here as well.

GrahamDumpleton commented 4 years ago

Possibly solved by wrapt adding an appropriate __signature__ property.

This didn't exist when I first wrote wrapt for Python 2.X. Python 3.X wasn't really usable back then from what I recollect.

So I didn't realise they had added this attribute. Up till now had been relying on how introspection relied upon working down chain of __wrapped__ properties.

I can't remember if I ever validated if signature worked when changing it on unbound method.

I'll look into it some more and add some unit tests so can work out a change.

GrahamDumpleton commented 4 years ago

What version of Python are you using?

GrahamDumpleton commented 4 years ago

Can you try:

pip https://github.com/GrahamDumpleton/wrapt/archive/develop.zip

and test with that.

benfogelson commented 4 years ago

Works great! Thanks for coming up with a fix so quickly. (Python 3.6.10 to answer your earlier question)

GrahamDumpleton commented 4 years ago

This has been released now in 1.12.0.