Декораторы в Python и их практическое использование

5,331 views
5,148 views

Published on

Доклад был дан на конференции Exception #03 в январе 2007.

Published in: Technology
2 Comments
5 Likes
Statistics
Notes
  • хорошая и понятная презентация
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Доклад был дан на конференции Exception #03 в январе 2007.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
5,331
On SlideShare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
0
Comments
2
Likes
5
Embeds 0
No embeds

No notes for slide

Декораторы в Python и их практическое использование

  1. 1. Декораторы Python
  2. 2. Про доклад <ul><li>Материала много </li></ul><ul><li>Возможно слишком много </li></ul><ul><li>И не только про декораторы </li></ul><ul><li>Теория вводится вперемешку с практикой в надежде что так легче понять </li></ul><ul><li>Кода тоже много </li></ul><ul><li>Задавайте вопросы по слайдам пока не поздно </li></ul>
  3. 3. Забегая вперед <ul><li>@threaded # <- декоратор def func(x): ... </li></ul><ul><li>@event(‘close’) # <- тоже декоратор def on_close(self, evt): ... </li></ul><ul><li>@template(‘article’) # <- очередной декоратор @default_slots(title=‘Untitled’, author=‘anon’) # <- и снова декоратор def article(self, **kw): return {…} </li></ul>
  4. 4. Функция как объект и объект как функция <ul><li>>>> def f(x): return str(x) </li></ul><ul><li>... </li></ul><ul><li>>>> f </li></ul><ul><li><function f at 0x009ED5B0> </li></ul><ul><li>>>> isinstance(f, object) </li></ul><ul><li>True </li></ul><ul><li>>>> callable(f) </li></ul><ul><li>True </li></ul><ul><li>>>> f(123) </li></ul><ul><li>'123' </li></ul><ul><li>>>> class F(object): </li></ul><ul><li>... def __call__(self, x): </li></ul><ul><li>... return str(x) </li></ul><ul><li>... </li></ul><ul><li>>>> f = F() </li></ul><ul><li>>>> f </li></ul><ul><li><__main__.F object at 0x009E7B50> </li></ul><ul><li>>>> isinstance(f, object) </li></ul><ul><li>True </li></ul><ul><li>>>> callable(f) </li></ul><ul><li>True </li></ul><ul><li>>>> f(123) </li></ul><ul><li>'123' </li></ul><ul><li>>>> callable(object()) </li></ul><ul><li>False </li></ul>
  5. 5. Синтаксис и семантика декоратора <ul><li>Вместо def func(): </li></ul><ul><li>… func = wrap(func) </li></ul><ul><li>мы можем писать </li></ul><ul><li>@wrap def func(): </li></ul><ul><li>… </li></ul>
  6. 6. Зачем нужны декораторы? <ul><li>Декораторы позволяют сделать код более читабельным </li></ul><ul><li>Требования к каждой отдельной функции (или методу) упрощаются </li></ul><ul><ul><li>Самый сложный или скучный код можно отдалить от основного </li></ul></ul><ul><ul><li>Можно меньше повторяться </li></ul></ul><ul><li>Это еще один способ разбиения кода </li></ul><ul><li>Благодаря декораторам мы можем сделать многие абстракции более практичными </li></ul>
  7. 7. Что может делать декоратор? <ul><li>Декоратор &quot;оборачивает&quot; функцию </li></ul><ul><ul><li>может исполнять код до вызова </li></ul></ul><ul><ul><ul><li>изменять параметры </li></ul></ul></ul><ul><ul><ul><li>проверять типы параметров </li></ul></ul></ul><ul><ul><ul><li>печатать отладочную информацию </li></ul></ul></ul><ul><ul><li>может исполнять код после вызова </li></ul></ul><ul><ul><ul><li>проверять результат </li></ul></ul></ul><ul><ul><ul><li>преобразовывать его </li></ul></ul></ul><ul><ul><li>повторять вызов в каких-то случаях </li></ul></ul><ul><ul><ul><li>ошибка сети? – попробуй еще пару раз </li></ul></ul></ul><ul><ul><li>перемежать выполнение своей логикой (генераторы) </li></ul></ul><ul><ul><li>напрашивающийся пример: транзакции </li></ul></ul>
  8. 8. Например <ul><li>def transact( method ): </li></ul><ul><li>def transacted_call( self, * args, **kw ): </li></ul><ul><li>try: </li></ul><ul><li>transaction = self.start_transaction() </li></ul><ul><li>r = method( self, *args, **kw) </li></ul><ul><li>transaction.commit() </li></ul><ul><li>return r </li></ul><ul><li>except: </li></ul><ul><li>transaction.rollback() </li></ul><ul><li>raise </li></ul><ul><li>return transacted_call </li></ul><ul><li>class C: </li></ul><ul><li>@transact </li></ul><ul><li>def update(self, ...): </li></ul><ul><li>... </li></ul>
  9. 9. Что за звёздочки? <ul><li>def transacted_call( self, * args, ** kw): </li></ul><ul><li>r = method( self, * args, ** kw) </li></ul><ul><li>>>> def lets_see(*args, **kw): </li></ul><ul><li>... print 'args =', args, 'kw =', kw </li></ul><ul><li>>>> lets_see(1, 2, 3) </li></ul><ul><li>args = (1, 2, 3) kw = {} </li></ul><ul><li>>>> lets_see(a=1, b=2) </li></ul><ul><li>args = () kw = {'a': 1, 'b': 2} </li></ul><ul><li>>>> lets_see(1, b=2) </li></ul><ul><li>args = (1,) kw = {'b': 2} </li></ul>
  10. 10. Как это будет работать? <ul><li>def transact(method): </li></ul><ul><li>def transacted_call( self, * ): </li></ul><ul><li>try: </li></ul><ul><li>transaction = self.sta.. </li></ul><ul><li>r = method( self, * ) </li></ul><ul><li>transaction.commit() </li></ul><ul><li>return r </li></ul><ul><li>except: </li></ul><ul><li>transaction.rollback() </li></ul><ul><li>raise </li></ul><ul><li>return transacted_call </li></ul><ul><li>class C: </li></ul><ul><li>@transact </li></ul><ul><li>def update(self, what): </li></ul><ul><li>bla-bla-bla </li></ul><ul><li>return X </li></ul><ul><li>class C: </li></ul><ul><li>def update(self, what): </li></ul><ul><li>try: </li></ul><ul><li>transaction = self.sta.. </li></ul><ul><li>bla-bla-bla </li></ul><ul><li>transaction.commit() </li></ul><ul><li>return X </li></ul><ul><li>except: </li></ul><ul><li>transaction.rollback() </li></ul><ul><li>raise </li></ul>
  11. 11. И где же читабельность? <ul><li>class C: </li></ul><ul><li>@transact </li></ul><ul><li>def update(self, what): </li></ul><ul><li>bla-bla-bla </li></ul><ul><li>return X </li></ul><ul><li>@transact </li></ul><ul><li>def insert(self, what): </li></ul><ul><li>foo-foo-foo </li></ul><ul><li>return Y </li></ul><ul><li>class C: </li></ul><ul><li>def update(self, what): </li></ul><ul><li>try: </li></ul><ul><li>transaction = self.sta.. </li></ul><ul><li>bla-bla-bla </li></ul><ul><li>transaction.commit() </li></ul><ul><li>return X </li></ul><ul><li>except: </li></ul><ul><li>transaction.rollback() </li></ul><ul><li>raise </li></ul><ul><li>def insert(self, what): </li></ul><ul><li>try: </li></ul><ul><li>transaction = self.sta.. </li></ul><ul><li>foo-foo-foo </li></ul><ul><li>transaction.commit() </li></ul><ul><li>return Y </li></ul><ul><li>except: </li></ul><ul><li>transaction.rollback() </li></ul><ul><li>raise </li></ul>
  12. 12. Еще один пример <ul><li>Сценарий: </li></ul><ul><ul><li>Есть класс исходный код которого мы не можем менять непосредственно потому что: </li></ul></ul><ul><ul><ul><li>у нас нет его исходного кода </li></ul></ul></ul><ul><ul><ul><li>он из чужой библиотеки и будет меняться </li></ul></ul></ul><ul><ul><li>У этого класса есть множество наследников </li></ul></ul><ul><ul><li>Мы хотим расширить этот класс (добавить или переопределить методы) и хотим чтобы изменения были унаследованы </li></ul></ul>
  13. 13. Инъекция методов <ul><li>class A: </li></ul><ul><li>def m1(self): pass </li></ul><ul><li>def m2(self): pass </li></ul><ul><li>class B(A): pass </li></ul><ul><li>def m1_ext(self): </li></ul><ul><li>#A.m1(self) !error </li></ul><ul><li>return True </li></ul><ul><li>A.m1 = m1_ext </li></ul><ul><li>assert B().m1() </li></ul><ul><li>@inject_into(A) </li></ul><ul><li>def m2(self): </li></ul><ul><li>self._A_m2() </li></ul><ul><li>return True </li></ul><ul><li>assert B().m2() </li></ul>
  14. 14. Как это было сделано <ul><li>То что идет после @ вычисляется в момент импорта модуля </li></ul><ul><li>Вычисленное должно быть функцией </li></ul><ul><li>Эта функция должна принимать один параметр – функцию </li></ul><ul><li>Возвращать также надо функцию </li></ul><ul><li>То есть @inject_into(A) означает что вызов inject_into(A) происходит только однажды и возвращает собственно декоратор </li></ul>
  15. 15. Собственно код <ul><li>def inject_into(cls): </li></ul><ul><li>def do_inject(method): </li></ul><ul><li>name = method.__name__ </li></ul><ul><li>saved_name = '_%s_%s' % (cls.__name__, name) </li></ul><ul><li>setattr(cls, saved_name, getattr(cls, name)) </li></ul><ul><li>setattr(cls, name, method) </li></ul><ul><li>return do_inject </li></ul><ul><li>в нашем примере сначала исполнялся inject_into( A ) </li></ul><ul><li>мы получали в результате do_inject </li></ul><ul><li>в определенном смысле на дальнейшее можно смотреть вот так: </li></ul><ul><li>@ do_inject </li></ul><ul><li>def m2(self): </li></ul><ul><li>что в свою очередь эквивалентно </li></ul><ul><li>do_inject( m2) </li></ul>
  16. 16. И всё таки как же это работает? <ul><li>В выражении do_inject( m2) нигде не упоминается класс в который мы делаем инъекцию. </li></ul><ul><li>Это возможно за счет того что функция do_inject определена внутри другой функции и использует параметр этой внешней функции который в нашем примере cls=A </li></ul><ul><li>Напоминаю код: </li></ul><ul><ul><li>def inject_into( cls ): </li></ul></ul><ul><ul><li>def do_inject(method): </li></ul></ul><ul><ul><li>name = method.__name__ </li></ul></ul><ul><ul><li>saved_name = '_%s_%s' % ( cls.__name__ , name) </li></ul></ul><ul><ul><li>setattr( cls , saved_name, getattr( cls , name)) </li></ul></ul><ul><ul><li>setattr( cls , name, method) </li></ul></ul><ul><ul><li>return do_inject </li></ul></ul>
  17. 17. Как об этом удобней думать <ul><li>Можно смотреть на определение функций как на исполнение последовательности команд интерпретатору ( так оно и есть) </li></ul><ul><li>таким образом сколько раз мы вызываем inject_into столько разных do_inject мы получаем в итоге, ведь код определения этой внутренней функции исполняется каждый раз заново. </li></ul>
  18. 18. Как это можно было сделать иначе <ul><li>class Injector: </li></ul><ul><li>def __init__(self, cls): </li></ul><ul><li>self.target_cls = cls </li></ul><ul><li>def __call__(self, method): </li></ul><ul><li>name = method.__name__ </li></ul><ul><li>cls = self.target_cls </li></ul><ul><li>saved_name = '_%s_%s' % (cls.__name__, name) </li></ul><ul><li>setattr(cls, saved_name, getattr(cls, name)) </li></ul><ul><li>setattr(cls, name, method) </li></ul><ul><li>@Injector(A) # тут декоратором оказывается экземпляр Injector </li></ul><ul><li>def m2(self): </li></ul><ul><li>... </li></ul>
  19. 19. Примеры применения <ul><li>@threaded, @lock(obj) </li></ul><ul><li>@exposed, @xml_rpc, @json </li></ul><ul><li>@cache_results, @memoize </li></ul><ul><li>@property </li></ul><ul><li>@staticmethod, @classmethod </li></ul><ul><li>@log_calls, @linetrace </li></ul><ul><li>@profile </li></ul><ul><li>@accepts, @returns </li></ul><ul><li>@meta(author=..) </li></ul>
  20. 20. Примеры применения в веб-приложениях <ul><li>@template(template_name) </li></ul><ul><li>@default_values(key=value, ..) </li></ul><ul><li>@require(group='admin') </li></ul><ul><li>@mimetype('text/plain') </li></ul><ul><li>@compact_spaces </li></ul><ul><li>@email_failures </li></ul>
  21. 21. Пример применения в настольных приложениях <ul><li>class FeedbackWin(Dialog): </li></ul><ul><li>send = Button(&quot;Send&quot;) </li></ul><ul><li>cancel = Button(&quot;Cancel&quot;) </li></ul><ul><li>@event('button', 'send', send=True) </li></ul><ul><li>@event('button', 'cancel', send=False) </li></ul><ul><li>def on_ done (self, evt, send): </li></ul><ul><li>if send: </li></ul><ul><li>... </li></ul><ul><li>self.close() </li></ul>
  22. 22. Совсем уж необычный пример <ul><li>@ a sync </li></ul><ul><li>def calculate(win): </li></ul><ul><li>win.status.set('Calculating...') </li></ul><ul><li>yield A SYNC </li></ul><ul><li>## ... страшные вычисления ... </li></ul><ul><li>yield SYNC </li></ul><ul><li>win.status.set('Writing results...') </li></ul><ul><li>yield A SYNC </li></ul><ul><li>## ... долго пишем много результатов ... </li></ul><ul><li>yield SYNC </li></ul><ul><li>win.status.set('Done') </li></ul><ul><li>Нечто отдаленно схожее есть в twisted , называется twisted flow </li></ul>
  23. 23. Когда стоить подумать о декораторах <ul><li>Всегда :) </li></ul><ul><li>Когда набору функций надо придать какие-то общие свойства </li></ul><ul><li>Когда вы замечаете что код выполняющий какую-то работу соседствует с кодом который занимается связкой (события, инъекции и т.п.) </li></ul><ul><li>Когда хочется разделить что-то на &quot;орех&quot; и &quot;скорлупу&quot; </li></ul>
  24. 24. Спасибо за внимание <ul><li>Оффтоп: если у вас есть идеи, время или желание сделать что-то интересное, а лучше всё вместе, подойдите после доклада. </li></ul>

×