Самоучитель PythonВыпуск 2Дмитрий Мусинмая 07, 2017
Скачать 0.74 Mb.
|
118 Самоучитель Python, Выпуск 0.2 def __add__ ( self , other): return Vector2D( self x + other x, self y + other y) def __iadd__ ( self , other): self x += other x self y += other y return self def __sub__ ( self , other): return Vector2D( self x - other x, self y - other y) def __isub__ ( self , other): self x -= other x self y -= other y return self def __abs__ ( self ): return math hypot( self x, self y) def __bool__ ( self ): return self x != 0 or self y != 0 def __neg__ ( self ): return Vector2D( - self x, - self y) >>> x = Vector2D( 3 , 4 ) >>> x Vector2D( 3 , 4 ) >>> (x) ( 3 , 4 ) >>> abs (x) 5.0 >>> y = Vector2D( 5 , 6 ) >>> y Vector2D( 5 , 6 ) >>> x + y Vector2D( 8 , 10 ) >>> x - y Vector2D( - 2 , - 2 ) >>> - x Vector2D( - 3 , - 4 ) >>> x += y >>> x Vector2D( 8 , 10 ) >>> bool (x) True >>> z = Vector2D( 0 , 0 ) >>> bool (z) False >>> - z 30.1. Перегрузка арифметических операторов 119 Самоучитель Python, Выпуск 0.2 Vector2D( 0 , 0 ) В заключение хочу сказать, что перегрузка специальных методов - вещь хорошая, но не стоит ей слишком злоупотреблять. Перегружайте их только тогда, когда вы уверены в том, что это поможет пониманию программного кода. 30.1. Перегрузка арифметических операторов 120 Глава 31 Декораторы Декораторы в Python и примеры их практического использования. Итак, что же это такое? Для того, чтобы понять, как работают декораторы, в первую оче- редь следует вспомнить, что функции в python являются объектами, соответственно, их можно возвращать из другой функции или передавать в качестве аргумента. Также сле- дует помнить, что функция в python может быть определена и внутри другой функции. Вспомнив это, можно смело переходить к декораторам. Декораторы — это, по сути, “обёртки”, которые дают нам возможность изменить поведение функции, не изменяя её код. Создадим свой декоратор “вручную”: >>> def my_shiny_new_decorator (function_to_decorate): # Внутри себя декоратор определяет функцию-"обёртку". Она будет обёрнута ˓→ вокруг декорируемой, # получая возможность исполнять произвольный код до и после неё. def the_wrapper_around_the_original_function (): ( "Я - код, который отработает до вызова функции" ) function_to_decorate() # Сама функция print ( "А я - код, срабатывающий после" ) # Вернём эту функцию return the_wrapper_around_the_original_function >>> # Представим теперь, что у нас есть функция, которую мы не планируем больше ˓→ трогать. >>> def stand_alone_function (): ( "Я простая одинокая функция, ты ведь не посмеешь меня изменять?" ) >>> stand_alone_function() Я простая одинокая функция, ты ведь не посмеешь меня изменять? >>> # Однако, чтобы изменить её поведение, мы можем декорировать её, то есть ˓→ просто передать декоратору, 121 Самоучитель Python, Выпуск 0.2 >>> # который обернет исходную функцию в любой код, который нам потребуется, и ˓→ вернёт новую, >>> # готовую к использованию функцию: >>> stand_alone_function_decorated = my_shiny_new_decorator(stand_alone_function) >>> stand_alone_function_decorated() Я - код, который отработает до вызова функции Я простая одинокая функция, ты ведь не посмеешь меня изменять? А я - код, срабатывающий после Наверное, теперь мы бы хотели, чтобы каждый раз, во время вызова stand_alone_function, вместо неё вызывалась stand_alone_function_decorated. Для этого просто перезапишем stand_alone_function: >>> stand_alone_function = my_shiny_new_decorator(stand_alone_function) >>> stand_alone_function() Я - код, который отработает до вызова функции Я простая одинокая функция, ты ведь не посмеешь меня изменять? А я - код, срабатывающий после Собственно, это и есть декораторы. Вот так можно было записать предыдущий пример, используя синтаксис декораторов: >>> @my_shiny_new_decorator def another_stand_alone_function (): ( "Оставь меня в покое" ) >>> another_stand_alone_function() Я - код, который отработает до вызова функции Оставь меня в покое А я - код, срабатывающий после То есть, декораторы в python — это просто синтаксический сахар для конструкций вида: another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function) При этом, естественно, можно использовать несколько декораторов для одной функции, например так: >>> def bread (func): def wrapper (): () func() ( "<\______/>" ) return wrapper >>> def ingredients (func): def wrapper (): ( "#помидоры#" ) func() ( "салат" ) return wrapper 122 Самоучитель Python, Выпуск 0.2 >>> def sandwich (food = "--ветчина--" ): (food) >>> sandwich() --ветчина-- >>> sandwich = bread(ingredients(sandwich)) >>> sandwich() #помидоры# --ветчина-- салат <\______/> И используя синтаксис декораторов: >>> @bread @ingredients def sandwich (food = "--ветчина--" ): (food) >>> sandwich() #помидоры# --ветчина-- салат <\______/> Также нужно помнить о том, что важен порядок декорирования. Сравните с предыдущим примером: >>> @ingredients @bread def sandwich (food = "--ветчина--" ): (food) >>> sandwich() #помидоры# --ветчина-- <\______/> салат Передача декоратором аргументов в функцию Однако, все декораторы, которые мы рассматривали, не имели одного очень важно- го функционала — передачи аргументов декорируемой функции. Собственно, это тоже несложно сделать. 31.1. Передача декоратором аргументов в функцию 123 Самоучитель Python, Выпуск 0.2 >>> def a_decorator_passing_arguments (function_to_decorate): def a_wrapper_accepting_arguments (arg1, arg2): ( "Смотри, что я получил:" , arg1, arg2) function_to_decorate(arg1, arg2) return a_wrapper_accepting_arguments >>> # Теперь, когда мы вызываем функцию, которую возвращает декоратор, мы вызываем ˓→ её "обёртку", >>> # передаём ей аргументы и уже в свою очередь она передаёт их декорируемой ˓→ функции >>> @a_decorator_passing_arguments def print_full_name (first_name, last_name): ( "Меня зовут" , first_name, last_name) >>> print_full_name( "Vasya" , "Pupkin" ) Смотри, что я получил: Vasya Pupkin Меня зовут Vasya Pupkin Декорирование методов Один из важных фактов, которые следует понимать, заключается в том, что функции и методы в Python — это практически одно и то же, за исключением того, что методы все- гда ожидают первым параметром ссылку на сам объект (self ). Это значит, что мы можем создавать декораторы для методов точно так же, как и для функций, просто не забывая про self. >>> def method_friendly_decorator (method_to_decorate): def wrapper ( self , lie): lie -= 3 return method_to_decorate( self , lie) return wrapper >>> class Lucy : def __init__ ( self ): self age = 32 @method_friendly_decorator def sayYourAge ( self , lie): ( "Мне {} лет, а ты бы сколько дал?" format( self age + lie)) >>> l = Lucy() >>> l sayYourAge( - 3 ) Мне 26 лет, а ты бы сколько дал? Конечно, если мы создаём максимально общий декоратор и хотим, чтобы его можно было применить к любой функции или методу, то можно воспользоваться распаковкой аргу- ментов: 31.2. Декорирование методов 124 Самоучитель Python, Выпуск 0.2 >>> def a_decorator_passing_arbitrary_arguments (function_to_decorate): # Данная "обёртка" принимает любые аргументы def a_wrapper_accepting_arbitrary_arguments ( * args, ** kwargs): ( "Передали ли мне что-нибудь?:" ) (args) (kwargs) function_to_decorate( * args, ** kwargs) return a_wrapper_accepting_arbitrary_arguments >>> @a_decorator_passing_arbitrary_arguments def function_with_no_argument (): ( "Python is cool, no argument here." ) >>> function_with_no_argument() Передали ли мне что-нибудь?: () {} Python is cool, no argument here. >>> @a_decorator_passing_arbitrary_arguments def function_with_arguments (a, b, c): (a, b, c) >>> function_with_arguments( 1 , 2 , 3 ) Передали ли мне что-нибудь?: (1, 2, 3) {} 1 2 3 >>> @a_decorator_passing_arbitrary_arguments def function_with_named_arguments (a, b, c, platypus = "Почему нет?" ): ( "Любят ли {} , {} и {} утконосов? {} " format(a, b, c, platypus)) >>> function_with_named_arguments( "Билл" , "Линус" , "Стив" , platypus = "Определенно!" ) Передали ли мне что-нибудь?: ('Билл', 'Линус', 'Стив') {'platypus': 'Определенно!'} Любят ли Билл, Линус и Стив утконосов? Определенно! >>> class Mary ( object ): def __init__ ( self ): self age = 31 @a_decorator_passing_arbitrary_arguments def sayYourAge ( self , lie =- 3 ): # Теперь мы можем указать значение по ˓→ умолчанию print ( "Мне {} лет, а ты бы сколько дал?" format( self age + lie)) >>> m = Mary() >>> m sayYourAge() Передали ли мне что-нибудь?: (<__main__.Mary object at 0x7f6373017780>,) {} Мне 28 лет, а ты бы сколько дал? 31.2. Декорирование методов 125 Самоучитель Python, Выпуск 0.2 Декораторы с аргументами А теперь попробуем написать декоратор, принимающий аргументы: >>> def decorator_maker (): ( "Я создаю декораторы! Я буду вызван только раз: когда ты попросишь ˓→ меня создать декоратор." ) def my_decorator (func): ( "Я - декоратор! Я буду вызван только раз: в момент декорирования ˓→ функции." ) def wrapped (): ( "Я - обёртка вокруг декорируемой функции.\n" "Я буду вызвана каждый раз, когда ты вызываешь декорируемую ˓→ функцию.\n" "Я возвращаю результат работы декорируемой функции." ) return func() ( "Я возвращаю обёрнутую функцию." ) return wrapped print ( "Я возвращаю декоратор." ) return my_decorator >>> # Давайте теперь создадим декоратор. Это всего лишь ещё один вызов функции >>> new_decorator = decorator_maker() Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать ˓→ декоратор. Я возвращаю декоратор. >>> >>> # Теперь декорируем функцию >>> def decorated_function (): ( "Я - декорируемая функция." ) >>> decorated_function = new_decorator(decorated_function) Я - декоратор! Я буду вызван только раз: в момент декорирования функции. Я возвращаю обёрнутую функцию. >>> # Теперь наконец вызовем функцию: >>> decorated_function() Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз, когда ты вызываешь декорируемую функцию. Я возвращаю результат работы декорируемой функции. Я - декорируемая функция. Теперь перепишем данный код с помощью декораторов: >>> @decorator_maker () def decorated_function (): ( "Я - декорируемая функция." ) Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать ˓→ декоратор. Я возвращаю декоратор. Я - декоратор! Я буду вызван только раз: в момент декорирования функции. 31.3. Декораторы с аргументами 126 Самоучитель Python, Выпуск 0.2 Я возвращаю обёрнутую функцию. >>> decorated_function() Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию. Я возвращаю результат работы декорируемой функции. Я - декорируемая функция. Вернёмся к аргументам декораторов, ведь, если мы используем функцию, чтобы созда- вать декораторы “на лету”, мы можем передавать ей любые аргументы, верно? >>> def decorator_maker_with_arguments (decorator_arg1, decorator_arg2): ( "Я создаю декораторы! И я получил следующие аргументы:" , decorator_arg1, decorator_arg2) def my_decorator (func): ( "Я - декоратор. И ты всё же смог передать мне эти аргументы:" , decorator_arg1, decorator_arg2) # Не перепутайте аргументы декораторов с аргументами функций! def wrapped (function_arg1, function_arg2): ( "Я - обёртка вокруг декорируемой функции.\n" "И я имею доступ ко всем аргументам\n" "\t- и декоратора: {0} {1} \n" "\t- и функции: {2} {3} \n" "Теперь я могу передать нужные аргументы дальше" format(decorator_arg1, decorator_arg2, function_arg1, function_arg2)) return func(function_arg1, function_arg2) return wrapped return my_decorator >>> @decorator_maker_with_arguments ( "Леонард" , "Шелдон" ) def decorated_function_with_arguments (function_arg1, function_arg2): ( "Я - декорируемая функция и я знаю только о своих аргументах: {0} " " {1} " format(function_arg1, function_arg2)) Я создаю декораторы! И я получил следующие аргументы: Леонард Шелдон Я - декоратор. И ты всё же смог передать мне эти аргументы: Леонард Шелдон >>> decorated_function_with_arguments( "Раджеш" , "Говард" ) Я - обёртка вокруг декорируемой функции. И я имею доступ ко всем аргументам - и декоратора: Леонард Шелдон - и функции: Раджеш Говард Теперь я могу передать нужные аргументы дальше Я - декорируемая функция и я знаю только о своих аргументах: Раджеш Говард Таким образом, мы можем передавать декоратору любые аргументы, как обычной функ- ции. Мы можем использовать и распаковку через *args и **kwargs в случае необходимо- сти. 31.3. Декораторы с аргументами 127 Самоучитель Python, Выпуск 0.2 Некоторые особенности работы с декораторами • Декораторы несколько замедляют вызов функции, не забывайте об этом. • Вы не можете “раздекорировать” функцию. Безусловно, существуют трюки, позво- ляющие создать декоратор, который можно отсоединить от функции, но это плохая практика. Правильнее будет запомнить, что если функция декорирована — это не отменить. • Декораторы оборачивают функции, что может затруднить отладку. Последняя проблема частично решена добавлением в модуле functools функции functools.wraps, копирующей всю информацию об оборачиваемой функции (её имя, из какого она модуля, её документацию и т.п.) в функцию-обёртку. Забавным фактом является то, что functools.wraps тоже является декоратором. >>> def foo (): ( "foo" ) >>> (foo __name__ ) foo >>> # Однако, декораторы мешают нормальному ходу дел: def bar (func): def wrapper (): ( "bar" ) return func() return wrapper >>> @bar def foo (): ( "foo" ) >>> (foo __name__ ) wrapper >>> import functools # "functools" может нам с этим помочь >>> def bar (func): # Объявляем "wrapper" оборачивающим "func" # и запускаем магию: @functools wraps(func) def wrapper (): ( "bar" ) return func() return wrapper >>> @bar def foo (): ( "foo" ) >>> (foo __name__ ) foo 31.4. Некоторые особенности работы с декораторами 128 Самоучитель Python, Выпуск 0.2 Примеры использования декораторов Декораторы могут быть использованы для расширения возможностей функций из сто- ронних библиотек (код которых мы не можем изменять), или для упрощения отладки (мы не хотим изменять код, который ещё не устоялся). Также полезно использовать декораторы для расширения различных функций одним и тем же кодом, без повторного его переписывания каждый раз, например: >>> def benchmark (func): """ Декоратор, выводящий время, которое заняло выполнение декорируемой функции. """ import time def wrapper ( * args, ** kwargs): t = time clock() res = func( * args, ** kwargs) (func __name__ , time clock() - t) return res return wrapper >>> def logging (func): """ Декоратор, логирующий работу кода. (хорошо, он просто выводит вызовы, но тут могло быть и логирование!) """ def wrapper ( * args, ** kwargs): res = func( * args, ** kwargs) (func __name__ , args, kwargs) return res return wrapper >>> def counter (func): """ Декоратор, считающий и выводящий количество вызовов декорируемой функции. """ def wrapper ( * args, ** kwargs): wrapper count += 1 res = func( * args, ** kwargs) ( " {0} была вызвана: {1} x" format(func __name__ , wrapper count)) return res wrapper count = 0 return wrapper >>> @benchmark @logging @counter def reverse_string (string): return '' join( reversed (string)) |