среда, марта 19, 2008

Доклад по Python: часть III

9. Функциональное программирование.

ФП не является "родной" парадигмой для Питона. В частности, в Питоне нет оптимизации хвостовой рекурсии (создатель языка недавно подтвердил, что и не будет), что делает практически невозможным программирование в характерном для ФП рекурсивном стиле.

Однако некоторые элементы ФП могут быть использованы в Питоне очень эффективно. В частности, выше мы видели примеры применения конструкции lambda. Эта конструкция создает анонимную функцию, а точнее - замыкание (то есть создаваемая функция "запоминает" значения внешних переменных в момент создания). Классический пример использования замыканий:


def add(x,y):
return x+y

inc = lambda x: add(x,1)

def incrementer(n):
return lambda x: add(x,n)

inc(3) # выдаст 4

f = incrementer(3)

f(5) # выдаст 8.

Другие типичные конструкции, позаимствованные из функциональных языков - это стандартные функции map,filter,zip и специальная форма, называемая списочным сокращением (list comprehension).

Функция map(f,list) возвращает список значений, полученный применением функции f к каждому элементу списка list. Пример:

>>> def sqr(x):
... return x*x
...
>>> map(sqr,[1,2,3])
[1, 4, 9]


Функция filter(p,list) возвращает список из только тех элементов списка list, для которых функция p возвращает истину. Пример:


>>> def even(n):
... return n%2 == 0
...
>>> filter(even,[1,2,3,4,5,6])
[2, 4, 6]

Функция zip принимает два (или больше) списка и возвращает список пар (кортежей), составленных из соответствующих элементов списков:

>>> zip([1,2,3,4],["A","B","C","D"],['x','y','z','t'])
[(1, 'A', 'x'), (2, 'B', 'y'), (3, 'C', 'z'), (4, 'D', 't')]

List comprehensions позволяют одной строкой создать списки по какому-нибудь правилу:

>>> [x*x for x in [1,2,3,4,5]]
[1, 4, 9, 16, 25]

Можно тут же отбирать нужные элементы списка:

>>> [x*x for x in [1,2,3,4,5] if even(x)]
[4, 16]

Можно перебирать одновременно несколько последовательностей:

>>> l = [1,2,3,4,5]
>>> [x*y for x in l for y in l]
[1, 2, 3, 4, 5, 2, 4, 6, 8, 10, 3, 6, 9, 12, 15, 4, 8, 12, 16, 20, 5, 10, 15, 20, 25]

10. Декораторы.

Декораторы - это одна из самых мощных особенностей Питона (особенно в сочетании с интроспекцией). В простейшем случае декоратор - это функция, принимающая в качестве аргумента функцию и возвращающая новую функцию.

Применяются декораторы так:

@decor
def f(x,y):
...

и это эквивалентно такой записи:

def f(x,y):
...
f = decor(f)

Классический пример использования декораторов - это отслеживание вызовов функции:
def trace(func):
name = func.func_name # имя переданной функции
def wrapper(*args,**kwargs): # создаем новую функцию
print "Function called: %s(%s,%s)" % (name,args,kwargs)
result = func(*args,**kwargs) # вызываем исходную функцию
print "Function %s returned %s" % (name,result)
return result
return wrapper # возвращаем созданную функцию

@trace
def f(x,y):
return x*y+2

Обычно декоратор выполняет какую-то работу дополнительно к тому, что делает сама функция. Например, в веб-фреймворке Django есть декоратор login_required - он проверяет, что пользователь уже авторизовался, и только в этом случае вызывает исходную функцию.

Таким образом, декораторы в Питоне обеспечивают возможность аспектно-ориентированного программирования.

Другой пример - декоратор может вызывать исходную функцию только в том случае, если включен режим отладки. Или в режиме выполнения юнит-тестов вызывать функцию-заглушку.

Собственно функция-декоратор должна принимать только один аргумент - исходную функцию. Но можно писать функции, которые возвращают декораторы:

def print_on_call(text):
def decorator(func):
def wrapper(*args,**kwargs):
print ">> "+text
return func(*args,**kwargs)
return wrapper
return decorator

@print_on_call("F called!")
def f(x):
...

Другой пример использования декораторов - реализация делегатов (методов объекта, которые вызывают метод другого класса с тем же именем):

def delegate(cls):
def decorator(meth):
name = meth.func_name
def wrapper(*args,**kwargs):
m = object.__getattribute__(cls,name)
result = m(*args,**kwargs)
return result
return wrapper
return decorator

class A(object):
x = 0
def two(self,z):
r = self.x*z
self.x = z
return r

class B(object):
x = 3

@delegate(A)
def two(self,z):
pass

b = B()
print b.two(5) # Выводит 15

11. Дескрипторы

Дескриптор — это класс с определенными методами __get__() и __set__(). Предполагается, что __get__() возвращает значение экземпляра, а __set__() — соответственно, устанавлнивает.

Особенность дескрипторов состоит в том, что они нормально работают только как атрибуты классов.

В Питоне есть стандартный класс-дескриптор property, конструирующий свойство объекта из getter-а и setter-а. Типичный пример использования property:


class A(object):
def getx(self):
print "Getter called."
return self._x

def setx(self,value):
print "Setting .x to %s" % value
self._x = value

x = property(getx,setx)
Пользовательские классы-дескрипторы могут выполнять более сложную работу: например, можно хранить количество присваиваний (или более сложное состояние) внутри класса-дескриптора.

Таким образом, дескрипторы - это значительное обобщение свойств (properties), имеющихся в C#.

12. Метаклассы

В Питоне всё есть объект. В том числе, классы - это тоже объекты.
Самые часто используемые объекты — это экземпляры классов. А классы, в свою очередь, являются экземплярами метаклассов.

Можно взглянуть на это несколько с другой стороны: класс — это шаблон для создания экземпляров класса. А метакласс — это шаблон для создания классов.

По умолчанию используется стандартный метакласс type. Однако, наследуясь от него, можно создавать свои метаклассы.

Чаще всего метаклассы используются, когда нужно добавить некоторые атрибуты (методы) во все создаваемые классы.

>>> class Meta(type):
... def whoami(cls):
... print "I'm a", cls.__name__
...
>>> class A(object):
... __metaclass__ = Meta # Указываем используемый метакласс
... def do_something(self):
... print "Doing something."
...
>>> a = A()
>>> A.whoami()
I'm a A
>>> a.do_something()
Doing something.

Также можно переопределять процесс создания классов:

>>> class Meta2(type):
... def __new__(cls,name,bases,dct): #Конструктор
... print "Creating class",name
... return type.__new__(cls,name,bases,dct)
... def __init__(cls,name,bases,dct): #Инициализатор
... print "Init'ing class", name
... super(Meta2,cls).__init__(name,bases,dct)
... cls.x = 25 # Добавляем атрибуты к классу
... cls.y = 30
...
>>> class B(object):
... __metaclass__ = Meta2
... def f(self):
... print "Calling F."
...
Creating class B
Init'ing class B
>>> b = B()
>>> b.x
25
>>> b.f()
Calling F.

Таким образом, метаклассы позволяют часть логики каждого класса вынести за пределы самого класса. Это еще одна возможность для аспектно-ориентированного подхода.

7 комментариев:

  1. Сама по себе lambda не создает замыкания:
    >>> a=1
    >>> b=lambda(x):x*a
    >>> b
    <function <lambda> at 0x4c4b0>
    >>> b(3)
    3
    >>> a=3
    >>> b(3)
    9

    ОтветитьУдалить
  2. Насколько я понимаю, дело обстоит так. Ваша b содержит ссылку на 'a', а 'a' меняется.
    В случае, который приведен у меня, функция содержит ссылку на переменную, которая уже не поменяется - т.к. является локальной.
    Что именно тут называть замыканием - да, вопрос.

    ОтветитьУдалить
  3. Что считать замыканием в вашем случае, понятно — результат функции, возвращающей лямбду. И в моем понятно — ничего, поскольку свободный аргумент не был связан ;)

    Просто, кмк, не совсем корректно писать, что конструкция lambda создает замыкание. Потому что сама по себе она создает лямбда-функцию, а контекст, с которым связываются свободные аргументы, создает обычная функция, возвращающая лямбду.

    ОтветитьУдалить
  4. Анонимный3/20/2008 4:07 ДП

    ну как только не извращаются, лишь бы паскаль не учить :)

    ОтветитьУдалить
  5. Спасибо, очень познавательно. Как раз недавно начал интересоваться питоном, так что ваши посты очень кстати :)

    ОтветитьУдалить
  6. Замыкание есть любая функция определённая внутри другой функции и запоминающая локальные переменные надфункции. Лямбда лишь частный случай создания функции, и к замыканиям имеет отношение не большее чем любой другой способ создания функции.

    ОтветитьУдалить
    Ответы
    1. вы не правы. замыкание это функция, у которой есть ссылки на переменные вне тела функции.

      Удалить