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

相关推荐
明月_清风29 分钟前
FastAPI 从入门到实战:3 分钟构建高性能异步 API
后端·python·fastapi
笨拙的老猴子34 分钟前
[特殊字符] Java GC机制详解:G1、ZGC、Shenandoah全面解析与版本演进对比
java·开发语言
bellus-36 分钟前
ubuntu26测试win10的ollama大模型性能
python
水木流年追梦37 分钟前
大模型入门-Reward 奖励模型训练
开发语言·python·算法·leetcode·正则表达式
JavaWeb学起来37 分钟前
Python学习教程(六)数据结构List(列表)
数据结构·python·python基础·python教程
liuyunshengsir1 小时前
PyTorch 动态量化(Dynamic Quantization)
人工智能·pytorch·python
电子云与长程纠缠1 小时前
UE5制作六边形包裹球体效果
开发语言·python·ue5
砍材农夫1 小时前
物联网 基于netty构建mqtt协议规范(遗嘱与保留消息)
java·开发语言·物联网·netty
DFT计算杂谈1 小时前
KPROJ编译教程
java·前端·python·算法·conda
froginwe111 小时前
Python3 迭代器与生成器
开发语言