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

相关推荐
邪修king16 分钟前
Re:Linux系统篇(十九):进程篇(八): 进程等待详解:wait/waitpid,僵尸进程到底该如何回收
java·开发语言
大汉堡玩测试27 分钟前
langfuse测试实战(一): 配置一个简单的项目快速落地
python·测试工具
我爱写代码i38 分钟前
SKAPP SK影视反编译详细教程+源码 含苹果端ipa
开发语言·javascript·ecmascript
一位正在转型AI全栈的前端工程师44 分钟前
AI 全栈学习之旅 -Week 8:从单 Agent 到多 Agent 协作:LangGraph 实战与记忆持久化
前端·python
长江后浪博客1 小时前
Python + YOLOv8 疲劳驾驶 AI 视觉检测入门:从模型训练到 ONNX 实时摄像头检测完整实战
人工智能·python·yolo·疲劳驾驶检测·onnx·yolov8
吴声子夜歌1 小时前
Java扩展——OkHttp
java·开发语言·okhttp
hhzz1 小时前
【OpenCV 入门到精通 01】认识 OpenCV 与计算机视觉:从零建立全局认知
人工智能·python·opencv·计算机视觉·开源
啊阿狸不会拉杆1 小时前
《计算机网络-自顶向下方法》5.7 网络管理、SNMP和NETCONF/YANG 读书笔记
开发语言·计算机网络·php
长友cy1 小时前
Qt官方示例D-BusListNames阅读
开发语言
萧瑟余晖1 小时前
Java深入解析篇六十一之编译器API(javax.tools)详解
java·开发语言