10. Декоратори
Появяваха се от време на време...
| @staticmethod
| def register(name)
| Person.people.append(name)
| print(len(Person.people), "people are registered now")
| [...]
| @classmethod
| def greet(cls, someone)
| print(someone, "was greeted from", cls)
Но преди това...
-
Какво различава функцията от всеки друг обект?
- __call__
-
Какви типове обекти може да връща една функция?
- всякакви
Хм...защо тогава една функция да не връща друга?
Айде малко математика
| def fibonacci(x):
| if x in [0,1]:
| return 1
| return fibonacci(x-1) + fibonacci(x-2)
Да де, ама е рекурсивна
=> при аргумент x > 40 е изключително бавна
Без значение колко пъти я извикваме с един и същи аргумент
Как да минимизираме излишните сметки?
- Голям dict() с първите стотина елемента е overkill
Повторението е баща на затъпяването
Защо просто не се опитаме да си записваме вече пресмятаните стойности?
| if x not in memory:
| memory[x] = fibonacci(x)
| print(memory[x])
- Много яко, нали?
- Ама какво се случва ако това го искаме на 10 места из кода ни?
- ... или на сто?
Защо тогава една функция да не връща друга?
Декоратори наричаме функциите f от вида
- f(функция) -> функция
Резултатът е нова функция, разширяваща функционалността на първата.
memoize()
def memoize(func)
memory = {}
def memoized(*args)
if args in memory:
return memory[args]
result = func(*args)
memory[args] = result
return result
return memoized
Под дефиницията на fibonacci
fibonacci = memoize(fibonacci)
Лошият и грозният
И все пак
| def fibonacci(x):
| if x in [0,1]:
| return 1
| return fibonacci(x-1) + fibonacci(x-2)
| fibonacci = memoize(fibonacci)
…е грозно. А и има шанс да не видите декоратора, понеже е отдолу.
Клинт Ийстууд
@memoized
def fibonacci(x)
if x in [0,1]:
return 1
return fibonacci(x-1) + fibonacci(x-2)
Друг пример за декоратор
def notifyme(f)
def logged(*args, **kwargs):
print(f.__name__, ' called with', args, 'and', kwargs)
return f(*args, **kwargs)
return logged
@notifyme
def square(x)
return x * x
res = square(25)
#square was called with (25,) and {}.
Няколко декоратора на една функция
class Mityo
@staticmethod
@notifyme
def work(): pass
Mityo.work()
work was called with () and {}
Първо се извикват най-вътрешните декоратори.
Прави същото като
def work(): pass
work = notifyme(work)
work = staticmethod(work)
или
work = staticmethod(notifyme(work))
- Какво правим, ако искаме да му подаваме аргументи?
Динамични декоратори
Декоратор, който приема параметри.
@memoize('/tmp/fibs')
def fibonacci(n)
[...]
е равно на
def fibonacci(n)
[...]
f = memoize('/tmp/fibs')(fibonacci)
Да не се бърка с
fibonacci = memoize('/tmp/fibs', fibonacci)
На лов за патици
Всъщност, защо да не си направим следния декоратор
@accepts(int, int)
def add(a, b)
return a+b
Превод на недекораторски
add = accepts(int, int)(add)
код > думи
def accepts(*types)
def accepter(f):
def decorated(*args)
for (i, (arg, t)) in enumerate(zip(args, types)):
if not isinstance(arg, t)
raise TypeError("""Argument #{0} of '{1}' should \
have been of type {2}".format(i,
f.__name__,
t.__name__))
#TODO: more complex checks
return f(*args)
return decorated
return accepter
За патиците с любов
duck typing е много важна част от философията на Python. @accepts е забавен пример и дори има някои употреби, но избягвайте да го ползвате масово. В повечето случаи губите, а не печелите.
Полезни декоратори
- classmethod — прави метода класов (приема клас, а не обект като първи аргумент)
- staticmethod — прави метода статичен
- property
@property
class Battery(object)
def __init__(self):
self._voltage = 100000
@property
def voltage(self)
"""Get the current voltage."""
return self._voltage
Това превръща voltage в getter към атрибут само за четене със същото име
@property си има и setter
@voltage.setter
def voltage(self, value)
self._voltage = value
- В този ред на мисли, си има и deleter
Meyer's substitution principle
Атрибутите на един обект трябва да бъдат достъпвани през хомогенна нотация, която не издава дали те се изчисляват или са записани.
Още въпроси?
- Пишете ни на fmi@py-bg.net
- Страница на курса: http://fmi.py-bg.net/
- Форуми на курса: http://fmi.py-bg.net/topics
- Курсът в Twitter: http://twitter.com/pyfmi
- Курсът във Facebook: http://www.facebook.com/group.php?gid=104970619536589