Python装饰器

装饰函数

Python装饰器类似与Rust的过程宏(Procedural Macro),用于动态生成代码。其基础语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def decorator_function(original_function):
def wrapper(*args, **kwargs):
# 这里是在调用原始函数前添加的新功能
before_call_code()

result = original_function(*args, **kwargs)

# 这里是在调用原始函数后添加的新功能
after_call_code()

return result
return wrapper

# 使用装饰器
@decorator_function
def target_function(arg1, arg2):
pass # 原始函数的实现

装饰器本身也可以接受参数,此时需要再额外定义一个函数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
func(*args, **kwargs)
return wrapper
return decorator

@repeat(3)
def say_hello():
print("Hello!")

say_hello()

装饰类

装饰器还可以用于生成类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()

内置装饰器

  • @staticmethod定义类的静态方法
  • @classmethod类方法,类似与静态方法,第一个参数是类本身(通常命名为 <font style="color:rgb(51, 51, 51);background-color:rgb(250, 252, 253);">cls</font>,常用于实现构造器相关用法,实现单例模式)
  • <font style="color:rgb(51, 51, 51);background-color:rgb(250, 252, 253);">@property</font>将方法转换为属性,使其可以像属性一样访问
  • <font style="color:rgb(51, 51, 51);background-color:rgb(250, 252, 253);">@$attr.getter / @$attr.setter</font>类属性的 getter 和 setter

python 中所有的类都继承自 object 类,不管你是是否显示标记出 class MyClass(object)

Python object 类的方法详解

在 Python 中,object 是所有类的基类,它提供了一系列内置方法作为所有对象的默认实现。理解这些方法对于掌握 Python 的面向对象编程至关重要。

object 类的主要方法

对象表示方法

这些方法控制对象的字符串表示形式:

  • __str__(self)
    返回对象的"非正式"字符串表示(面向用户)
    print(obj)str(obj) 时调用
1
2
3
4
5
6
class Person:
def __str__(self):
return "Person Object"

p = Person()
print(p) # 输出: Person Object
  • __repr__(self)
    返回对象的"正式"字符串表示(面向开发者)
    在 REPL 或 repr(obj) 时调用
1
2
3
4
5
6
class Person:
def __repr__(self):
return "<Person instance>"

p = Person()
repr(p) # 返回: <Person instance>
比较方法

实现对象比较操作:

  • __eq__(self, other)
    定义 == 操作符行为
  • __ne__(self, other)
    定义 != 操作符行为(默认使用 __eq__ 的否定)
  • __lt__(self, other)
    定义 < 操作符行为(小于)
  • __le__(self, other)
    定义 <= 操作符行为(小于等于)
  • __gt__(self, other)
    定义 > 操作符行为(大于)
  • __ge__(self, other)
    定义 >= 操作符行为(大于等于)
哈希与布尔值
  • __hash__(self)
    返回对象的哈希值(用于字典键、集合成员)
1
2
3
4
5
6
7
8
9
class Book:
def __init__(self, isbn):
self.isbn = isbn

def __hash__(self):
return hash(self.isbn)

book = Book("978-0134757599")
hash(book) # 返回基于 ISBN 的哈希值
  • __bool__(self)
    定义对象的布尔值(bool(obj)if obj 时调用)
1
2
3
4
5
6
7
8
9
10
class Account:
def __init__(self, balance):
self.balance = balance

def __bool__(self):
return self.balance > 0

acc = Account(100)
if acc: # 调用 __bool__
print("Account has funds")
属性访问方法
  • __getattribute__(self, name)
    属性访问拦截器(每次属性访问都调用)
1
2
3
4
class LoggedAccess:
def __getattribute__(self, name):
print(f"Accessing: {name}")
return super().__getattribute__(name)
  • __getattr__(self, name)
    当属性不存在时调用
1
2
3
class DynamicAttributes:
def __getattr__(self, name):
return f"Property {name} doesn't exist"
  • __setattr__(self, name, value)
    设置属性时调用
1
2
3
4
5
class ValidatedSet:
def __setattr__(self, name, value):
if name == "age" and value < 0:
raise ValueError("Age cannot be negative")
super().__setattr__(name, value)
  • __delattr__(self, name)
    删除属性时调用
类创建方法
  • __init_subclass__(cls)
    当子类被创建时调用(类方法)
1
2
3
4
5
6
7
class Base:
def __init_subclass__(cls, **kwargs):
print(f"Subclass created: {cls.__name__}")
cls.registry = []

class Child(Base): # 输出: Subclass created: Child
pass
其他重要方法
  • __dir__(self)
    返回对象的属性列表(dir(obj) 时调用)
1
2
3
4
5
6
class CustomDir:
def __dir__(self):
return ['name', 'age', 'city']

obj = CustomDir()
dir(obj) # 返回: ['age', 'city', 'name']
  • __sizeof__(self)
    返回对象在内存中的大小(字节)
1
2
import sys
sys.getsizeof(object()) # 返回对象大小
  • __format__(self, format_spec)
    定义格式化输出(format(obj) 时调用)
1
2
3
4
5
6
7
8
9
10
11
12
class Temperature:
def __init__(self, celsius):
self.celsius = celsius

def __format__(self, spec):
if spec == 'f':
return f"{(self.celsius * 9/5) + 32:.2f}°F"
return f"{self.celsius}°C"

t = Temperature(25)
print(f"{t}") # 25°C
print(f"{t:f}") # 77.00°F

方法使用场景总结

方法类别 主要方法 使用场景
对象表示 __str__, __repr__ 打印对象、调试输出
比较操作 __eq__, __lt__ 对象比较、排序
类型转换 __bool__, __hash__ 布尔上下文、字典键
属性管理 __getattr__, __setattr__ 动态属性、属性验证
类构造 __init_subclass__ 类注册、元编程
内省/反射 __dir__, __class__ 检查对象属性、类型

私有方法

类的私有方法和私有属性使用 __两个下划线开头

受保护类型的方法和私有属性使用_单个下划线开头(“受保护”意味着只有自身和子类可以访问)

专有方法

构造和析构函数:

  • init : 构造函数,在生成对象时调用
  • del : 析构函数,释放对象时使用

当自定义的类是类似于数据结构的类型:

  • setitem : 按照索引赋值
  • getitem: 按照索引获取值

重载运算符:

  • len: 获得长度
  • cmp: 比较运算
  • call: 函数调用
  • add: 加运算
  • sub: 减运算
  • mul: 乘运算
  • truediv: 除运算
  • mod: 求余运算
  • pow: 乘方

项目管理

Python中一个独立的.py文件就是一个单独的模块(module),而一个单独的文件夹就是一个包(package)。

1
2
3
4
my_package/        # 包名 = 目录名
├── __init__.py
├── module1.py # 模块:my_package.module1
└── module2.py # 模块:my_package.module2

模块

一个模块可以包含多个函数,多个类,并且在其他文件中,可以通过_module_._function_/_module_._class_来导入。(或者是使用from _module_ import _function/class_

每一个模块都有一个 __name__的属性——如果该文件作为模块使用,则__name__的值就是这个文件的文件名;如果直接运行该文件,则__name__"__main__"

1
2
if __name__ == "__main__": # 检测是否为主模块
main()

一个包(package)可以包括多个模块,多个子包(sub-package),以及一个**__init__.py****文件。**任何包含__init__.py的文件夹都会被视作一个python的包,而没有这个文件的文件夹就只是一个普通的目录。

  • __init__.py用与导入包时候的初始化,其中的代码会在包导入的时候执行,通常用于初始化包级变量、设置包的环境、执行启动代码…
  • __init__.py中可以设置一个特殊的变量__all__,例如__all__ = ["module1", "helper"] ,所有__all__变量中包含的模块、函数、类都可以使用 from package import *一次性导入。例如:
1
2
3
4
5
6
7
8
9
# math_tools/
# ├── __init__.py
# ├── basic.py # 包含 add(), subtract()
# └── advanced.py # 包含 sqrt(), log()

# math_tools/__init__.py
from .basic import add, subtract
from .advanced import sqrt
__all__ = ["add", "subtract", "sqrt"]
  • 即使__init__.py是一个空文件,仍然标志着该文件夹是一个有效的包。Python 3.3+ 支持"命名空间包",允许没有 __init__.py 的包,但显式创建仍是推荐做法。

*args 和 **kwargs

*args是可变位置参数,用于接收任意数量的位置参数(positional arguments),并且把他们储存成一个元组

**kwargs是可变关键字参数,用于接收任意数量的关键字参数(keyword arguments),并且把他们储存成一个字典

1
2
3
4
5
6
7
8
9
10
11
def func(*args):
print("位置参数:", args)

func(1, 2, 3) # 输出:位置参数: (1, 2, 3)
func("a", "b") # 输出:位置参数: ('a', 'b')

def func(**kwargs):
print("关键字参数:", kwargs)

func(a=1, b=2) # 输出:关键字参数: {'a': 1, 'b': 2}
func(name="Alice") # 输出:关键字参数: {'name': 'Alice'}