创建函数式函数

PythonPythonBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

本节介绍使用函数来创建其他函数的概念。

简介

考虑以下函数。

def add(x, y):
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add

这是一个返回另一个函数的函数。

>>> a = add(3,4)
>>> a
<function add.<locals>.do_add at 0x7f27d8a38790>
>>> a()
Adding 3 4
7

局部变量

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

def add(x, y):
    def do_add():
        ## `x` 和 `y` 在 `add(x, y)` 之上定义
        print('Adding', x, y)
        return x + y
    return do_add

进一步观察,在 add() 完成后,这些变量不知为何仍然存在。

>>> a = add(3,4)
>>> a
<function do_add at 0x6a670>
>>> a()
Adding 3 4      ## 这些值从哪里来?
7

闭包

当一个内部函数作为结果被返回时,该内部函数被称为闭包

def add(x, y):
    ## `do_add` 是一个闭包
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add

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

使用闭包

闭包是Python的一个重要特性。然而,它们的使用往往很微妙。常见应用包括:

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

延迟求值

考虑这样一个函数:

def after(seconds, func):
    import time
    time.sleep(seconds)
    func()

用法示例:

def greeting():
    print('Hello Guido')

after(30, greeting)

after 会在稍后执行提供的函数。

闭包会携带额外的信息。

def add(x, y):
    def do_add():
        print(f'Adding {x} + {y} -> {x+y}')
    return do_add

def after(seconds, func):
    import time
    time.sleep(seconds)
    func()

after(30, add(2, 3))
## `do_add` 具有对 x -> 2 和 y -> 3 的引用

代码重复

闭包还可以用作避免过度代码重复的一种技术。你可以编写生成代码的函数。

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

闭包更强大的特性之一是其在生成重复代码方面的应用。如果你回顾一下练习5.7,还记得用于定义带类型检查的属性的代码。

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
  ...
    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        if not isinstance(value, int):
            raise TypeError('Expected int')
        self._shares = value
  ...

你无需一遍又一遍地重复输入这段代码,而是可以使用闭包自动创建它。

创建一个名为typedproperty.py的文件,并将以下代码放入其中:

## typedproperty.py

def typedproperty(name, expected_type):
    private_name = '_' + name
    @property
    def prop(self):
        return getattr(self, private_name)

    @prop.setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError(f'Expected {expected_type}')
        setattr(self, private_name, value)

    return prop

现在,通过定义如下类来试用一下:

from typedproperty import typedproperty

class Stock:
    name = typedproperty('name', str)
    shares = typedproperty('shares', int)
    price = typedproperty('price', float)

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

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

>>> s = Stock('IBM', 50, 91.1)
>>> s.name
'IBM'
>>> s.shares = '100'
... 应该会得到一个TypeError...
>>>

练习7.8:简化函数调用

在上述示例中,用户可能会觉得像typedproperty('shares', int)这样的调用输入起来有点冗长 —— 尤其是如果它们被大量重复使用时。在typedproperty.py文件中添加以下定义:

String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)

现在,重写Stock类以使用这些函数:

class Stock:
    name = String('name')
    shares = Integer('shares')
    price = Float('price')

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

啊,这样好多了。这里的主要收获是闭包和lambda通常可以用来简化代码并消除烦人的重复。这通常是件好事。

总结

恭喜你!你已经完成了“返回函数”实验。你可以在LabEx中练习更多实验来提升你的技能。