събота, 17 януари 2009 г.

Декоратори и with в python


„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

04.04.2007г.

Декоратори

  • Припомнете си как правихме статични методи:
  • class Mityo:
    # не зависи от инстанцията -- винаги пасуват
    def work(): pass
    work = staticmethod(work)
  • Горното се нарича „декориране“ на метода work
  • Подходът за смилане на метод или функция за да бъдат по-различни или по-полезни е често срещан в Python

Синтаксис

def add(a, b):
return a+b
add = accepts2ints(add)

Поради честотата на използване и поради неудобността на горния синтаксис, в Python си има отделен синтаксис за декорирането.

@accepts2ints
def add(a, b):
return a+b

В този случай, accepts2ints трябва да бъде функция, която приема функция и връща нейната нова версия.

Пример за прост декоратор

def notifyme(f):
def logged(*args, **kwargs):
print f.__name__, 'was called with', args, 'and', kwargs
return f(*args, **kwargs)
return logged

@notifyme
def square(x): return x*x

res = square(25)
square was called.

Няколко декоратора на една функция

class Mityo:
@staticmethod
@notifyme
def work(): pass

Mityo.work()
work was called with () and {}

Горният метод е еквивалентен на:

def work(): pass
work = notifyme(work)
work = classmethod(work)

Или:

work = classmethod(notifyme(work))

Параметри на декораторите

Ако се върнем към примера с accepts2int, сигурно ще ни се прииска да си напишем малко по-общ декоратор, на когото да казваме типовете, които искаме функцията ни искаме да приема а не да пишем нов декоратор за всяка комбинация, която ни хрумне.

@accepts(int, int)
def add(a, b): return a+b

Това се превежда до следното:

add = accepts(int, int)(add)

Другояче казано accepts трябва да бъде функция, която приема като аргументи типовете и връща друга фунцкия. Тя пък трябва да приема вече фунцкията, която ще декорираме и да върне декорираната.

Код срещу думи

def accepts(*types):
def accepter(f):
def decorated(*args):
for (i, (arg, t)) in enumerate(zip(args, types)):
if not isinstance(arg, t):
raise TypeError("Argument #%d of '%s' should have been of type %s" % (i, f.__name__, t.__name__))
#TODO: more complex checks: tuple of a type, list of type
return f(*args)
return decorated
return accepter

Още код


instances = {}

def singleton(cls):
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance

@singleton
class MyClass:

Вградени декоратори

  • staticmethod — прави един метод статичен
  • classmethod — прави класов метод (като първи аргумент приема класа)

Работа с файлове


src = raw_input()
target = raw_input()

try:
sourceFile = open(src, 'r')
buffer = []
try:
buffer = sourceFile.readlines()
finally:
sourceFile.close()

targetFile = open(target, 'w')
try:
for line in reversed(buffer):
targetFile.write(line)
finally:
targetFile.close()
except IOError:
print "Tough luck, junior"

With

  • Твърдението with позволява да преизползвате сходна try/catch логика (и не само)
  • Синтаксис:
    with [израз]:
    [блок]

    или

    with [израз] as [име]:
    [блок]

Втори опит


src = raw_input()
target = raw_input()

buffer = []
try:
with open(src) as sourceFile:
buffer = sourceFile.readlines()
with open(target) as targetFile:
for line in reversed(buffer):
targetFile.write(line)
except IOError:
print "Much better, now, ain't it?"

Как работи with?

  • Изразът който получава се изпълнява и се взема върнатия обект. Той се нарича context manager
  • Методът __enter__() на content manager-а се изпълнява. Получената от него стойност се записва в името след as.
  • Изпълнява се блокът подаден на with
  • Ако се получи изключение в блока, се извиква метода __exit__(type, value, traceback) на context manager-а.
  • Ако не се получи изключение, пак се вика __exit__(None, None, None).

Как се ползва with?

Понеже with е нова ключова дума, въведена в Python 2.5, се налага да добавите малко код към вашето приложение за да я ползвате:

from __future__ import with_statement

Ако не сте импортирали with и го ползвате като име, Python ще даде грешка. В Python 2.6 with ще го има по подразбиране и няма да се налага да го импортвате. Това е стандартния подход за приемане на нови feature-и в езика.

Малък пример


from __future__ import with_statement

class Manager:
def __enter__(self):
print "I've been entered!"
return "ticket"
def __exit__(self, type, value, traceback):
print "I've been exited!"

with Manager() as something:
print "Am I inside?"
print something
I've been entered!
Am I inside?
ticket
I've been exited!

contextlib

Модула contextlib предлага следните неща:

  • closing
  • nested
  • contextmanager

closing

contextlib.closing е нещо подобно на...

class closing(object):
def __init__(self, thing): self.thing = thing
def __enter__(self): return thing
def __exit__(self, type, value, traceback): self.thing.close()
...и ви позволява да пишете така...

from __future__ import with_statement
from contextlib import closing
import codecs

with closing(urllib.urlopen('http://www.python.org')) as page:
for line in page:
print line

nested

Долния код...


from contextlib import nested

with nested(A, B, C) as (X, Y, Z):
do_something()
...е еквивалентен на...

with A as X:
with B as Y:
with C as Z:
do_something()

contextmanager

contextmanager е декоратор, който превръща генератор функция в context manager:


from __future__ import with_statement
from contextlib import contextmanager

@contextmanager
def entering(whom):
print "I've been entered by %s" % whom
yield "ticket"
print "I've been exited!"

with entering("someone") as something:
print "Am I inside?"
print something
I've been entered by someone
Am I inside?
ticket
I've been exited!

Няма коментари:

Публикуване на коментар