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

相关推荐
用户27784491049935 小时前
借助DeepSeek智能生成测试用例:从提示词到Excel表格的全流程实践
人工智能·python
JavaEdge在掘金7 小时前
ssl.SSLCertVerificationError报错解决方案
python
我不会编程5558 小时前
Python Cookbook-5.1 对字典排序
开发语言·数据结构·python
李少兄8 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
老歌老听老掉牙8 小时前
平面旋转与交线投影夹角计算
python·线性代数·平面·sympy
满怀10158 小时前
Python入门(7):模块
python
无名之逆8 小时前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
你觉得2058 小时前
哈尔滨工业大学DeepSeek公开课:探索大模型原理、技术与应用从GPT到DeepSeek|附视频与讲义下载方法
大数据·人工智能·python·gpt·学习·机器学习·aigc
似水এ᭄往昔8 小时前
【C语言】文件操作
c语言·开发语言
啊喜拔牙8 小时前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala