Fluent Python 译本 读书笔记 第7章 函数装饰器和闭包

《Fluent Python》借来5天了。最近两天没顾上看书,作业有点多。今天把这一章开个头。之前大概看过《Python高级编程》了解了一点装饰器,有些太基础的就不写了。

7.2 Python何时执行装饰器 Page 156

装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即Python加载模块时)。

# registration.py
registry = []

def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func

@register
def f1():
    print('running f1()')

@regigter
def f2():
    print('running f2()')

def f3():
    print('running f3()')

def main():
    print('running main()')
    print('registry ->', registry)
    f1()
    f2()
    f3()

if __name__ == '__main__':
    main()

registration.py当作脚本运行得到的输出如下:

$ python3 registration.py
running register(<function f1 at 0x...>)
running register(<function f2 at 0x...>)
running main()
registry -> [<function f1 at 0x...>, <function f2 at 0x...>]
running f1()
running f2()
running f3()

如果导入registration.py模块(不作为脚本运行),输出如下:

>>> import registration
running register(<function f1 at 0x...>)
running register(<function f2 at 0x...>)
>>> registraion.registry
[<function f1 at 0x...>, <function f2 at 0x...>]

这个示例突出了Python程序员所说的 导入时运行时 的区别。

7.4 变量作用域规则 Page 159

下面的示例,我们定义并测试了一个函数,它读取两个变量的值:一个是局部变量a,是函数的参数;另一个是变量b,这个函数没有定义它。

>>> def f1(a):
        print(a)
        print(b)
>>> f1(3)
3
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 3, in f1
NameError: name 'b' is not defined

出现错误并不奇怪。如果先给b赋值,然后再调用f1,那就不会出错。

>>> b = 6
>>> f1(3)
3
6

下面是一个令人吃惊的示例。

>>> b = 6
>>> def f2(a):
    print(a)
    print(b)
    b = 9
>>> f2(3)
3
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    f2(3)
  File "<pyshell#5>", line 3, in f2
    print(b)
UnboundLocalError: local variable 'b' referenced before assignment

事实上,Python在编译该函数的定义体时,它判断b是局部变量,因为在函数中给它赋值了。生成的字节码证实了这种判断,Python会尝试从本地环境获取b。后面调用f2(3)时,f2的定义体会获取并打印局部变量a的值,但是尝试获取局部变量b的值时,发现b没有绑定值。
这不是缺陷,而是设计选择:Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。这比JavaScript的行为好多了,JavaScript也不要求声明变量, 但是如果忘记把变量声明为局部变量(使用var),可能会在不知情的情况下获取全局变量。
如果在函数中赋值时想让解释器把b当成全局变量,要使用global声明:

>>> b = 6
>>> def f3(a):
    global b
    print(a)
    print(b)
    b = 9
>>> f3(3)
3
6
>>> b
9

比较字节码
dis模块为反汇编Python函数字节码提供了简单的方式。

>>> def f1(a):
        print(a)
        print(b)
>>> def f2(a):
        print(a)
        print(b)
        b = 9
>>> from dis import dis
>>> dis(f1)
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

  3           8 LOAD_GLOBAL              0 (print)
             10 LOAD_GLOBAL              1 (b)
             12 CALL_FUNCTION            1
             14 POP_TOP
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE
>>> dis(f2)
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

  3           8 LOAD_GLOBAL              0 (print)
             10 LOAD_FAST                1 (b)
             12 CALL_FUNCTION            1
             14 POP_TOP

  4          16 LOAD_CONST               1 (9)
             18 STORE_FAST               1 (b)
             20 LOAD_CONST               0 (None)
             22 RETURN_VALUE

LOAD_GLOBAL意思是加载全局名称,LOAD_FAST意思是加载本地名称。
运行字节码的CPython VM是栈机器,因此LOADPOP操作引用的是栈。深入说明Python操作码不在本书范畴之内,不过dis模块的文档对其做了说明。

7.5 闭包 Page 161

闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外的非全局变量。
这个概念难以掌握,最好通过示例理解。
假如有个名为avg的函数,它的作用是计算不断增加的系列值的均值;例如,整个历史中某个商品的平均收盘价。每天都会增加新价格,因此平均值要考虑至目前为止所有的价格。
初学者可能会像下例那样使用类实现:

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hull Qin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值