-1

In general, I want to know how to write a simple decorator in python.

However, it might help to have a specific example.

Consider the following function:

def pow(base:float, exp:int):
    """
         +------------------------------------------+
         |                EXAMPLES                  |
         +------------------------------------------+
         | BASE | EXPONENT |       OUTPUT           |
         +------+----------+------------------------+
         |    2 |        5 | 2^5      |          32 |
         |  2.5 |        7 | 2.5^7    | 610.3515625 |
         |   10 |        3 | 10^3     |        1000 |
         |  0.1 |        5 | 0.1^5    |     0.00001 |
         |    7 |        0 | 7^0      |           1 |
         +------+----------+----------+-------------+
    """
    base = float(base)
    # convert `exp` to string to avoid flooring, or truncating, floats
    exp  = int(str(exp))
    if exp > 0:
        return base * pow(base, exp-1)
    else: # exp == 2
        return 1

With the original implementation, the following function call will result in an error:

raw_numbers = [0, 0]
raw_numbers[0] = input("Type a base on the command line and press enter")  
raw_numbers[1] = input("Type an exponent (power) on the command line and press enter")

numbers = [float(num.strip()) for num in raw_numbers]

# As an example, maybe numbers == [4.5, 6]

result = pow(numbers)

print(result)  

Suppose that we want to decorate the pow function so that both of the following two calls are valid:

  1. result = pow(numbers) where numbers is a reference to the list-object [4.5, 6]

  2. result = pow(4.5, 6)


We want to use a decorator named something similair to flatten_args...

@flatten_args
def pow(*args):
   pass

How do we do write such a decorator?

Also, how do we preserve the doc-string when we decorate a callable?

print(pow.__doc__)
Toothpick Anemone
  • 4,290
  • 2
  • 20
  • 42

2 Answers2

2

The simplest way to write a Python decorator is as a function that returns a function.

def flatten_args(func):
    '''
    Decorator to allow multi-argument functions to be called with arguments in list/tuple.
    '''
    # Helper function to implement the decorator behavior.
    def wrapped_func(*args):
        if len(args) == 1:
            return func(*args[0])
        else:
            return func(*args)
    # Make help() behave
    wrapped_func.__name__ = func.__name__
    wrapped_func.__doc__ = func.__doc__
    return wrapped_func
dan04
  • 87,747
  • 23
  • 163
  • 198
-2

The following is an example of a decorator which is half-way pythonic or more:

class print_calling_args:

    def __new__(cls, kallable):
        instance = super().__new__(cls)
        instance = functools.update_wrapper(instance, kallable)
        return instance

    def __init__(self, kallable):
        self._kallable = kallable
        self._file     = sys.stdout

    def __call__(self, *args, **kwargs):
        print("__init__(" + ", ".join(str(x) for x in [self, *args]) + ")", file=self._file)
        return self._kallable(*args, **kwargs)

Below, we decorate a pow function.

We could just write 10**3 for 10 raised to the third power, but this is a nice simple example of a function that we can decorate.

@print_calling_args
def pow(base:float, exp:int):
    """
         +------------------------------------------+
         |                EXAMPLES                  |
         +------------------------------------------+
         | BASE | EXPONENT |       OUTPUT           |
         +------+----------+------------------------+
         |    2 |        5 | 2^5      |          32 |
         |  2.5 |        7 | 2.5^7    | 610.3515625 |
         |   10 |        3 | 10^3     |        1000 |
         |  0.1 |        5 | 0.1^5    |     0.00001 |
         |    7 |        0 | 7^0      |           1 |
         +------+----------+----------+-------------+
    """
    base = float(base)
    # convert `exp` to string to avoid flooring, or truncating, floats
    exp  = int(str(exp))
    if exp > 0:
        return base * pow(base, exp-1)
    else: # exp == 2
        return 1

Below, we see the decorated pow function being called or invoked with a few simple inputs:

result1 = pow(2, 5)
result2 = pow([8.1, 0])
print("pow(2, 5)"    , " == ", result1)
print("pow([8.1, 0])", " == ", result2)

Note that the doc string is still transparently visible through the wrapper:

print(pow.__doc__) 
Toothpick Anemone
  • 4,290
  • 2
  • 20
  • 42