IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python作用域、闭包与装饰器 -> 正文阅读

[Python知识库]Python作用域、闭包与装饰器

一、命名空间

1.1、全局命名空间

命名空间为 namespace 的直译。解析如下:

num = 5
name = "xiaohao"

以上,我们简单地定义了两个变量,便等同于创建了两个名字与对象的对应关系,这种建立名字与对象映射关系的行为便是命名
字典就是一个名字与值对应的典型例子,这使得 python 中的命名空间通常用字典实现。

print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002F3C774FFD0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\86177\\Documents\\1.py', '__cached__': None, 'num': 5, 'name': 'xiaohao'}

调用globals方法便可返回 全局命名空间 ,打印结果可以看到全局命名空间是一个字典对象,同时包含了我们刚刚定义的两个变量。
包含 全局命名空间 在内有以下三种命名空间:

  • 局部命名空间 Local namespace:函数中定义的命名,包括函数的参数。
  • 全局命名空间 Global namespace:当前模块中的命名,与其他模块中 import 进来的命名。
  • 内置命名空间 Built-in namespace:Python 语言内置的命名,比如函数名 print、type 等等关键字。
a = 1 # a 处在全局命名空间

def func():
    b = 2 # b 处在局部命名空间

print(__name__) # __name__ 处在内建命名空间

1.2、内建命名空间

所谓的内建命名空间便是包括了 python 自带的一些变量与函数,比如 dict,list,type,print 等等。这些都是由 python 默认从 builtins 模块导入。
我们可以配合 dir 函数打印一下 __builtins__ 模块的内容,便可以看到内建命名空间中的内容。

print(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']

1.3、局部命名空间

程序当前执行的代码块内的命名都属于局部命名空间。
locals 函数可以看到当前局部命名空间中的内容。

a = 1

def func():
    b = 2
    print(locals())

func()
{'b': 2}

如果在最外层代码中调用 locals ,此时它与 glocals 输出的内容是等价的,因为当程序执行到最外层代码时,局部命名空间等同于全局命名空间。

a = 1

def func():
    b = 2
    print(locals())

func()

print(locals())
print(globals())
{'b': 2}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002203218FFD0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\86177\\Documents\\1.py', '__cached__': None, 'a': 1, 'func': <function func at 0x0000022032625820>}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002203218FFD0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\86177\\Documents\\1.py', '__cached__': None, 'a': 1, 'func': <function func at 0x0000022032625820>}

1.4、命名空间的作用

命名空间包含了从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。

命名空间可以将我们各部分的命名独立,避免了它们之间的冲突。你在命名一个变量时不再需要翻遍整个项目来防止命名冲突导致影响其他部分的代码。

a = 1

def func():
    a = 2
    print('局部命名空间的a:', a)

func()
print('全局命名空间的a:', a)
局部命名空间的a: 2
全局命名空间的a: 1

以上的两个 a 处于不同的命名空间,它们之间互不影响,在局部命名空间的命名不会影响到全局命名空间的命名。

二、作用域

2.1、定义

我们了解到命名空间是可以独立存在的,并且它们以一定的层次来构建,这使得我们能以一定范围引用我们的变量。

限定变量名字的可用性的区域就是这个名字的作用域,并且作用域会直接决定我们引用一个变量时查找区域的顺序。

a = 1  # 全局作用域

def func():
    b = 2  # 闭合作用域

    def func_2():
        c = 3  # 局部作用域

print(__name__)  # __name__ 处于内建作用域

在这段代码中 a, b, c, __name__ 分别处于 全局作用域闭合作用域局部作用域内建作用域

一个函数内如果还定义有函数,则当前函数为闭合作用域,否则为局部作用域

作用域有四种:

  • 局部作用域 Local:最内层的函数 (def 或 lambda)。
  • 闭合作用域 Enclosing:如果 A 函数内定义了一个 B 函数,那么相对 B 函数内的变量 A 函数内的变量就处于闭合作用域。
  • 全局作用域 Global:最外部定义的变量
  • 内建作用域 Built-in:内建模块内定义的函数与关键字

python 寻找一个变量的顺序是:LEGB,也就是 局部→闭合→全局→内建。
在这里插入图片描述

2.2、global 和 nonlocal关键字

如果我们想在函数中修改全局作用域中的值,就只会在局部作用域中生成一个新的命名。

这会就需要global 关键字,它使得我们可以修改全局作用域内的变量。

a = 1 # 全局作用域的 a

def func():
    global a # 引入了全局作用域的 a 变量
    print('修改前的 a:', a)
    a = 2
    print('修改后的 a:', a)

func()
print('最终的 a:', a)
修改前的 a: 1
修改后的 a: 2
最终的 a: 2

nonlocal 关键字同理,它允许我们修改闭合作用域内的变量。

a = 1  # 全局作用域的 a

def func():
    a = 2  # 闭合作用域的 a 变量

    def func_2():
        nonlocal a  # 引入了闭合作用域的 a 变量
        print('修改前的 a:', a)
        a = 3

    func_2()
    print('闭合作用域的 a:', a)

func()
print('全局作用域的 a:', a)
修改前的 a: 2
闭合作用域的 a: 3       
全局作用域的 a: 1

注意:

# 全局作用域
a = 1

def func_1():
    # 在局部作用域定义 a 变量
    a = 2

def func_2():
    # 引入全局作用域的 a 变量并重新赋值
    global a
    a = 2

def func_3():
    # 尝试对局部作用域的 a 变量进行自增操作
    # 由于此时 a 变量未定义则报错
    a += 1

func_1()
func_2()
func_3()
Traceback (most recent call last):
  File "C:\Users\86177\Documents\1.py", line 20, in <module>
    func_3()
  File "C:\Users\86177\Documents\1.py", line 16, in func_3
    a += 1
UnboundLocalError: local variable 'a' referenced before assignment

python 会将在 func_3 的 a += 1 解释为尝试对局部作用域的 a 变量进行自增操作,由于此时 a 变量未定义会导致报错。

2.3、命名空间与作用域的区别与联系

  • 命名空间是一种实际的代码实现,而作用域是程序设计上的一种规定。
  • 一个变量处在哪个命名空间则决定了它的作用域,而它的作用域则表示了它的作用范围与被引用时所查找的顺序优先级。

三、闭包

3.1、简介

在 python 里万物皆对象,函数也不例外,于是乎我们可以在一个函数中定义另一个函数作为返回值。

def func(name):
    def wrapper():
        print(f'你好,{name}')

    return wrapper

result = func('小明')
result()
你好小明

闭包: f 函数中定义了 g 函数, g 还引用了 f 定义的变量, 就是闭包。
由作用域的查找规则可知,在这段代码中,func 函数内是局部作用域, wrapper 函数内是闭合作用域, wrapper 函数可以直接使用 func 函数内的变量。

在实现闭包时,返回的函数会带有 __closure __属性,在__closure __里可以找到函数所需要的变量。

print(result.__closure__) # 打印一下 __closure__ 属性
print(result.__closure__[0].cell_contents) # 通过 cell_contents 可以得到保存的变量
(<cell at 0x00000201DE3664F0: str object at 0x00000201DE397A50>,)
小明

3.2、闭包的应用场景

使用闭包主要有以下三个好处:

  • 关联数据与函数
  • 保存临时变量,减少全局变量
  • 保存私有变量,防止变量被修改

3.2.1、关联数据与函数

# 写法 1:用类实现
class Timer:
    def __init__(self, base):
        self.base = base

    def time(self, x):
        return self.base * x

timer2 = Timer(2)
print('结果:', timer2.time(4))

# 写法 2:用闭包实现
def timer(base):
    def wrapper(x):
        return x * base

    return wrapper

timer2 = timer(2)
print('结果:', timer2(4))
结果: 8
结果: 8

3.2.2、保存临时变量

 # 写法 1:将变量定义在全局作用域
count = 0

def func():
    global count
    count += 1
    print(f'执行了{count}次')

func()
func()

# 写法 2:将变量定义在闭包中的局部作用域
def counter():
    count = 0

    def wrapper():
        nonlocal count
        count += 1
        print(f'执行了{count}次')

    return wrapper

func = counter()
func()
func()
执行了1次
执行了2次
执行了1次
执行了2

3.2.3、保存私有变量

def my_dict(**kwargs):
    def wrapper():
        # 这里是重新生成了一个字典对象,
        # 因为如果直接返回则属于浅拷贝,
        # 会导致外部仍能修改字典的值。
        return {**kwargs}

    return wrapper

xiaoming = my_dict(name='小明', age=18)

print('修改前:', xiaoming())
xiaoming()['name'] = '小红'
print('修改后:', xiaoming())
修改前: {'name': '小明', 'age': 18}
修改后: {'name': '小明', 'age': 18}

四、装饰器

4.1、函数装饰器

在闭包中得知,函数是一个对象。

  • 能在函数中定义一个函数
  • 能作为参数传递
  • 能作为返回值
def decorator(func):
    def wrapper(*args, **kwargs):
        print('123')
        return func(*args, **kwargs)

    return wrapper

def say_hello():
    print('同学你好')

say_hello_super = decorator(say_hello)
say_hello_super()
123
同学你好

在这段代码中,我们将一个函数 say_hello 作为参数传入函数 decorator,返回一个wrapper 函数并赋值到 say_hello_super,此时执行 say_hello_super 相当于执行 wrapper 函数。当我们执行 wrapper 函数时会先打印123再执行先前传入的 func 参数也就是 say_hello 函数。

装饰器写法:

def decorator(func):
    def wrapper(*args, **kwargs):
        print('123')
        return func(*args, **kwargs)

    return wrapper

@decorator
def say_hello():
    print('同学你好')

say_hello()

这里在函数前加上 @decorator 相当于在定义函数后执行了一条语句, say_hello = decorator(say_hello) 。

4.2、带参数的装饰器

def info(value):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(value)
            return func(*args, **kwargs)

        return wrapper

    return decorator

@info('456')
def say_hello():
    print('同学你好')

say_hello()
456
同学你好

我们可以在装饰器外部再套上一层函数,用该函数的参数接收我们想要打印的数据,并将先前的 decorator 函数作为返回值。这就是之前学到的闭包的一种功能,就是用闭包来生成一个命名空间,在命名空间中保存我们要打印的值 value

4.3、wraps 装饰器

一个函数不止有他的执行语句,还有着 __name__(函数名),__doc__(说明文档)等属性,我们之前的例子会导致这些属性改变。

def decorator(func):
    def wrapper(*args, **kwargs):
        """doc of wrapper"""
        print('123')
        return func(*args, **kwargs)

    return wrapper

@decorator
def say_hello():
    """doc of say hello"""
    print('同学你好')

print(say_hello.__name__)
print(say_hello.__doc__)
wrapper
doc of wrapper

由于装饰器返回了 wrapper 函数替换掉了之前的 say_hello 函数,导致函数名,帮助文档变成了 wrapper 函数的了。

解决这一问题的办法是通过 functools 模块下的 wraps 装饰器。

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """doc of wrapper"""
        print('123')
        return func(*args, **kwargs)

    return wrapper

@decorator
def say_hello():
    """doc of say hello"""
    print('同学你好')

print(say_hello.__name__)
print(say_hello.__doc__)
say_hello
doc of say hello

4.4、内置装饰器

有三种我们经常会用到的装饰器, propertystaticmethodclassmethod,他们有个共同点,都是作用于类方法之上。

4.4.1、property 装饰器

property 装饰器用于类中的函数,使得我们可以像访问属性一样来获取一个函数的返回值。

class XiaoMing:
    first_name = '明'
    last_name = '小'

    @property
    def full_name(self):
        return self.last_name + self.first_name

xiaoming = XiaoMing()
print(xiaoming.full_name)
小明

我们像获取属性一样获取 full_name 方法的返回值,这就是用property 装饰器的意义,既能像属性一样获取值,又可以在获取值的时候做一些操作。

4.4.2、staticmethod 装饰器

staticmethod 装饰器同样是用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self参数,也无法访问实例化后的对象。

class XiaoMing:
    @staticmethod
    def say_hello():
        print('同学你好')

XiaoMing.say_hello()

# 实例化调用也是同样的效果
# 有点多此一举
xiaoming = XiaoMing()
xiaoming.say_hello()
同学你好
同学你好

4.4.3、classmethod 装饰器

classmethod 依旧是用于类中的方法,这表示这个方法将会是一个类方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。相对于 staticmethod 的区别在于它会接收一个指向类本身的 cls 参数

class XiaoMing:
    name = '小明'

    @classmethod
    def say_hello(cls):
        print('同学你好, 我是' + cls.name)
        print(cls)

XiaoMing.say_hello()
同学你好, 我是小明
<class '__main__.XiaoMing'>

4.5、类装饰器

类能实现装饰器的功能, 是由于当我们调用一个对象时,实际上调用的是它的 __call__ 方法。

class Demo:
    def __call__(self):
        print('我是 Demo')

demo = Demo()
demo()
我是 Demo

通过这个特性,我们便可以用类的方式来完成装饰器,功能与刚开始用函数实现的一致。

class Decorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('123')
        return self.func(*args, **kwargs)

@Decorator
def say_hello():
    print('同学你好')

say_hello()
123
同学你好

比如说我们有一些计算耗时很长的函数,并且每次计算的结果不变,那么我们就可以通过类定义一个缓存装饰器,来缓存第一次执行的结果。

import time

class Cache:
    __cache = {}

    def __init__(self, func):
        self.func = func

    def __call__(self):

        # 如果缓存字典中有这个方法的执行结果
        # 直接返回缓存的值
        if self.func.__name__ in Cache.__cache:
            return Cache.__cache[self.func.__name__]

        # 计算方法的执行结果
        value = self.func()
        # 将其添加到缓存
        Cache.__cache[self.func.__name__] = value
        # 返回计算结果
        return value

@Cache
def long_time_func():
    time.sleep(5)
    return '我是计算结果'

start = time.time()
print(long_time_func())
end = time.time()
print(f'计算耗时{end-start}秒')

start = time.time()
print(long_time_func())
end = time.time()
print(f'计算耗时{end-start}秒')
我是计算结果
计算耗时5.009094476699829秒
我是计算结果
计算耗时0.0

实现一个装饰器,能够计算函数的执行耗时。

import time

def calc(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f'{func.__name__}函数执行耗时{end-start}秒。')
        return result

    return wrapper

@calc
def count_2():
    for i in range(0, 2):
        print(f'第{i+1}次计数')
        time.sleep(1)  # 暂停一秒钟

@calc
def count_5():
    for i in range(0, 5):
        print(f'第{i+1}次计数')
        time.sleep(1)  # 暂停一秒钟

count_2()
count_5()
1次计数
第2次计数
count_2函数执行耗时2.0178287029266357秒。
第1次计数
第2次计数
第3次计数
第4次计数
第5次计数
count_5函数执行耗时5.0245983600616455秒。
  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-07-24 00:06:18  更:2021-07-24 00:06:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 13:58:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码