翻译:《实用的Python编程》07_03_Returning_functions

本文深入探讨Python中的闭包概念,展示了如何使用闭包创建可避免重复代码的函数。通过实例解释了闭包在回调、延迟计算和装饰器中的应用。此外,还介绍了如何使用闭包简化属性类型检查,创建typedproperty函数,减少代码冗余。最后,通过练习展示了如何在实际项目中应用闭包和类型检查特性。

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

7.3 返回函数

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

简介

考虑以下函数:

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

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

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

局部变量

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

def add(x, y):
    def do_add():
        # `x` and `y` are defined above `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      # Where are these values coming from?
7

闭包

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

def add(x, y):
    # `do_add` is a closure
    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 (延迟30 秒后)执行给定的函数…

闭包附带了其它信息。

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` has the references x -> 2 and 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'
... should get a 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 常用于简化代码,并消除令人讨厌的代码重复。这通常很不错。

练习 7.9:付诸实践

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

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

注:完整翻译见 https://github.com/codists/practical-python-zh

### 解决方案 编译器发出警告 `C4013` 表明函数 `SIM_HARDKEY_SetCallback` 被调用但未声明或定义。此问题通常发生在头文件中缺少函数原型声明或者链接阶段缺失实现的情况下。 #### 函数声明与定义 为了消除此类警告,需确保以下两点: 1. **函数声明**:在调用之前,在适当的位置(通常是 `.h` 文件)提供函数的正确定义或前向声明[^1]。 2. **函数定义**:确认源代码中有实际的函数体实现,并且该实现被正确编入项目构建流程中[^2]。 以下是具体的解决方案: #### 方法一:添加函数声明 如果仅存在函数调用而无任何声明,则应在相关头文件中加入如下形式的声明: ```c void SIM_HARDKEY_SetCallback(void (*callback)(void)); ``` 上述语句表示接受一个指向回调函数指针作为参数并返回 void 类型的方法签名。注意调整为匹配目标平台的实际需求以及遵循既定编码标准。 #### 方法二:验证函数实现 检查整个工程目录下是否存在名为 `SIM_HARDKEY_SetCallback` 的具体实现版本。如果没有找到对应的 .c 或其他支持的语言扩展名下的实现部分,则需要补充完整的逻辑处理代码片段。例如: ```c #include "sim_hardkey.h" // 假设 callback 是用户自定义的一个函数指针变量 static void (*callback)(void); void SIM_HARDKEY_SetCallback(void (*new_callback)(void)) { if (new_callback != NULL) { callback = new_callback; } } ``` 以上实例展示了如何设置全局静态成员来存储外部传入的回调接口地址。 #### 方法三:配置预处理器宏 有时由于条件编译的原因可能导致某些模块未能参与最终组合过程从而引发类似的错误提示。因此建议审查 Makefile 或 IDE 设置里有关于特定区域激活与否的关键字选项是否恰当设定好。 通过执行这些修正措施可以有效避免因早期绑定失败而导致的形式化告警信息输出情况发生。 ```python # Python 示例无关于此情境,仅供演示Markdown格式保留. def example_function(): pass ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值