美文网首页
Effective Python(15): 了解如何在闭包里使用

Effective Python(15): 了解如何在闭包里使用

作者: warmsirius | 来源:发表于2019-10-22 23:43 被阅读0次

一、举例

题目:
假如有一份列表,其中的额元素都是数字,现在要对其排序,但排序时候,要把出现在某个群组内的数字,放在群组外的那些数字之前。

用途

  • 将某些重要的消息或意外事件优先显示在其他内容前面。

实现
在调动列表的sort方法时,把辅助函数传给key参数。这个辅助参数的返回值,将会确定列表中各个元素的顺序。

def sort_priority(values, group):
    def helper(x):
        if x in group:
            return (0, x)
        return (1, x)

    values.sort(key=helper)


numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
sort_priority(numbers, group)
print(numbers)

# 输出
[2, 3, 5, 7, 1, 4, 6, 8]

实现的原因:

  • 1. Python支持闭包

闭包是一种定义在某个作用域的函数,这种函数引用了那个作用域里面的变量。

helper函数之所以能够访问sort_prioritygroup参数,原因在于它就是闭包

  • 2. Python的函数是一级对象

可以直接引用函数,把函数赋给变量、把函数当成参数传递给其他函数,并通过表达式及if语句对其进行比较和判断,等等。

  • 3. Python使用特殊的规则来比较两个元组

它首先比较元组中哥下表为0的对应元素,如果相等,再比较下标为1的对应元素。如果还是相等,那么就继续比较下标为2的对应元素,以此类推。

二、改进需求

需求
sort_priority函数改进一下,应该返回一个值,用来表示用户界面里是否出现了优先级较高的元件,使得该函数的调用者,可以根据这个返回值做出相应的处理。

实现(wrong)*

def sort_priority2(numbers, group):
    found = False
    def helper(x):
        if x in group:
            found = True
            return (0, x)
        return (1, x)
    numbers.sort(key=helper)
    return found


numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
found = sort_priority2(numbers, group)
print("Found:", found)

输出如下:

Found: False

排序结果是对的,但是found值不对。

问题:
numbers里面的某些数字确实包含在group中,可是函数却返回了False,这是为什么呢?

在表达式引用变量时,Python解释器将按如下顺序遍历各作用域,以解析该引用:

    1. 当前函数作用域
    1. 任何外围作用域(例如,包含当前函数的其他函数)
    1. 包含当前代码的那个模块的作用域(全局作用域)
    1. 内置作用域(包含lenstr等函数的那个作用域)
      如果上面这些地方都没有定义过名称相符的变量,那就抛出NameError异常

给变量赋值的规则:

    1. 如果当前作用域已经定义了这个变量,那么该变量就会具备新值
    1. 如果当前作用域没有这个变量,则会把这次赋值视为对该变量的定义,而新定义的这个变量,其作用域就是包含赋值操作的这个函数。

解释
上面所说的这种赋值行为,可以解释sort_priority2函数的返回值错误的原因:

found变量定义为True,是在helper闭包里进行的。于是,比保重的这次赋值操作,就相当于在helper内定义了名为found的新变量,而不是给sort_priority2中的那个found赋值。

def sort_priority2(numbers, group):
    found = False  # Scope: 'sort_priority2'
    def helper(x):
        if x in group:
            found = True  # Scope: 'helper' 
            return (0, x)
        return (1, x)
    numbers.sort(key=helper)
    return found

这种问题,有时称为作用域bug

其实,Python语言是故意要这么设计的。这样做可以防止函数中的局部变量污染函数外面的那个模块。

三、获取闭包内的数据

Python3中有一种特殊的写法,能够获取闭包内的数据。我们可以用nonlocal语句来表明这样的意图。

  • nonlocal作用:即给相关变量赋值的时候,应该再上层作用域中查找该变量。
  • nonlocal的唯一限制:不能延伸到模块级别,这是为了防止它污染全局作用域。
def sort_priority3(numbers, group):
    found = False
    def helper(x):
        nonlocal found
        if x in group:
            found = True
            return (0, x)
        return (1, x)
    numbers.sort(key=helper)
    return found


numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
found = sort_priority3(numbers, group)
print("Found:", found)

输出结果如下:

Found: True

nonlocal语句:如果在闭包内给该变量赋值,那么修改的其实是闭包外那个作用域中的变量。
global语句:对该变量的赋值操作,将会直接修改模块作用域里的那个变量

四、建议

  1. nonlocal也会像全局变量那样,遭到滥用,所以,建议大家只在极其简单的函数里使用这种机制。

  2. nonlocal的副作用很难追踪,尤其在比较长的函数中,修饰某变量的nonlocal语句可能和修改该变量的赋值操作离得比较远,从而导致代码更加难以理解。

如果使用nonlocal的代码,已经写得越来越复杂,那就应该将相关的状态封装成辅助类。

下面定义的这个类,与nonlocal所达成的功能相同。它虽然有点长,但是理解起来相当容易:

class Sorter(object):
    def __init__(self, group):
        self.group = group
        self.found = False

    def __call__(self, x):
        if x in self.group:
            self.found = True
            return (0, x)
        return (1, x)


sorter = Sorter(group)
numbers.sort(key=sorter)
assert sorter.found == True

五、Python2中的值

不幸的是,Python2中不支持nonlocal关键字。

为此,我们需要利用Python的作用域规则来解决。虽然这种做法不太优雅,但是已经成了一种编码习惯

# Python2
def sort_priority(numbers, group):
    found = [False]
    def helper(x):
        nonlocal found
        if x in group:
            found[0] = True
            return (0, x)
        return (1, x)
    numbers.sort(key=helper)
    return found

运行上面这段代码时,Python要解析found变量的当前值,于是,它会按照刚才所讲的变量搜寻选择,在上级作用域中查找这个变量。

上级作用域中的found变量是个列表,由于列表本身是可供修改的,所以获取到这个found的列表后,我们就可以在比闭包通过found[0]=True语句,来修改found的状态。

六、总结

  • 对于定义在某作用域内的闭包来说,它可以引用这些作用域中的变量
  • 使用默认方式对闭包内的变量赋值,不会影响外围作用域中的同名变量
  • 在Python3中,程序可以在比包内用nonlocal语句来修饰整个名称,使该闭包能够修改外围作用域中的同名变量
  • 在Python2中,程序可以使用可变值(例如列表、字典)来实现与nonlocal语句相仿的机制
  • 除了比较简单的函数,尽量不要使用nonlocal语句

相关文章

  • Effective Python(15): 了解如何在闭包里使用

    一、举例 题目:假如有一份列表,其中的额元素都是数字,现在要对其排序,但排序时候,要把出现在某个群组内的数字,放在...

  • 函数(2)--闭包

    第15条:了解如何在闭包里使用外围作用域中的变量 闭包在 python 中不是很好理解的内容,还是通过简单的例子逐...

  • python闭包学习

    参考文章 python闭包python闭包一步一步教你认识Python闭包深入浅出python闭包

  • python函数之闭包

    目录 python函数之闭包什么是闭包python中的namespace闭包的条件闭包的优点 python函数之闭...

  • 闭包

    浅谈 python 的闭包思想 首先 python的闭包使用方法是:在方法A内添加方法B,然后return 方法B...

  • Python的闭包

    # 闭包是函数编程的语法结构,简化程序,可重复使用行更加强 # python中的闭包从表现形式上定义(解释)为:如...

  • Python装饰器与闭包!

    闭包是Python装饰器的基础。要理解闭包,先要了解Python中的变量作用域规则。 变量作用域规则 首先,在函数...

  • 15、python 闭包

    Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西。 闭包...

  • Python 入门之 闭包

    Python 入门之 闭包 1、闭包 (1)在嵌套函数内使用(非本层变量)和非全局变量就是闭包 (2)_ clos...

  • Swift 闭包里 self 总是为nil

    全局闭包 或者 逃逸闭包 里嵌套闭包的时候 不要在嵌套的闭包里使用上一个闭包里的局部变量 会导致weakself ...

网友评论

      本文标题:Effective Python(15): 了解如何在闭包里使用

      本文链接:https://www.haomeiwen.com/subject/gjremctx.html