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

More Related Content

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

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