Python 的面向对象体系里,有几个进阶机制经常让初学者感到困惑:运行时动态添加类成员、控制类创建过程的元类,以及用于错误传递的异常类。这三个主题分别体现了 Python 的动态性、底层实现和健壮性设计。本文结合实例代码,梳理它们的核心用法和适用场景。
一、动态添加类成员
Python 允许在运行时为类或实例动态添加属性与方法,这是其动态特性的直接体现。普通对象的属性赋值就是最简单的动态添加方式,例如给一个实例追加 age、city 等字段。
- class Person:
- def __init__(self, name: str):
- self.name = name
- p = Person("张三")
- p.age = 25
- p.city = "北京"
- print(p.name) # 张三
- print(p.age) # 25
复制代码
当属性名本身是变量时,可以使用 setattr() 实现动态设置,配合 getattr() 动态读取。这种方式在批量写入配置、解析外部数据时非常实用。
- class Student:
- pass
- s = Student()
- setattr(s, "name", "李四")
- setattr(s, "score", 95)
- print(getattr(s, "name")) # 李四
- print(getattr(s, "score")) # 95
- attrs = {"grade": 3, "major": "计算机科学", "gpa": 3.8}
- for key, value in attrs.items():
- setattr(s, key, value)
- print(s.major) # 计算机科学
复制代码
方法本质上也是类属性,因此可以把外部定义的函数直接赋给类,成为实例方法。
- class Calculator:
- pass
- def add(self, a, b):
- return a + b
- def multiply(self, a, b):
- return a * b
- Calculator.add = add
- Calculator.multiply = multiply
- calc = Calculator()
- print(calc.add(3, 5)) # 8
- print(calc.multiply(3, 5)) # 15
复制代码
如果只希望某个实例拥有特定方法,而不影响其他实例,可以用 types.MethodType 将函数绑定到具体实例上。
- import types
- class Person:
- def __init__(self, name: str):
- self.name = name
- def greet(self):
- return f"你好,我是 {self.name}"
- p1 = Person("张三")
- p2 = Person("李四")
- p1.greet = types.MethodType(greet, p1)
- print(p1.greet()) # 你好,我是 张三
- # print(p2.greet()) # AttributeError
复制代码
如果希望限制实例只能拥有固定属性,可以在类中定义 __slots__。这样既能节省内存,也能阻止外部随意动态添加属性。
- class Person:
- __slots__ = ["name", "age"]
- def __init__(self, name: str):
- self.name = name
- p = Person("张三")
- p.age = 25 # 允许
- # p.city = "北京" # AttributeError
复制代码
类属性和类方法同样可以动态添加。通过给类直接赋值,可以注入新的类属性;通过 @classmethod 装饰后赋值,可以为类动态增加类方法。
- class Config:
- pass
- Config.DEBUG = True
- Config.PORT = 8000
- @classmethod
- def get_config(cls):
- return {k: v for k, v in cls.__dict__.items() if not k.startswith("_")}
- Config.get_config = get_config
- print(Config.DEBUG) # True
- print(Config.get_config()) # {'DEBUG': True, 'PORT': 8000, ...}
复制代码
二、元类(Metaclass)
在 Python 中,类本身也是对象。既然类是对象,那么它必然由某个“类”创建,这个创建类的类就是元类。默认情况下,所有类的元类都是 type。
- class Person:
- pass
- print(type(Person)) # <class 'type'>
复制代码
type 有两个主要用途:一是调用 type(obj) 查看对象类型;二是以 type(name, bases, dict) 的形式动态创建类。后者非常适合在运行时按需生成类。
- MyClass = type("MyClass", (object,), {
- "x": 10,
- "hello": lambda self: "Hello"
- })
- obj = MyClass()
- print(obj.x) # 10
- print(obj.hello()) # Hello
复制代码
普通定义方式与 type 动态创建是等价的。例如下面的 Student 类,既可以用 class 语句声明,也可以用 type 函数构造。
- def __init__(self, name):
- self.name = name
- def info(self):
- return f"{self.name},北京大学"
- Student = type(
- "Student",
- (object,),
- {
- "school": "北京大学",
- "__init__": __init__,
- "info": info
- }
- )
- s = Student("张三")
- print(s.info()) # 张三,北京大学
复制代码
自定义元类需要继承 type,并重写 __new__ 或 __init__。__new__ 在类对象创建时触发,适合修改类属性、添加成员;__init__ 在类对象创建后触发,适合对类对象做进一步初始化。
一个典型应用是把类中的属性名统一转为大写:
- class UpperAttrMeta(type):
- def __new__(cls, name, bases, attrs):
- uppercase_attrs = {}
- for key, value in attrs.items():
- if not key.startswith("__"):
- uppercase_attrs[key.upper()] = value
- else:
- uppercase_attrs[key] = value
- return super().__new__(cls, name, bases, uppercase_attrs)
- class MyClass(metaclass=UpperAttrMeta):
- name = "张三"
- age = 25
- def greet(self):
- return "Hello"
- print(MyClass.NAME) # 张三
- print(MyClass.AGE) # 25
- # print(MyClass.name) # AttributeError
复制代码
再看 __new__ 与 __init__ 的触发顺序和用途。以下面这个元类为例,创建 Demo 类时,会先执行 __new__,再执行 __init__,并且可以在 __new__ 中向类的属性字典注入新的字段。
- class MyMeta(type):
- def __new__(cls, name, bases, attrs):
- print(f"__new__: 创建类 {name}")
- attrs["created_by"] = "MyMeta"
- return super().__new__(cls, name, bases, attrs)
- def __init__(cls, name, bases, attrs):
- print(f"__init__: 初始化类 {name}")
- super().__init__(name, bases, attrs)
- class Demo(metaclass=MyMeta):
- pass
- print(Demo.created_by) # MyMeta
复制代码
元类虽然强大,但在日常业务代码中并不常用。它更多出现在框架底层,比如 ORM 的模型定义、单例模式的实现、类注册表、属性自动改写等场景。
三、异常类(Exception)
异常类是 Python 错误处理的核心。程序运行出错时,会抛出异常对象,通过 try...except 捕获后,可以让程序继续执行而非直接崩溃。
Python 内置异常类具有清晰的继承层次:BaseException 是所有异常的基类,其下常见分支包括 SystemExit、KeyboardInterrupt、GeneratorExit,以及我们最常捕获的 Exception。日常开发中应该尽量捕获具体的异常子类,而不是裸用 except。
典型的数值输入与除法运算场景可以展示完整的异常捕获结构:
- while True:
- try:
- v1 = int(input("输入第一个数字,输入的信息要是数字格式"))
- v2 = int(input("输入第二个数字,输入的信息要是数字格式,不能输入0"))
- v3 = v1 / v2
- print(f"结果为{v3}")
- except ValueError as e:
- print(f"类型错误,{e}")
- except ZeroDivisionError as e:
- print(f"除数错误,{e}")
- except Exception as e:
- print(f"未知错误{e}")
- else:
- print(f"运算正常")
- finally:
- print(f"本轮运算结束")
复制代码
这里 ValueError 处理非数字输入,ZeroDivisionError 处理除数为零,Exception 兜底未知异常。else 分支在无异常时执行,finally 分支无论是否异常都会执行,适合释放资源或打印善后信息。
四、总结
这三个主题对应 Python 进阶的三个方向:动态添加成员让代码拥有更高的灵活性;元类把控制点提升到“类的创建过程”;异常类则构建了稳健的错误处理链路。掌握它们,不仅有助于写出更灵活的代码,也能更好地理解 Django、SQLAlchemy 等框架的底层设计。在实际项目中,动态添加属性适合轻量数据扩展,自定义元类适合框架级抽象,异常类则应遵循“具体异常优先、兜底异常在后”的原则。 |