装饰器
函数装饰器
装饰器就是在不改动原有函数的前提下,动态修改函数的功能,然后返回一个新函数。
py
def my_decorator(func):
def wrapper():
print("在原函数之前执行")
func()
print("在原函数之后执行")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello() # 在原函数之前执行 # Hello! # 在原函数之后执行
装饰器函数也可以接收到原函数的参数:
py
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"在原函数之前执行, {args}")
func(*args, **kwargs)
print(f"在原函数之后执行, {args}")
return wrapper
@my_decorator
def greet(name):
print(f"Hello, {name}!") greet("Alice")
# 在原函数之前执行, ('Alice',) # Hello, Alice! # 在原函数之后执行, ('Alice',)
注意:装饰器不是继承!装饰器是在不改变原始函数的前提下,通过对原始函数的包装来扩展它的功能。而继承是子类在扩展父类。
类装饰器
类装饰器的效果和函数装饰器大致相同,接收一个类作为参数,返回一个修改过的新类。
py
def log_class(cls):
"""类装饰器,在调用方法前后打印日志"""
class Wrapper:
def __init__(self, *args, **kwargs):
self.wrapped = cls(*args, **kwargs)
# 实例化原始类
def __getattr__(self, name):
"""拦截未定义的属性访问,转发给原始类"""
return getattr(self.wrapped, name)
def display(self):
print(f"调用 {cls.__name__}.display() 前")
self.wrapped.display()
print(f"调用 {cls.__name__}.display() 后")
return Wrapper
# 返回包装后的类
@log_class
class MyClass:
def display(self):
print("这是 MyClass 的 display 方法")
obj = MyClass()
obj.display()
# 调用 MyClass.display() 前
# 这是 MyClass 的 display 方法
# 调用 MyClass.display() 后
内置装饰器
- @staticmethod: 将方法定义为静态方法,不需要实例化类即可调用。
- @classmethod: 将方法定义为类方法,第一个参数是类本身(通常命名为 cls)。
- @property: 将方法转换为属性,使其可以像属性一样访问。
py
class MyClass:
@staticmethod
def static_method():
print("这是一个静态方法!")
@classmethod
def class_method(cls):
print(f"这是一个类方法 {cls.__name__}.")
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
# 使用
MyClass.static_method()
MyClass.class_method()
obj = MyClass()
obj.name = "Alice"
print(obj.name)
多个装饰器堆叠
装饰器可以通过堆叠的方式对函数进行无限扩展,多个装饰器存在的时候会按照从上到下的顺序依次应用。
py
def decorator1(func):
def wrapper():
print("Decorator 1")
func()
return wrapper
def decorator2(func):
def wrapper():
print("Decorator 2")
func()
return wrapper
@decorator1
@decorator2
def say_hello():
print("Hello!")
say_hello()
引用
Python使用 import 关键字对其他 py 模块进行引用。
py
import supper supper.test('123')
注意:在Python中无论文件声明了多少次 import ,只要是相同的文件,则当前文件只会被导入一次!
Python 导入文件的查找路径为:当前脚本目录 → PYTHONPATH → 标准库 → 第三方包(site-packages)
这种导入方案带来的一个问题是项目中不能存在相同名称的模块,否则会存在冲突!
所以永远不要给模块/代码库起相同的名字。
部分引用
Python 支持部分导入的模式,使用 form...import 的形式。
py
from fibo import fib, fib2 fib(500) # 1 1 2 3 5 8 13 21 34 55 89 144 233 377
还可以通过使用 as 关键字对导入的函数重命名。
py
import numpy as np # 将 numpy 模块别名设置为 np from math import sqrt as square_root # 将 sqrt 函数别名设置为 square_root
全量引用
可以使用 * 字符将所有的函数导入进来。
py
from modname import *
注意:不建议使用此方式,可能会导致命名冲突。
__name__ 属性
使用引入的方式开发某些功能时,我们可能希望在原始函数中执行某个功能,在引入的代码中屏蔽这个功能。
可以使用 __name__ 属性,当在原始函数中执行的时候 __name__ == '__main__'。
py
if __name__ == '__main__':
print('程序自身在运行')
else:
print('我来自另一模块')
注意 :__name__ 属性始终存在,在原始函数中叫 __main__,在引入函数中为模块名。
dir方法
Python 存在一个内置方法 dir() ,可以获取到模块内定义的所有名称。
py
import fibo, sys
dir(fibo)
# ['__name__', 'fib', 'fib2']
dir(sys)
"""
['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__', '__package__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe', '_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount', 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions']
"""