Python 浅谈 python 作用域与闭包函数

打工人 · 2021年09月12日 · 1365 次阅读

作用域简介

在 python 程序中创建、改变、查找变量名时,都是在一个保存变量名的空间中进行,我们称之为命名空间,也被称之为作用域。
python 的作用域是静态的,在源代码中变量名被赋值的位置决定了该变量能被访问的范围。即 python 变量的作用域由变量所在源代码中的位置决定

常用开发语言对变量的使用过程

一般常用的开发语言在使用变量时(不同语言之间也会存在一些区别),大多分为以下四个过程:
1.变量声明:让编辑器知道有这么一个变量的存在
2.定义变量:为不同数据类型的变量分配内存空间
3.变量初始化:赋值,填充分配好的内存空间
4.引用变量:通过引用对象(变量名)来调用内存对象(内存数据)

python 对变量的使用过程

在 python 中,使用一个变量时并不一定要求需要预先声明它,但是在真正使用它之前,它必须被绑定在某个内存对象(被定义、赋值);这种变量名的绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量

python 作用域的产生

在 python 中并不是所有的语句块中都会产生作用域。只有当变量在 module、class、def 中定义的时候,才会有作用域的概念

def func():
     var = 100    # 局部作用域
    print(var)
func()          # 100
print(var)    # NameError:name ‘var’ is not defined

在作用域中定义的变量,一般只在作用域中有效。需要注意的是:在 if - elif - else、for - else 、while、try-except-finally 等关键字语句块中并不会产生作用域

python 的变量解析规则(LEGB)

搜索变量名的优先级:局部作用域(local)>嵌套作用域(enclosing)>全局作用域(global)>内置作用域(built-in)

LEGB 法则:

当在函数中使用未确定的变量名时,python 会按照优先级依次搜索 4 个作用域,以此来确定该变量名的意义。首先搜索局部作用域(L),之后是上一层嵌套结构中 def 或 lambda 函数的嵌套作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止,如果都没有找到,抛出 NameError 错误

  • 局部作用域(local):包含在 def 关键字定义的语句块中,即在函数中定义的变量。每当函数被调用时都会创建一个新的局部作用域。在函数内部的变量声明中,除非特别的声明为全局变量,否则默认为局部变量。局部作用域就像一个 栈,仅仅是暂时的存在,依赖创建该局部作用域的函数是否处于活动的状态。所以,一般建议尽量少定义全局变量,因为全局变量在模块文件运行的过程中会一直存在,占用内存空间
  • 嵌套作用域(enclosing):嵌套作用域也包含在 def 关键字中,嵌套作用域与局部作用域是相对的,嵌套作用域相对于更上层的函数而言也是局部作用域。与局部作用域的区别在于,对一个函数而言,局部作用域是定义在此函数内部的局部作用域,而嵌套作用域是定义在此函数的上一层父级函数的局部作用域。主要是为了实现 python 的闭包而增加的实现
  • 全局作用域(global):即在模块层次中定义的变量,每一个模块都是一个全局作用域。也就是说 在模块文件顶层声明的变量具有全局作用域,从外部看来,模块的全局变量就是一个模块对象的属性。注意:全局作用域的作用范围仅限于单个模块文件内
  • 内置作用域(built-in):系统内固定模块里定义的变量,内置作用域指的是一个名为 builtin 的内置模块
import builtins
dir(builtins)
输出
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
# python是允许创建嵌套函数的,也就是说我们可以在函数内部定义一个函数,这些函数都遵循各自的作用域和生命周期
var = 100      # 全局作用域
def func():
    var = 200    # 嵌套作用域
    print(var)
    def func2():
        var = 300      # 局部作用域
        print(var)

补充:python 解释器的运行过程:首先加载内存,再进行语法分析,之后再到编译,最后执行的过程

不同作用域变量的修改

必须强调的是,内层作用域对外层作用域的变量只有只读(read-only)的访问权限,需要从内存到外层作用域的变量进行修改必须使用特殊关键字

global 关键字
  • 全局变量是位于模块文件内部的顶层的变量名
  • 全局变量如果是在函数内被赋值的话,需要先声明
  • 全局变量名在函数的内部不经过声明也可以被引用
# 在局部作用域中修改全局作用域的变量
x = 100
def func():
    global x
    x += 1
    print(x)
func()          # 101
print(x)        # 101
nonlocal 关键字

nonlocal 关键字可以修改嵌套作用域的变量,nonlocal 关键字变量必须要在一个嵌套的 def 作用域中赋值过

# 在局部作用域中修改嵌套作用域的变量
def outer():
    count = 10      # 嵌套作用域
    def inner():
        nonlocal count
        count = 20      # 局部作用域
        print(count)
    inner()
    print(count)
outer()        # 20 ,  20

闭包函数

闭包函数介绍

简介:闭包函数返回的函数对象,不仅仅是一个函数对象,而是在该函数外包裹了一层作用域,使得该函数无论在何处被调用,都可以使用自己外层包裹的作用域
作用:函数封装,代码复用

闭包函数的应用

装饰器函数
就是用于扩展原函数功能的一种函数,目的是在不改变原函数功能的前提下,给函数增加新的功能

import time
from functools import wraps

def decorate(func):
    @wraps(func)
    def demo():
        start = time.time()
        func()
        end = time.time()
        print("消耗时长是%d" %(end - start))
    return demo

@decorate
def foo():
    time.sleep(3)
    print("running")
    return 'demo'

foo()
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册