浅谈Python中kwargs、动态属性和元类

有些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 函数接受三个参数:

  1. 对象实例:你想要修改或添加属性的那个对象。
  2. 属性名(字符串形式):你想要设置或修改的属性的名称。
  3. 属性值:你想要赋予该属性的新值。
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。这样做有几个关键目的:

  1. 定制类的创建过程 :通过覆盖元类中的方法(如__new____init__),可以在类实例化之前或之后执行特定操作,比如例子中的添加属性、或者检查类的合规性等。

  2. 增强类的功能:元类可以为所有继承自它的类自动添加方法、属性或者实现特定的设计模式,类似单键可以用元类来定义一个模版。

  3. 代码复用与规范:在大型项目或框架中,元类可以作为统一管理和规范类定义的标准手段,确保所有子类遵循相同的规则或拥有共同的行为。这正是一些三方库选择使用元类的主要原因。

相关推荐
程序员的开发手册13 分钟前
新手教学系列——慎用Flask-SQLAlchemy慢日志记录
数据库·python·flask·sqlalchemy
虫小宝18 分钟前
如何在Java中实现PDF生成
java·开发语言·pdf
菜鸡且互啄691 小时前
在线教育平台,easyexcel使用案例
java·开发语言
木觞清2 小时前
Django学习第三天
python·学习·django
电饭叔2 小时前
《python程序语言设计》2018版第5章第52题利用turtle绘制sin函数
开发语言·python
weixin_452600692 小时前
如何为老化的汽车铅酸电池充电
开发语言·单片机·安全·汽车·电机·电源模块·充电桩
Java资深爱好者3 小时前
如何在std::map中查找元素
开发语言·c++
YCCX_XFF213 小时前
ImportError: DLL load failed while importing _imaging: 操作系统无法运行 %1
开发语言·python
哥廷根数学学派4 小时前
基于Maximin的异常检测方法(MATLAB)
开发语言·人工智能·深度学习·机器学习
FutureUniant5 小时前
GitHub每日最火火火项目(7.7)
python·计算机视觉·ai·github·视频