пятница, 1 мая 2020 г.

ЛМНты Python, 56 - 60

>>> Значение параметра по умолчанию

В следующем определении функции параметр c имеет значение по умолчанию:

>>> def fun(a, b, c=True):
...     return a if c else b
...
>>> fun(1, 2)
1
>>> fun(1, 2, 2<1)
2

Значения параметров по умолчанию вычисляются один раз в момент выполнения предложения def. Из этого следует, что

  • в них можно использовать видимые на этот момент имена и
  • они могут изменяться уже после определения функции, если являются изменяемыми (mutable) объектами.

Например:

>>> def fun(a, b=[]):
...     b.append(a)
...     print(b)
...
>>> fun(1)
[1]
>>> fun(2)
[1, 2]
>>> fun(3)
[1, 2, 3]

Чтобы избежать такого эффекта, не используйте изменяемые объекты в качестве значений параметров по умолчанию.

>>> Цирк?

Используя возможности распаковки и упаковки аргументов при вызове функции, можно написать универсальную функцию для вызова любой функции с произвольными аргументами. Например, функция callfun выводит имя вызываемой функции и значения агрументов, после чего вызывает функцию и возвращает результат:

>>> def callfun(fun, *args, **kwargs):
...     print('Call %s with %s, %s' % (fun, args, kwargs))
...     return fun(*args, **kwargs)
...
>>> callfun(iter, [])
Call <built-in function iter> with ([],), {}
<list_iterator object at 0x0000019E664B04A8>

>>> callfun(range, 10, 16)
Call <class 'range'> with (10, 16), {}
range(10, 16)

>>> callfun(print, 'hello', 'world', end='!\n')
Call <built-in function print> with ('hello', 'world'), {'end': '!\n'}
hello world!

Используя эти же возможности, можно организовать вызовы разных функций с разными аргументами в цикле:

>>> funs = (
...     (iter, ([], ), {}),
...     (range, (10, 16), {}),
...     (print, ('hello', 'world'), {'end': '!\n'})
... )
>>>
>>> for f, a, k in funs:
...     f(*a, **k)
...
<list_iterator object at 0x000001FFA3B80780>
range(10, 16)
hello world!

>>> Господин декоратор

Для всякой функции можно создать функцию-обертку, которая, помимо вызова этой функции, будет делать что-то еще, например, тот же вывод на печать имени и агрументов при вызове:

>>> def wrap(fun):
...     def f(*args, **kwargs):
...         print('Call %s with %s, %s' % (fun, args, kwargs))
...         return fun(*args, **kwargs)
...     return f
...
>>> def hello(name):
...     return 'hello ' + name
...
>>> hello('Андрей')
'hello Андрей'
>>>
>>> wrapped = wrap(hello)
>>> wrapped('Андрей')
Call <function hello at 0x000001BAE285A950> with ('Андрей',), {}
'hello Андрей'

А если функцию-обертку присвоить имени, связанному с обернутой функцией, то произойдет по сути замена изначальной функции на функцию-обертку:

>>> hello = wrap(hello)
>>> hello('Андрей')
Call <function hello at 0x000001BAE285A950> with ('Андрей',), {}
'hello Андрей'

Именно это делают декораторы в Python.

>>> Разоблачение декоратора

Назначение декораторов в Python – добавить в ваши функции дополнительную функциональность или как-то иначе обработать ваши функции. Функция wrap из предыдущего лмнта – это готовый декоратор:

>>> def wrap(fun):
...     def f(*args, **kwargs):
...         print('Call %s with %s, %s' % (fun, args, kwargs))
...         return fun(*args, **kwargs)
...     return f

Для декорирования функций используется такой синтаксис:

>>> @wrap
... def hi(name):
...     return 'hi ' + name
...
>>> hi('Иван')
Call <function hi at 0x000001BAE285AAE8> with ('Иван',), {}
'hi Иван'

Обратите внимание, что после обработки декоратором функция, связанная с именем hi, – это на самом деле функция-обертка f:

>>> hi.__name__
'f'

>>> Маскировка декоратора

У функции-обёртки не только атрибут __name__ отличается от соответствующего атрибута обёрнутой функции, но и другие атрибуты, среди которых, в частности, __module__, __doc__ и __dict__. Для того, чтобы обновить значения этих атрибутов у функции-обёртки и сделать ее более похожей на обёрнутую функцию, модуль functools стандартной библиотеки Python предлагает декоратор wraps:

>>> from functools import wraps
>>>
>>> def deco(fun):
...     @wraps(fun)
...     def f(*args, **kwargs):
...         """Wrap function"""
...         print('Calling', fun)
...         return fun(*args, **kwargs)
...     return f
...
>>> @deco
... def hi(name):
...     """Greet named person"""
...     return 'hi, ' + name
...
>>> hi('Иван')
Calling <function hi at 0x000002987E2402F0>
'hi, Иван'
>>>
>>> hi.__name__
'hi'
>>> hi.__doc__
'Greet named person'

Комментариев нет:

Отправить комментарий