有些Python的开源库会用到类的动态属性,其中关键字参数是主要实现方式之一。结合使用__init__
方法和**kwargs,可以定义
一个支持任意属性的类模板。此外,使用元类可以定义一个更加规范的类模版,为类型提供统一管理和规范定义。
这些语言特性在我们自己的业务代码中不一定会用到。但是熟悉了这些特性,会有助于阅读很多三方源码库。
关键字参数
在Python中,**kwargs
是一个特殊语法,用于传递关键字参数(keyword arguments)给函数。这里的kwargs
是对关键字参数字典的惯用命名,你可以将其理解为"k eyw ord arg uments "的缩写。当你在函数定义中使用**kwargs
时,Python会自动将传入的所有关键字参数打包成一个字典。这样,你可以在函数内部通过字典的方式访问这些参数,而无需预先知道参数的具体名称,这为函数提供了很大的灵活性。
python
In [1]: def foo(**kwargs):
...: for key, value in kwargs.items():
...: print(f"{key}: {value}")
...:
In [2]: foo(name="Ziggy", age=18, addr="Beijing")
name: Ziggy
age: 18
addr: Beijing
In [3]: foo(gender="male", age=18, hobit="Girl")
gender: male
age: 18
hobit: Girl
setattr
在讨论定义类的属性之前,我们看下setattr方法:
setattr
是 Python 中的一个内置函数,用于设置对象的任意属性值。这个函数允许你在运行时动态地给对象添加属性或者修改已有属性的值。setattr
函数接受三个参数:
- 对象实例:你想要修改或添加属性的那个对象。
- 属性名(字符串形式):你想要设置或修改的属性的名称。
- 属性值:你想要赋予该属性的新值。
python
In [1]: class MyClass:
...: pass
...:
In [2]: obj = MyClass()
In [3]: setattr(obj, 'name', 'ziggy')
In [4]: obj.name
Out[4]: 'ziggy'
类的动态参数
结合setattr和``**kwargs可以在类的
初始化方法__init__
中,类实例化时,接受任意数量的属性初始化,从而创建出高度可定制的对象:
python
In [11]: class MyClass:
...: def __init__(self, **kwargs):
...: for key, value in kwargs.items():
...: setattr(self, key, value)# Python内置函数
...:
In [12]: obj1 = MyClass(name="Ziggy", age=18, addr="Beijing")
In [13]: obj2 = MyClass(gender="male", age=18, hobit="Girl")
In [14]: print(obj1.name)
Ziggy
In [15]: print(obj2.gender)
male
In [16]: print(obj2.name)
AttributeError: 'MyClass' object has no attribute 'name'
In [18]: print(obj1.gender)
AttributeError: 'MyClass' object has no attribute 'gender'
上述代码的一个不足之处是这个类可以接受任意的属性,但是很多时候我们更希望可以为具体的类型规范一些允许接收的属性,这个功能可以使用简单的代码实现:
python
In [11]: class MyClass:
...: params = ['gender', 'name']
...:
...: def __init__(self, **kwargs):
...: for key, value in kwargs.items():
...: if key not in self.params:
...: raise KeyError()
...: setattr(self, key, value)# Python内置函数
...:
上述实现当然也可以做到为每个属性提供默认值,从而类的初始化参数完全可选。此点本文不再赘述。
元类浅述
上面讨论了关于kwargs和动态类属性的内容,虽然与前文并没有密切的联系,不过我们可以额外聊一下Python的元类。因为使用上述功能的部分三方库也很喜欢元类来生成内部类型。
Python中的元类负责生成类对象,有点类似于类的工厂。当你定义一个类时,默认情况下使用的是内置的type
元类。通过自定义元类,可以在类创建时插入额外的逻辑,一个简单的示例如下:
python
In [28]:class MetaBase(type):
...: def __new__(cls, name, bases, dct):
...: dct['_id'] = uuid.uuid4()
...: dct['created_at'] = dt.datetime.now()
...: return super().__new__(cls,name, bases, dct)
...:
In [29]:class MyClass(metaclass=MetaBase):
...: pass
...:
In [30]: my_instance = MyClass()
In [31]: my_instance._id
Out[31]: UUID('ee9e88b2-213a-422d-96c6-b67747b6de98')
In [32]: my_instance.created_at
Out[32]: datetime.datetime(2024, 6, 25, 17, 28, 26, 664044)
如上代码为每个通过MetaBase生成的类型都会增加一个id和一个createdAt属性。
为什么要使用元类
在定义上述class MetaBase(type)
时,实际上是创建了一个继承自type
的新元类MetaBase
。这样做有几个关键目的:
-
定制类的创建过程 :通过覆盖元类中的方法(如
__new__
或__init__
),可以在类实例化之前或之后执行特定操作,比如例子中的添加属性、或者检查类的合规性等。 -
增强类的功能:元类可以为所有继承自它的类自动添加方法、属性或者实现特定的设计模式,类似单键可以用元类来定义一个模版。
-
代码复用与规范:在大型项目或框架中,元类可以作为统一管理和规范类定义的标准手段,确保所有子类遵循相同的规则或拥有共同的行为。这正是一些三方库选择使用元类的主要原因。