micheles/decorator

__code__ attribute no longer copied/passed through in decorator>=5

bergtholdt opened this issue · 3 comments

Consider this simple example:

import decorator

def decor(func, *args, **kwargs):
    return func(*args, **kwargs)

@decorator.decorator(decor)
def test(a, b, c=1, d=2):
    pass

With decorator 4.4.2 I get:

test.__code__.co_argcount
4
>>> test.__code__.co_varnames
('a', 'b', 'c', 'd')

With decorator 5.0.9 I get:

>>> test.__code__.co_argcount
0
>>> test.__code__.co_varnames
('args', 'kw')

This breaks all my method decorators that I use with enthought traits library since they rely on func.code.co_argcount for trait change notification.

Is there a workaround I can do to my wrappers, without changing anything in the enthought traits library (like introducing inspect module there)?

Version 5 of the decorator module relies on the Signature object in the standard library which is a leaking abstraction and fails if one looks at low level objects like the __code__ objects. For the moment I would suggest you to stick with version 4. Otherwise you should define your decorators with the FunctionMaker class that still uses exec and would pass the __code__ attribute, but it requires some work.

I am adding the following function in version 5.1:

def decoratorx(caller):
    """
    A version of "decorator" implemented via "exec" and not via the
    Signature object. Use this if you are want to preserve the `.__code__`
    object properties (https://github.com/micheles/decorator/issues/129).
    """
    def dec(func):
        return FunctionMaker.create(
            func,
            "return _call_(_func_, %(shortsignature)s)",
            dict(_call_=caller, _func_=func),
            __wrapped__=func, __qualname__=func.__qualname__)
    return dec

Then things will work as you want if you define

@decorator.decoratorx(decor)
def test(a, b, c=1, d=2):
    pass

However, rather than using decoratorx, you should fix your introspection routines to use inspect.Signature without fiddling with the __code__ object.

For the record, decoratorx is very helpful also with PySide6 signals. See GrahamDumpleton/wrapt#243 and/or https://stackoverflow.com/q/76596620/880783 if interested.