Giter VIP home page Giter VIP logo

Comments (4)

GrahamDumpleton avatar GrahamDumpleton commented on May 25, 2024

Not entirely sure what you are getting at and what the problem you are having is.

A few things to note about how wrapt is different and why.

The first thing is that the decorator wrapper function takes the arguments of the call as 'args' and 'kwargs'. This is instead of 'args' and '*kwargs', with possible variable substitution. In other words, it does not use:

@wrapt.decorator
def dec(wrapped, instance, *args, **kwargs):
    print instance, args, kwargs
    return wrapped(*args, **kwargs)

nor allow as a result:

@wrapt.decorator
def dec(wrapped, instance, bar, *args, **kwargs):
    print instance, bar, args, kwargs
    return wrapped(bar, *args, **kwargs)

@dec
def foo(bar, baz):
    pass

This was a conscious decision because allowing this always raises the possibility of a decorated function using a argument name that could conflict with the arguments names used in the decorator wrapper function of 'wrapped', 'instance', 'args' and 'kwargs', as well as 'self' if the decorator wrapper function was actually a method of a class.

Especially where a decorator can be used in many contexts such as a timing decorator, to avoid problems, the person implementing the decorator would have to use magic argument names with special prefixes which they are pretty sure will not clash with the argument names of any function the decorator is applied to. Needing to adopt such a convention to me looks ugly and is a bit of a pain.

A further reason was that I found that early binding of the arguments could result in misleading error details about location. That is, the error would arise in applying the arguments to the decorator wrapper and not at the point where being applied to the wrapped function. In applying it against the decorator wrapper function, that the decorator wrapper had additional arguments would also confuse things because the error message would talk about a different number of arguments being passed to the function than what a user was expecting.

So that is some of the reasons why no early binding of arguments is done.

There are also other cases where late binding is good as well when one gets into using the wrappers for monkey patching and you need to to write an instrumentation module which works on multiple versions of a target package which has changed argument names and what arguments are accepted between versions. Having the unbound arguments passed as args and kwargs allows detection of such cases through trial binding against multiple prototypes within the decorator wrapper function if necessary.

In recent Python 3 version there is also inspect.getcallargs() which can help with such introspection.

>>> import inspect
>>> def foo(a, b, *args, **kwargs):
...   pass
…
>>> inspect.getcallargs(foo, *(), **{'a':1, 'b':2})
{'a': 1, 'args': (), 'b': 2, 'kwargs': {}}

I am aware of how the decorator module works, but I have various issues with that. This includes that it doesn't solve the universal decorator issue, that the inspect.getsource() doesn't work from memory, plus the need to create a new compiled function with the correct signature rules it out for certain uses cases in doing monkey patching and instrumentation where the wrapper may need to be create dynamically on each call.

The runtime cost of compiling the new function on the fly is quite excessive in this latter case and was something that was going to note in my next blog post if I ever get to it where I was going to compare decorator module performance to wrapt module.

I don't know if this answers you question or solves your problem or whether you were just making an observation. If you can perhaps explain how the difference causes an issue then I can perhaps explain how you are meant to deal with it.

The documentation at:

already explains the need to explicitly bind the arguments in the decorator wrapper function if you need to use or change the arguments being passed in.

from wrapt.

simonpercivall avatar simonpercivall commented on May 25, 2024

Thanks for the reply. I guess it would have been easier to refer to the documentation that already mentions this issue, I just didn't read it carefully enough to see it being mentioned.

Specifically, it's this section:

You should not simply attempt to extract positional arguments from args directly because this will fail if those positional arguments were actually passed as keyword arguments, and so were passed in kwargs with args being an empty tuple.

There are two issues, I'd say:

  • it's ugly to have to create an inner wrapper just to be able to get at the arguments. Were you able to rely on args being args and kwargs being kwargs it would have been much easier.
  • in one specific usage, one decorator I wrote accepts a key_func to create a cache key from the arguments passed in to the underlying function. That key function needs to match the exact argument names of the underlying function rather than being able to rely on argument order. This makes it harder/impossible to create such a key function that works with multiple underlying functions.

from wrapt.

GrahamDumpleton avatar GrahamDumpleton commented on May 25, 2024

Going to leave this issue open for now as reminder. I suspect what will help is to create a version of inspect.getcallargs() from Python 2.7 that works for Python 2.6 and expose it via a function in wrapt API.

Thus instead of doing something like:

@wrapt.decorator
def my_decorator(wrapped, instance, args, kwargs):
    def _arguments(arg1, arg2, *args, **kwargs):
        return (arg1, arg2)

    arg1, arg2 = _arguments(*args, **kwargs)

    # Do something with arg1 and arg2 but still pass through
    # the original arguments to the wrapped function.

    return wrapped(*args, **kwargs)

Could have something like:

@wrapt.decorator
def my_decorator(wrapped, instance, args, kwargs):
    params = wrapt.getcallargs(wrapped, *args, **kwargs)
    key_func = params['key_func']
    …
    return wrapped(*args, **kwargs)

The version of getcallargs() in Python is pure Python code, but I could always look at an optimised C version if made sense and sped things up.

from wrapt.

GrahamDumpleton avatar GrahamDumpleton commented on May 25, 2024

Have added wrapt.getcallargs() for version 1.7.0. For Python 2.7+ this is actually the same as inspect.getcallargs(), but for Python 2.6, it returns a local copy.

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.