0

I have a decorator, and in that decorator I want to check if the decorated function has an argument with the name query. If it exists I want to detect its position in *args like this: args[1]. I want to perform this in wrapper function. I want to do something like this:

def some_decorator(func):
    @wraps(func)
    def wrapper_func(*args, **kwargs):
        ind = 0
        query = ''
        for arg in args:
            if arg.__name__ == 'query'
                query = 'Found'
                break
            ind +=1
        if query == 'Found':
            query = args[ind]
    return wrapper_func

For clarity, I use it like this:

@some_decorator
def find_all(self, some_arg, query=None):
    pass

The thing is, I use the decorator also for other functions, with different signatures that may or may not have query argument. I hope this clarifies things.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Eziz Durdyyev
  • 1,110
  • 2
  • 16
  • 34
  • 1
    I don't think you can do this. `*args` only contains values. There's no way to know the name. If you need to know the name, use `**kwargs`. – John Gordon Dec 13 '19 at 17:40
  • 1
    @JohnGordon, ...the presence of an accepted/working answer to [getting method parameter names in python](https://stackoverflow.com/questions/218616/getting-method-parameter-names-in-python) appears to contradict that statement. – Charles Duffy Dec 13 '19 at 17:45
  • 1
    @CharlesDuffy I think that question asked something completely different. It asked "If I have a reference to a function, how can I determine the names of its arguments?" i.e. if I have a function `def foo(arg1, arg2)`, how can I, at runtime, know that its arguments are named `arg1` and `arg2`? But my interpretation of _this_ question is that it wants the names of the args in the _caller's_ context, i.e. if I call `foo(x, y)`, how can foo determine that the names of the passed arguments were `x` and `y`? It can't. – John Gordon Dec 13 '19 at 17:53
  • Well, "can't" without doing some truly grotty (and questionably-correct) things like walking up to the caller's stack frame and inspecting its locals to find ones that match, or trying to analyze its AST and hoping it only has a single invocation of the function at hand. But I don't see any basis in the question for the assumption that it's the name in the caller's context, vs the name in the called function's context, that the OP is looking for. – Charles Duffy Dec 13 '19 at 18:11
  • @EzizDurdyyev, ...so, if you want to settle the debate I'm having with JohnGordon conclusively, show not just how you're *using* the decorator, but how you're *invoking* the decorated function. Would you expect `foo.find_all(baz, qux)` to trigger on `qux` because it's being passed in the query position (as I expect, and in which case GCG's answer is responsive), or to be ignored because the caller doesn't give it the name `query` in its context (which is what I understand John to be interpreting you as asking for). – Charles Duffy Dec 13 '19 at 18:18
  • @CharlesDuffy, so qux is actually a query, and it is being passed to query in find_all, so yeah it should be triggered. But to be sure, i need to run the app. I will write the result – Eziz Durdyyev Dec 13 '19 at 18:25
  • @JohnGordon, ...see above. – Charles Duffy Dec 13 '19 at 18:39
  • @CharlesDuffy, it worked ;) btw, after all, i made use of CCebrian's answer too.. for try/expect. – Eziz Durdyyev Dec 13 '19 at 18:45

2 Answers2

2

If I'm understanding you correctly, then I think you can use the inspect module:

import inspect
...
def some_decorator(func):
    def wrapper_func(*args, **kwargs):
        # get function signature using inspect.signature()
        signature = inspect.signature(func)
        # find the subset of arguments that are positional-only
        positional_args = [name for name in signature.parameters 
                           if signature.parameters[name].kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)]
        # This should give us the names of args that *may* be positional. 
        # They should also be in the proper order, since positional-or-keyword args
        # cannot precede positional-only args.
        # Now, we can search for a positional arg named 'query', get an index...
        try:
            query_idx = positional_args.index('query')
        except ValueError:
            query_idx = -1
        # ... and extract the corresponding value passed for that in *args
        if 0 <= query_idx < len(args):
            query = args[query_idx]
        else:
            query = None
        # then do something with that
        ...
    return wrapper_func
Green Cloak Guy
  • 23,793
  • 4
  • 33
  • 53
-1

You could try with array.index()

query = ''

try:
    idx = args.index('query')
except ValueError:
    idx = -1

if idx != -1:
   query = args[idx]
CCebrian
  • 75
  • 8
  • That's looking for an argument with the *value* `query`, not an argument with the *name* `query`. Note how the OP's pseudocode refers to the `.__name__` of an argument, not its value. – Charles Duffy Dec 13 '19 at 18:11