The functions as well!
>>> class Panda:
... pass
...
>>> def foo():
... return 'bar'
...
>>> panda = Panda()
>>> type(panda)
<class '__main__.Panda'>
>>> type(Panda)
<class 'type'>
>>> type(foo)
<class 'function'>
>>> type(foo())
<class 'str'>
>>> type(dir)
<class 'builtin_function_or_method'>
How?
>>> class Panda:
... def __call__(self):
... print('What am I!?')
...
>>> panda = Panda()
>>> panda()
What am I!?
>>> def take_print():
... print('I am wrapping the print')
... return print
...
>>> my_print = take_print()
I am wrapping the print
>>> type(my_print)
<class 'builtin_function_or_method'>
>>> my_print('Marto, WTF?!')
Marto, WTF?!
global_x = 123
def foo():
global_x = 42
print('In foo: ', global_x)
print('Before foo: ', global_x)
foo()
print('After foo: ', global_x)
╰─Ѫ py demo.py
Before foo: 123
In foo: 42
After foo: 123
def foo(x):
print('Before bar, x is: ', x)
def bar():
x = 100
print('x in bar: ', x)
bar()
print('After bar, x is: ')
foo(42)
╰─Ѫ py demo.py
Before bar, x is: 42
x in bar: 100
After bar, x is: 42
A function that uses non-local variables is called closure
def make_multiplier(n):
def multiply(x):
print(x * n)
return multiply
times_three = make_multiplier(3)
times_five = make_multiplier(5)
times_three(10)
times_five(10)
╰─Ѫ py demo.py
30
50
def make_multiplier(n):
def multiply(x):
print(x * n)
return multiply
print(make_multiplier.__closure__)
times_three = make_multiplier(3)
times_three(10)
print(times_three.__closure__)
print(times_three.__closure__[0].cell_contents)
del make_multiplier
print('make_multiplier is deleted')
times_three(10)
╰─Ѫ py demo.py
None
30
(<cell at 0x7f25fcf47210: int object at 0x55980abcca40>,)
3
make_multiplier is deleted
30
Let's say you offer a software where peaople can pay their bills
def pay_phone(user, money):
save_to_db('phone', user, money)
def pay_net(user, money):
save_to_db('internet', user, money)
def pay_tv(user, money):
save_to_db('tv', user, money)
pay_phone('Marto', 10)
pay_net('Marto', 30)
pay_tv('Marto', 20)
def pay_phone(user, money, promo=100):
save_to_db('phone', user, money * (promo / 100))
def pay_net(user, money, promo=100):
save_to_db('internet', user, money * (promo / 100))
def pay_tv(user, money, promo=100):
save_to_db('tv', user, money * (promo / 100))
PROMO = 50
pay_phone('Marto', 10, PROMO)
pay_net('Marto', 30, PROMO)
pay_tv('Marto', 20, PROMO)
def add_discount(func, promo=100):
def discounted(user, money):
return func(user, money * (promo / 100))
return discounted
def pay_phone(user, money):
save_to_db('phone', user, money)
def pay_net(user, money):
save_to_db('internet', user, money)
def pay_tv(user, money):
save_to_db('tv', user, money)
PROMO = 50
pay_phone = add_discount(pay_phone, PROMO)
pay_net = add_discount(pay_net, PROMO)
pay_phone('Marto', 10)
pay_net('Marto', 30)
pay_tv('Marto', 20)
def add_discount(func, promo=100):
def discounted(user, money):
return func(user, money * (50 / 100)) # hardcoded!
return discounted
@add_discount
def pay_phone(user, money):
save_to_db('phone', user)
@add_discount
def pay_net(user, money):
save_to_db('internet', user)
def pay_tv(user, money):
save_to_db('tv', user)
pay_phone('Marto', 10)
pay_net('Marto', 30)
pay_tv('Marto', 20)
Essentially, decorators work as wrappers, modifying the behavior of the code before and after a target function execution, without the need to modify the function itself, augmenting the original functionality, thus decorating it.
def add_discount(promo=100):
def inner(func):
def discounted(user, money):
return func(user, money * (promo / 100))
return discounted
return inner
@add_discount(promo=50)
def pay_phone(user, money):
print('money: ', money)
save_to_db('phone', user)
@add_discount(promo=20)
def pay_net(user, money):
save_to_db('internet', user)
def pay_tv(user, money):
save_to_db('tv', user)
pay_phone('Marto', 10)
pay_net('Marto', 30)
pay_tv('Marto', 20)
@send_sms('Phone paid.')
@add_discount(promo=50)
def pay_phone(user, money):
print('money: ', money)
save_to_db('phone', user)
@send_email('Internet paid.')
@add_discount(promo=20)
def pay_net(user, money):
save_to_db('internet', user)
@send_email('TV paid.')
def pay_tv(user, money):
save_to_db('tv', user)
pay_phone('Marto', 10)
pay_net('Marto', 30)
pay_tv('Marto', 20)
Which decorator is going to be called first here?