在使用Python的面向对象编程时,我们经常遇到这样的情况:子类需要被设计得能够处理其父类的任何更改或添加,而不需要持续的调整。这种情况经常出现在类的 init 方法中,尤其是在处理继承时。
为了优雅地处理这种情况,Python提供了 *args
和 **kwargs
这两种约定。在深入主要示例之前,让我们简单了解这些约定是什么:
*args
: 允许你将可变数量的非关键字参数传递给函数。这些参数被捕获到一个元组中。
**kwargs
: 允许传递可变数量的关键字参数到函数。这些参数被捕获到一个字典中。
有了这些知识,我们来看一个这些约定可以发挥作用的场景。
场景:扩展父类
假设我们有一个类A,它在其构造函数中接受两个参数:
class A:def __init__(self, arg1, arg2):self.arg1 = arg1self.arg2 = arg2print(f"类A的构造函数带有参数: {arg1}, {arg2}")
现在,如果我们设计一个从类A继承的类B,并且我们希望类B能适应类A的任何更改而不必每次都修改类B,我们可以使用 *args
和 **kwargs
使类B的 init 方法变得通用:
class B(A):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs) # 将所有参数传递给类A的构造函数print("类B的构造函数")
通过这样设计类B,只要我们确保传递给类B的参数满足类A的更新要求,类A的 init 方法的签名发生的任何更改都不会破坏我们的类B实例化。
例如:
b = B("Hello", "World")
# 输出:
# 类A的构造函数带有参数: Hello, World
# 类B的构造函数
这种方法的优点:
-
灵活性:类B对类A的修改保持了韧性。在更大的项目或库中,这特别有用,例如类A可能是接收更新的模块的一部分。
-
可维护性:随着项目的增长,每次类A发生变化时,开发者都不必重新访问类B。
-
代码更简洁:我们以一种通用的方式捕获所有参数,而不是单独处理每个参数,从而使代码更加简洁和明了。
注意事项:
尽管这种方法提供了灵活性,但也需要谨慎:
盲目传递所有参数有时可能导致意外后果,尤其是如果类A的构造函数以与类B的实例化方式不兼容的方式更改。
调试可能会有点困难,因为错误消息可能指向超类的构造函数,而不是派生类。
结论:
使用 *args 和 **kwargs 设计灵活的构造函数是Python开发者工具箱中的一个强大工具,尤其是在处理继承时。通过理解和采用这种方法,开发者可以确保他们的类在更改和迭代中保持灵活性和可维护性。与往常一样,尽管灵活性有益,但也至关重要的是要小心并确保灵活性不引入意想不到的问题。
英文
原文
AI好书推荐
AI日新月异,但是万丈高楼拔地起,离不开良好的基础。您是否有兴趣了解人工智能的原理和实践? 不要再观望! 我们关于 AI 原则和实践的书是任何想要深入了解 AI 世界的人的完美资源。 由该领域的领先专家撰写,这本综合指南涵盖了从机器学习的基础知识到构建智能系统的高级技术的所有内容。 无论您是初学者还是经验丰富的 AI 从业者,本书都能满足您的需求。 那为什么还要等呢?
人工智能原理与实践 全面涵盖人工智能和数据科学各个重要体系经典
北大出版社,人工智能原理与实践 人工智能和数据科学从入门到精通 详解机器学习深度学习算法原理