- Функции и методы в Python: передача функции в функцию. Декоратор
- Что надо знать о методах и функциях в Python?
- Функция как объект в Python
- Функция внутри функции в Python
- Декоратор функции в Python
- Создание
- Передаём аргументы в функцию с помощью декоратора
- Декораторы для методов класса в Python
- Возвращаем результат работы функции через декоратор
- From Function to Method
- From Function .
- . To Method, via the Descriptor Protocol
- References
Функции и методы в Python: передача функции в функцию. Декоратор
Эта статья посвящена теме декораторов в Python. Поговорим о том, что это такое, уделим особое внимание свойствам функций в Python, на базе которых реализована данная идея, а также рассмотрим декораторы, которые принимают аргументы и возвращают значение из функции.
Что надо знать о методах и функциях в Python?
Говоря о функциях в Python, нужно упомянуть два аспекта: 1) функция в Python — есть объект специального вида, который можно передавать в виде аргумента другим функциям; 2) внутри функций в Python вы можете создавать другие функции, а также вызывать их, возвращая результат посредством return.
Теперь давайте поговорим об этом подробнее.
Функция как объект в Python
В языке программирования Python часто практикуется передача одной функции в виде аргумента другой функции. Представьте, что есть список целых чисел, и вы желаете на его базе получить другой список с элементами, которые будут квадратами первого списка. Вот, как это можно реализовать в Python:
>>> # исходный список >>> a = [1, 2, 3, 4, 5] >>> # функция, которая возводит в квадрат переданное ей число >>> sq = lambda x: x**2 >>> # проверка работы функции в Python >>> print(sq(5)) 25 >>> # получение списка квадратов >>> b = list(map(sq, a)) >>> print(b) [1, 4, 9, 16, 25]В нашем примере мы передали функции map в виде первого аргумента функцию sq. Последняя будет по очереди применяться ко всем элементам нашего списка a.
Кроме того, в Python функция является специальным объектом, имеющим метод __call__() . Представьте, что мы создали следующий класс:
class DemoCall(): def __call__(self): return "Hello!"
Объект такого класса в Python мы сможем вызывать как функцию:
>>> hello = DemoCall() >>> hello() 'Hello!'Функция внутри функции в Python
Функции в Python мы можем создавать, вызывать и возвращать из других функций. Кстати, на этом основана идея замыкания (closures) в Python.
Давайте создадим функцию, умножающую 2 числа:
def mul(a): def helper(b): return a * b return helperВ этой функции в Python реализованы два важных свойства: 1) внутри функции mul() мы создаём ещё одну функцию helper() ; 2) функция mul() возвращает нам функцию helper() в качестве результата работы.
Вызов этой функции в Python:
Особенность заключается в том, что мы можем создавать на базе функции mul() собственные кастомизированные функции. Давайте создадим функцию в Python, умножающую на 3:
>>>three_mul = mul(3) >>>three_mul(5) 15В результате была построена функция three_mul() , умножающая на 3 любое переданное ей число.
Декоратор функции в Python
Конструктивно речь идёт о некоторой функции, в качестве аргумента которого выступает другая функция. Декоратор в Python добавляет дополнительный функционал к функции, не меняя её содержимое.
Создание
Представьте, что мы имеем пару простых функций в Python:
def first_test(): print("Test function 1") def second_test(): print("Test function 2")При этом мы желаем их дополнить таким образом, чтобы перед вызовом основного кода нашей функции печаталась строчка “Start function”, а в конце – строка “Stop function”.
Реализовать поставленную задачу можно несколькими методами. Во-первых, мы можем добавить необходимые строки в конец и начало каждой функции. Но вряд ли это удобно, ведь если мы пожелаем их убрать, придётся модифицировать тело функции.
Теперь поговорим о втором пути. Для начала создадим функцию:
def simple_decore(fn): def wrapper(): print("Start function") fn() print("Stop function") return wrapperТеперь нужно обернуть функции в оболочку:
first_test_wrapped = simple_decore(first_test) second_test_wrapped = simple_decore(second_test)Обратите внимание, что функции first_test и second_test не поменялись.
>>> first_test() Test function 1 >>> second_test() Test function 2Наши функции second_test_wrapped и first_test_wrapped обладают функционалом, который нам и нужен.
>>> first_test_wrapped() Start function Test function 1 Stop function >>> first_test_wrapped() Start function Test function 1 Stop functionТеперь, если надо, чтобы так работали функции с именами first_test и second_test, делаем следующее:
first_test = first_test_wrapped second_test = second_test_wrapped>>> first_test() Start function Test function 1 Stop function >>> second_test() Start function Test function 2 Stop functionВыполненные нами действия и являются реализацией идеи декоратора.
def first_test(): print("Test function 1") first_test_wrapped = simple_decore(first_test) first_test = first_test_wrapped@simple_decore def first_test(): print("Test function 1")
В нашем случае @simple_decore – это не что иное, как декоратор функции.
Передаём аргументы в функцию с помощью декоратора
Бывает, что функция требует наличие аргумента, поэтому мы можем передать его через декоратор. Давайте создадим декоратор, принимающий аргумент и выводящий информацию о декорируемой нами функции и её аргументе.
def param_transfer(fn): def wrapper(arg): print("Start function: " + str(fn.__name__) + "(), with param: " + str(arg)) fn(arg) return wrapperЧтобы продемонстрировать работу, создадим функцию, выводящую квадратный корень переданного ей числа, а в качестве декоратора, укажем созданный param_transfer:
@param_transfer def print_sqrt(num): print(num**0.5)
Теперь давайте выполним данную функцию с аргументом 4:
>>> print_sqrt(4) Start function: print_sqrt(), with param: 4 2.0Декораторы для методов класса в Python
С декоратором можно объявлять и методы классов. Давайте выполним модификацию декоратора param_transfer:
def method_decor(fn): def wrapper(self): print("Start method: " + str(fn.__name__)) fn(self) return wrapperТеперь приступим к созданию класса для представления 2-мерного вектора (математического). В классе определим метод norm() , выводящий модуль вектора.
class Vector(): def __init__(self, px = 0, py = 0): self.px = px self.py = py @method_decor def norm(self): print((self.px**2 + self.py**2)**0.5)Что же, осталось продемонстрировать работу нашего метода:
>>> vc = Vector(px=10, py=5) >>> vc.norm() Start method: norm 11.180339887498949Возвращаем результат работы функции через декоратор
Зачастую создаваемые функции выполняют возвращение какого-либо значения. Чтобы это стало возможным осуществить через декоратор, нужно специальным образом построить нашу внутреннюю функцию:
def decor_with_return(fn): def wrapper(*args, **kwargs): print("Start method: " + str(fn.__name__)) return fn(*args, **kwargs) return wrapperВозможно применение этого декоратора и для оборачивания функций, принимающих различные аргументы и возвращающие значение:
@decor_with_return def calc_sqrt(val): return val**0.5
Осталось выполнить функцию calc_sqrt() с параметром 16:
>>> tmp = calc_sqrt(16) Start method: calc_sqrt >>> print(tmp)From Function to Method
Python newcomers often have hard time understanding the "magic" behind Python's methods. And the truth is that Python's object model can be a bit peculiar when compared to most mainstream (or not-so-mainstream) object-oriented programming languages. (There are quite a few threads on c.l.py with either direct or indirect questions about what makes a Python method.)
Here's a brief -- but hopefully helpful -- overview of what exactly is a Python method, showing how Python magically inserts self or cls into the argument list of a method call.
From Function .
The def statement always yields a function object. Always. If you don't believe it, try the following snippet:
class Foo(object): def bar(self): return "baaz" print type(Foo.bar) # print type(Foo.__dict__['bar']) #
So, why is it that type(Foo.bar) is not the same as type(Foo.__dict__['bar'])? The answer is: attribute lookup rules and the descriptor protocol.
. To Method, via the Descriptor Protocol
The return value of this call becomes the result of the attribute lookup. This mechanism is what provides support for computed attributes.
The function type implements this descriptor protocol. So when a function is an attribute of a class object and you access it as an attribute of the class itself, its __get__ method is called with None and the class as arguments. When you access it as an attribute of an instance of the class, its __get__ method is called with the instance and the class as arguments.
With the instance object (if any) and class object available, it's easy to create a method object that wraps the function object. This is itself a callable object; calling it mostly injects the instance as the first item in the argument list and returns the result of calling the wrapped function object.
A (naive) implementation of the whole thing might look like this:
class method(object): def __init__(self, func, instance, cls): self.im_func = func self.im_self = instance self.im_class = cls def __call__(self, *args, **kw): # XXX : all sanity checks removed for readability if self.im_self: args = (self.im_self,) + args return self.im_func(*args, **kw) class function(object): def __get__(self, instance, cls): return method(self, instance, cls)So, what turns a function into a method is not that the function is defined in a class statement's body (well, not directly at least). Rather, it's that the function is an attribute of the class. For what it's worth, the following code is perfectly legal:
class Foo(object): pass def func(obj): print "obj is %s " % obj Foo.method = func f = Foo() f.method() # all three of these Foo.method(f) # invocations produce func(f) # the same resultReferences
FromFunctionToMethod (last edited 2014-07-23 10:01:07 by mjpieters )