一、命名空间
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
def func():
b = 2
print(__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__)
在这段代码中 a , b , c , __name__ 分别处于 全局作用域 、闭合作用域 、局部作用域 、内建作用域 。
一个函数内如果还定义有函数,则当前函数为闭合作用域,否则为局部作用域
作用域有四种:
- 局部作用域 Local:最内层的函数 (def 或 lambda)。
- 闭合作用域 Enclosing:如果 A 函数内定义了一个 B 函数,那么相对 B 函数内的变量 A 函数内的变量就处于闭合作用域。
- 全局作用域 Global:最外部定义的变量
- 内建作用域 Built-in:内建模块内定义的函数与关键字
python 寻找一个变量的顺序是:LEGB,也就是 局部→闭合→全局→内建。
2.2、global 和 nonlocal关键字
如果我们想在函数中修改全局作用域中的值,就只会在局部作用域中生成一个新的命名。
这会就需要global 关键字,它使得我们可以修改全局作用域内的变量。
a = 1
def func():
global a
print('修改前的 a:', a)
a = 2
print('修改后的 a:', a)
func()
print('最终的 a:', a)
修改前的 a: 1
修改后的 a: 2
最终的 a: 2
nonlocal 关键字同理,它允许我们修改闭合作用域内的变量。
a = 1
def func():
a = 2
def func_2():
nonlocal 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 = 2
def func_2():
global a
a = 2
def func_3():
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__)
print(result.__closure__[0].cell_contents)
(<cell at 0x00000201DE3664F0: str object at 0x00000201DE397A50>,)
小明
3.2、闭包的应用场景
使用闭包主要有以下三个好处:
- 关联数据与函数
- 保存临时变量,减少全局变量
- 保存私有变量,防止变量被修改
3.2.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))
def timer(base):
def wrapper(x):
return x * base
return wrapper
timer2 = timer(2)
print('结果:', timer2(4))
结果: 8
结果: 8
3.2.2、保存临时变量
count = 0
def func():
global count
count += 1
print(f'执行了{count}次')
func()
func()
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_supe r 相当于执行 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、内置装饰器
有三种我们经常会用到的装饰器, property 、 staticmethod 、 classmethod ,他们有个共同点,都是作用于类方法之上。
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秒。
|