57

I am in progress to learn Python. Hopefully someone points me to correct way.
This is what I'd like to do below:

def decorate(function):
    def wrap_function(*args, **kwargs):
        str = 'Hello!'  # This is what I want
        return function(*args, **kwargs)
    return wrap_function

@decorate
def print_message():
    # I'd like to pass 'str' as mentioned above
    # to any functions' argument like below:
    print(str)  # 'str' is same as above

Any idea? Thanks in advance.

abyx
  • 69,862
  • 18
  • 95
  • 117
Japboy
  • 2,699
  • 5
  • 27
  • 28

2 Answers2

84

You can't pass it as its own name, but you can add it to the keywords.

def decorate(function):
    def wrap_function(*args, **kwargs):
        kwargs['str'] = 'Hello!'
        return function(*args, **kwargs)
    return wrap_function

@decorate
def print_message(*args, **kwargs):
    print(kwargs['str'])

Alternatively you can name its own argument:

def decorate(function):
    def wrap_function(*args, **kwargs):
        str = 'Hello!'
        return function(str, *args, **kwargs)
    return wrap_function

@decorate
def print_message(str, *args, **kwargs):
    print(str)

Class method:

def decorate(function):
    def wrap_function(*args, **kwargs):
        str = 'Hello!'
        args.insert(1, str)
        return function(*args, **kwargs)
    return wrap_function

class Printer:
    @decorate
    def print_message(self, str, *args, **kwargs):
        print(str)
Tor Valamo
  • 33,261
  • 11
  • 73
  • 81
  • 4
    Note that the first solution is also achieved by using `functools.partial()` (but from version 2.6 only). The same module also offers the `wraps()` function which can be used with decorators to maintain the name and the doc of the original function. – RedGlyph Dec 27 '09 at 10:24
  • @RedGlyph: why using `functools.partial()` from version 2.6 only? the documentation say nothing about it except that functools module is new in pyhton 2.5. – mg. Dec 27 '09 at 10:49
  • Thanks to Tor Valamo. Alternative solution is clear for me. But how about if the decorated function is an instance method? The instance method's argument should be something like: `def print_message(str, self, *args, **kwargs):` Is this correct? Thanks again. – Japboy Dec 27 '09 at 11:41
  • switch self and str. def print_message(self, str, *args, **kwargs) – Tor Valamo Dec 27 '09 at 11:46
  • in this case `def print_message(self, str, *args, **kwargs)`, `self` gives me `Hello!` from `wrap_function`, and `str` gives me something like class object. – Japboy Dec 27 '09 at 12:14
  • 1
    yes, that's because the args list contains 'self' as the first item. you can fix it with args.insert(1, str) and then return function(*args, **kwargs) – Tor Valamo Dec 27 '09 at 12:17
  • now everything seems clear for me. thanks again Tor Valamo :D happy holidays! – Japboy Dec 27 '09 at 12:23
  • 1
    @Japboy: i don't know if you noticed but `print_message` don't need extra `*args` and `**kwargs` arguments so don't declare so you function if you don't need it. imho a decorator is more powerful the more the decorated function is unaware of it – mg. Dec 27 '09 at 15:00
  • @mg: you're right, 2.6 was for the function above in a table. It should read "2.5". – RedGlyph Dec 28 '09 at 17:38
18

If you want the argument to be "optionally-injected", only in case the function actually takes it, use something like this:

import inspect

def decorate(func):
    def wrap_and_call(*args, **kwargs):
        if 'str' in inspect.getargspec(func).args:
            kwargs['str'] = 'Hello!'
        return func(*args, **kwargs)
    return wrap_and_call

@decorate
def func1(str):
    print "Works! - " + str

@decorate
def func2():
    print "Should work, also."
Alex
  • 278
  • 1
  • 14
Vladius
  • 4,268
  • 2
  • 20
  • 21
  • 11
    Note that getargspec is deprecated. For Python3, use [getfullargspec](https://docs.python.org/3/library/inspect.html#inspect.getfullargspec). – bubbassauro May 29 '18 at 20:48
  • 1
    Alternatively: 'str' in inspect.signature(func).parameters – Bemmu Apr 10 '19 at 05:43