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

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

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

04.04.2007г.

Декоратори

Синтаксис

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:
    

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

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


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

Втори опит


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?

Как се ползва 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

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!

Още въпроси?