面向对象思想的实现

class Averager():
    def __init__(self):
    self.series = []

    def __call__(self, new_value):
        self.series.append(new_value)
        total = sum(self.series)
        return total/len(self.series)
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

函数式编程思想的实现

def make_averager():
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return averager
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

nonlocal关键字

def make_averager():
    count = 0
    total = 0
    def averager(new_value):
        count += 1
        total += new_value
        return total / count
    return averager
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
 ...
UnboundLocalError: local variable 'count' referenced before assignment
>>>

The problem is that the statement count += 1 actually means the same as count = count + 1, when count is a number or any immutable type. So we are actually assigning to count in the body of averager, and that makes it a local variable. The same problem affects the total variable.

We did not have this problem in the example because we never assigned to the series list, we only called series.append and invoked sum and len on it. So we took advantage of the fact that lists are mutable. But with immutable types like numbers, strings, tuples etc., all you can is read, but never update. If you try to rebind them, as in count = count + 1, then you are implicitly creating a local variable count. It is no longer a free variable, therefore it is not saved in the closure.

To work around this the nonlocal declaration was introduced in Python 3. It lets you flag a variable as a free variable even when it is assigned a new value within the function. If a new value is assigned to a nonlocal variable, the binding stored in the closure is changed.

def make_averager():
    count = 0
    total = 0
    def averager(new_value):
        nonlocal count, total
        count += 1
        total += new_value
        return total / count
    return averager

带参数的装饰器

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            elif level == "info":
                logging.info("%s is running" % func.__name__)
            return func(*args)
        return wrapper
    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

foo()

Stacked decorators

@d1
@d2
def f():
    print('f')

# equivalent to
def f():
    print('f')
f = d1(d2(f))

ormsf
163 声望2 粉丝