前言
"类对象"这个说法经常被误用。有人用它泛指"类的实例",有人用它指"类本身"。本文取后者:类本身也是一个对象。这一句不是修辞——你可以把类赋值给变量、放进字典、当参数传、当返回值返回,就像操作任何普通对象一样。
另一个常见误解是"类只能靠class语法创建"。实际上class语句只是调用元类type的语法糖,你也可以直接用type()动态造类。
本文用一系列示例展示"类作为对象"的方方面面:类的元数据、动态创建、类作为一等公民传递、可调用对象,以及子类钩子。
一、类对象的元数据
每个类都带有一组属性,描述它自己叫什么、属于哪个模块、继承自谁。
# 适用于 Python 3.8+
class Animal:
pass
class Dog(Animal):
"""一只狗。"""
print(Dog.__name__) # Dog
print(Dog.__qualname__) # Dog
print(Dog.__module__) # __main__
print(Dog.__doc__) # 一只狗。
print(Dog.__bases__) # (<class '__main__.Animal'>,)
print(type(Dog)) # <class 'type'>
print(isinstance(Dog, type)) # True| 属性 | 含义 |
|---|
__name__ | 类名(短名) |
__qualname__ | 限定名,含外层路径 |
__module__ | 定义所在的模块名 |
__bases__ | 直接基类元组 |
__mro__ | 方法解析顺序元组 |
__dict__ | 类的命名空间 |
type(cls) | 元类,默认是type |
因为type(Dog)是type,所以"类"也是某个类的实例——它的实例所属类型就是元类。
二、用type()动态创建类
type有两个用法:单参数调用返回对象类型,三参数调用创建新类。三参数形式是type(类名, 基类元组, 命名空间字典)。
# 适用于 Python 3.8+
def greet(self):
return f"你好,{self.name}"
def make(self, name):
self.name = name
Person = type("Person", (object,), {
"__init__": make,
"greet": greet,
"species": "人",
})
p = Person("Ada")
print(p.greet()) # 你好,Ada
print(Person.__name__) # Person
print(p.species) # 人这说明了class语句的本质:它执行类体,收集出一个命名空间字典,然后交给元类构造类对象。理解这点,后面学元类就顺了。
三、类是一等公民
类可以像普通值一样被传递和存储,这让"注册表"和"工厂"模式变得非常自然。
# 适用于 Python 3.8+
class JsonParser:
name = "json"
def parse(self, text):
return f"解析 json: {text}"
class CsvParser:
name = "csv"
def parse(self, text):
return f"解析 csv: {text}"
registry = {cls.name: cls for cls in (JsonParser, CsvParser)}
def build(fmt):
cls = registry.get(fmt)
if cls is None:
raise ValueError(f"不支持的格式: {fmt}")
return cls() # 类对象可直接调用
print(build("csv").parse("a,b")) # 解析 csv: a,b这里registry里的值就是类对象本身,cls()通过调用类来创建实例。
# 适用于 Python 3.8+
def describe(cls):
return f"{cls.__name__} 继承自 {[b.__name__ for b in cls.__bases__]}"
print(describe(JsonParser)) # JsonParser 继承自 ['object']四、类也可被调用,实例也可以
调用类会创建实例(由元类的__call__完成);而如果一个类定义了__call__,它的实例也能像函数一样被调用。
# 适用于 Python 3.8+
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
print(double(10)) # 20
print(callable(double)) # True
print(callable(Multiplier)) # True判断"能不能调用"用内置的callable(),它同时适用于类、函数、定义了__call__的实例。
五、子类钩子__init_subclass__
__init_subclass__(Python 3.6 引入)是定义在父类里的类方法,每当有子类被创建时自动调用,常用来做注册或校验。
# 适用于 Python 3.8+
class Plugin:
registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Plugin.registry[cls.__name__] = cls
class AuthPlugin(Plugin):
pass
class CachePlugin(Plugin):
pass
print(sorted(Plugin.registry)) # ['AuthPlugin', 'CachePlugin']注意__init_subclass__是隐式类方法,第一个参数是新建的子类cls,不需要@classmethod装饰器。
六、一个综合示例
# 适用于 Python 3.8+
class Model:
table = None
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if cls.table is None:
cls.table = cls.__name__.lower()
def __repr__(self):
return f"{type(self).__name__}(**{vars(self)!r})"
class User(Model):
def __init__(self, user_id, name):
self.user_id = user_id
self.name = name
class Post(Model):
table = "posts"
def __init__(self, title):
self.title = title
print(User.table) # user(由钩子自动补)
print(Post.table) # posts(子类自己指定)
print(User(1, "Ada")) # User(**{'user_id': 1, 'name': 'Ada'})常见坑点
- 把"类对象"和"类的实例"混为一谈
❌
``python # 适用于 Python 3.8+ def use(obj): print(obj.__name__) # 传实例进来会 AttributeError``
✅
``python # 适用于 Python 3.8+ def use(obj): if isinstance(obj, type): return obj.__name__ return type(obj).__name__``
type(x) == SomeClass无法匹配子类
❌
``python # 适用于 Python 3.8+ print(type(Dog()) == Animal) # False``
✅
``python # 适用于 Python 3.8+ print(isinstance(Dog(), Animal)) # True``
- 用
type()动态建类时忘了传基类元组
❌
``python # 适用于 Python 3.8+ type("A", object, {}) # TypeError:第二参必须是元组``
✅
``python # 适用于 Python 3.8+ type("A", (object,), {})``
- 在
__init_subclass__里写实例参数
❌
``python # 适用于 Python 3.8+ def __init_subclass__(self): # 名字错,参数其实是子类 ...``
✅
``python # 适用于 Python 3.8+ def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs)``
- 以为
callable(cls)是False
❌
``python # 适用于 Python 3.8+ assert not callable(Animal) # AssertionError,类其实可调用``
✅
``python # 适用于 Python 3.8+ print(callable(Animal)) # True``
- 动态造类时字典里写了实例方法却忘了 self
❌
```python # 适用于 Python 3.8+ def greet(): # 缺 self return "hi"
C = type("C", (object,), {"greet": greet}) C().greet() # TypeError ```
✅
```python # 适用于 Python 3.8+ def greet(self): return "hi"
C = type("C", (object,), {"greet": greet}) print(C().greet()) # hi ```
- 在子类里访问父类
__init_subclass__未处理的参数
❌
``python # 适用于 Python 3.8+ class Base: def __init_subclass__(cls): pass # 不转发 kwargs,带类参数的子类定义会失败``
✅
``python # 适用于 Python 3.8+ class Base: def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs)``
总结
| 观点 | 说明 |
|---|
| 类是对象 | 实例类型是元类(默认type) |
| 类可动态创建 | type(name, bases, ns) |
| 类是一等公民 | 可赋值、可传参、可入容器 |
| 类可调用 | Cls()创建实例 |
| 实例可调用 | 定义了__call__即可 |
| 子类钩子 | __init_subclass__在子类创建时触发 |
"类也是一个对象"不是抽象口号,它直接解释了工厂模式、注册表和元类为什么能这么写。把类当普通值来用,把type三参数形式当class语句的底层形式来理解,很多高级用法都会变得顺理成章。