目录 | 上一节 (7.2 匿名函数) | 下一节 (7.4 装饰器)

7.3 返回函数

本节介绍使用函数创建其它函数的思想。

简介

考虑以下函数:

  1. def add(x, y):
  2. def do_add():
  3. print('Adding', x, y)
  4. return x + y
  5. return do_add

这是返回其它函数的函数。

  1. >>> a = add(3,4)
  2. >>> a
  3. <function do_add at 0x6a670>
  4. >>> a()
  5. Adding 3 4
  6. 7

局部变量

请观察内部函数是如何引用外部函数定义的变量的。

  1. def add(x, y):
  2. def do_add():
  3. # `x` and `y` are defined above `add(x, y)`
  4. print('Adding', x, y)
  5. return x + y
  6. return do_add

进一步观察会发现,在 add() 函数结束后,这些变量仍然保持存活。

  1. >>> a = add(3,4)
  2. >>> a
  3. <function do_add at 0x6a670>
  4. >>> a()
  5. Adding 3 4 # Where are these values coming from?
  6. 7

闭包

当内部函数作为结果返回时,该内部函数称为闭包(closure)。

  1. def add(x, y):
  2. # `do_add` is a closure
  3. def do_add():
  4. print('Adding', x, y)
  5. return x + y
  6. return do_add

基本特性:闭包保留该函数以后正常运行所需的所有变量的值。可以将闭包视作一个函数,该函数拥有一个额外的环境来保存它所依赖的变量的值。

使用闭包

虽然闭包是 Python 的基本特性,但是它们的用法通常很微妙。常见应用:

  • 在回调函数中使用。
  • 延迟计算。
  • 装饰器函数(稍后介绍)。

延迟计算

考虑这样的函数:

  1. def after(seconds, func):
  2. import time
  3. time.sleep(seconds)
  4. func()

使用示例:

  1. def greeting():
  2. print('Hello Guido')
  3. after(30, greeting)

after (延迟30 秒后)执行给定的函数……

闭包附带了其它信息。

  1. def add(x, y):
  2. def do_add():
  3. print(f'Adding {x} + {y} -> {x+y}')
  4. return do_add
  5. def after(seconds, func):
  6. import time
  7. time.sleep(seconds)
  8. func()
  9. after(30, add(2, 3))
  10. # `do_add` has the references x -> 2 and y -> 3

代码重复

闭包也可以用作一种避免代码大量重复的技术。

练习

练习 7.7:使用闭包避免重复

闭包的一个更强大的特性是用于生成重复的代码。让我们回顾 练习 5.7 代码,该代码中定义了带有类型检查的属性:

  1. class Stock:
  2. def __init__(self, name, shares, price):
  3. self.name = name
  4. self.shares = shares
  5. self.price = price
  6. ...
  7. @property
  8. def shares(self):
  9. return self._shares
  10. @shares.setter
  11. def shares(self, value):
  12. if not isinstance(value, int):
  13. raise TypeError('Expected int')
  14. self._shares = value
  15. ...

与其一遍又一遍地输入代码,不如使用闭包自动创建代码。

请创建 typedproperty.py 文件,并把下述代码放到文件中:

  1. # typedproperty.py
  2. def typedproperty(name, expected_type):
  3. private_name = '_' + name
  4. @property
  5. def prop(self):
  6. return getattr(self, private_name)
  7. @prop.setter
  8. def prop(self, value):
  9. if not isinstance(value, expected_type):
  10. raise TypeError(f'Expected {expected_type}')
  11. setattr(self, private_name, value)
  12. return prop

现在,通过定义下面这样的类来尝试一下:

  1. from typedproperty import typedproperty
  2. class Stock:
  3. name = typedproperty('name', str)
  4. shares = typedproperty('shares', int)
  5. price = typedproperty('price', float)
  6. def __init__(self, name, shares, price):
  7. self.name = name
  8. self.shares = shares
  9. self.price = price

请尝试创建一个实例,并验证类型检查是否有效:

  1. >>> s = Stock('IBM', 50, 91.1)
  2. >>> s.name
  3. 'IBM'
  4. >>> s.shares = '100'
  5. ... should get a TypeError ...
  6. >>>

练习 7.8:简化函数调用

在上面示例中,用户可能会发现调用诸如 typedproperty('shares', int) 这样的方法稍微有点冗长 ——尤其是多次重复调用的时候。请将以下定义添加到 typedproperty.py 文件中。

  1. String = lambda name: typedproperty(name, str)
  2. Integer = lambda name: typedproperty(name, int)
  3. Float = lambda name: typedproperty(name, float)

现在,请重新编写 Stock 类以使用以下函数:

  1. class Stock:
  2. name = String('name')
  3. shares = Integer('shares')
  4. price = Float('price')
  5. def __init__(self, name, shares, price):
  6. self.name = name
  7. self.shares = shares
  8. self.price = price

啊,好一点了。这里的要点是:闭包和 lambda 常用于简化代码,并消除令人讨厌的代码重复。这通常很不错。

练习 7.9:付诸实践

请重新编写 stock.py 文件中的 Stock 类,以便使用上面展示的类型化特性(typed properties)。

目录 | 上一节 (7.2 匿名函数) | 下一节 (7.4 装饰器)