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

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

2010/05/03

Декоратори

Декоратори наричаме функциите f от вида:

Резултата е нова функция, разширяваща функционалността на аргумента си.

Memoization

def memoize(func):
    memory
= {}
   
def memoized(*args):
       
if args in memory: return memory[args]
        result
= func(*args)
        memory
[args] = result
       
return result
   
return memoized

def fib(x):
   
if x in [0, 1]: return 1
   
return fib(x - 1) + fib(x - 2)

fib
= memoize(fib)
print(fib(33))

…, лошият и грозният

И все пак…

def fib(x):
   
if x in [0, 1]: return 1
   
return fib(x - 1) + fib(x - 2)

fib
= memoize(fib)

…е грозно. А и има шанс да не видите декоратора, понеже е отдолу.

Клинт Ийстууд

@memoized
def fib(n):
   
if x in [0, 1]: return 1
   
return fib(x - 1) + fib(x - 2)

Динамични декоратори

Декоратор, който приема параметри.

@memoize('/tmp/fibs')
def fib(n):
   
...

е равно на

def fib(n):
   
...
fib
= memoize('/tmp/fibs')(fib)

Да не се бърка с fib = memoize('/tmp/fibs', fib)

with_retries(number)

Трябва да направим функция with_retries(number), която да връща декоратор. Тя изглежда така:

def with_retries(number):
   
def decorator(func):
       
"""Тяло на декоратора, виждащо number, тук"""
       
# TODO
   
return decorator

with_retries(number) (2)

def with_retries(number):
   
def decorator(func):
       
def retrying(*args, **kwargs):
            retries_left
= number
           
while retries_left:
               
try: return func(*args, **kwargs)
               
except: retries_left -= 1
           
return func(*args, **kwargs)
       
return retrying
   
return decorator

staticmethod и classmethod

Вградените функции staticmethod и classmethod също са декоратори.

class Person(object):
    _people
= []
   
def __init__(self, name):
        self
.name = name
       
Person._people.append(self)
       
   
def name_register():
       
return [_.name for _ in Person._people]
    name_register
= staticmethod(name_register)

Друг пример за декоратор

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 with (25,) and {}.

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

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

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

Горният код прави същото като:

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

или:

work = staticmethod(notifyme(work))

Първо се извикват най-вътрешните декоратори.

В лов на патици

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

код > думи

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 #{0} of '{1}' should have been " \
             
"of type {2}".format(i, f.__name__, t.__name__))
         
#TODO: more complex checks: tuple of a type, list of type
       
return f(*args)
     
return decorated
 
return accepter

За патиците с любов

duck typing е много важна част от философията на Python. @accepts е забавен пример и дори има някои употреби, но избягвайте да го ползвате масово. В повечето случаи губите, а не печелите.

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

Малка неконсистентност

class Person(object):
   
def __init__(self, first, last):
        self
.first, self.last = first, last
   
def name(self, value=None):
       
if value == None:
           
return '{0} {1}'.format(self.first, self.last)
       
else:
            self
.first, self.last = value.split(None, 1)

pijo
= Person('Пижо', 'Пендов')
print(pijo.first)
pijo
.last = 'Пендов'
print(pijo.last)
print(pijo.name())
pijo
.name('Кънчо Кънчев')
print(pijo.last)

Решение 0

class Person(object):
   
def __init__(self, first, last):
        self
.first, self.last = first, last
   
def get_name(self):
       
return '{0} {1}'.format(self.first, self.last)
   
def set_name(self):
        self
.first, self.last = value.split(None, 1)
   
def __getattr__(self, attr):
       
if 'name' == attr:
           
return self.get_name()
       
return object.__getattr__(self, attr)
   
def __setattr__(self, attr, value):
       
if 'name' == attr:
            self
.set_name(value)
       
else:
           
object.__setattr__(self, attr, value)

Решение 1

class Person(object):
   
def __init__(self, first, last):
        self
.first, self.last = first, last
   
def get_name(self):
       
"""Full name"""
       
return '{0} {1}'.format(self.first, self.last)
   
def set_name(self, value):
        self
.first, self.last = value.split(None, 1)
    name
= property(get_name, set_name)

property като декоратор

class Parrot(object):
   
def __init__(self):
        self
._voltage = 100000

   
@property
   
def voltage(self):
       
"""Get the current voltage."""
       
return self._voltage

Meyer's substitution principle

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

Пример с файлове

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

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

Още един опит

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

with

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

with нагледно


with open('/etc/passwd') as source_file:
    buffer
= source_file.readlines()
print('Done!')

е същото като


source_file
= open('/etc/passwd').__enter__()
try:
  buffer
= source_file.readlines()
  source_file
.__exit__(None, None, None)
except Exception:
  source_file
.__exit__(*sys.exc_info())
print('Done!')

Малък пример

 
class Manager:
   
def __enter__(self):
       
print("I've been entered!")
       
return 42
   
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?
42
I've been exited!

contextlib

Вграденият модул contextlib ни предлага три много полезни Context Manager-а:

closing

contextlib.closing вика метода close на обекта, с който работим, след изпълнение на блока:
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 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
()

Още въпроси?