如何在 Python 类中提供多个构造函数2024年8月29日 | 阅读 21 分钟 有时,我们需要创建一个 Python 类,使其可以通过不同的方式来构建对象。此外,如果用户需要一个具有多个构造函数的对象,那将很有用。这种类型的类在必须创建使用各种类型参数的实例的情况下很有用。允许多个构造函数的工具可以帮助编写灵活的类,以适应不断变化的需求。 在 Python 中,我们可以使用多种方法和工具来创建类。这些方法包括使用可选参数模拟多个构造函数、通过类方法自定义创建以及使用装饰器执行特殊分派。如果用户想了解更多关于这些方法和工具的信息,本教程将为你提供帮助。 在本课程中,我们将学习如何
此外,用户还可以一窥 Python 内部创建普通类实例的方式,以及某些标准库类如何提供多个构造函数。 为了最大限度地从本教程中获益,建议用户掌握面向对象编程的基础知识,并能够使用 @classmethod 定义类。此外,他们必须有使用 Python 装饰器的经验。 在 Python 中实例化类Python 是一种支持面向对象编程概念的编程语言,它提供了易于编写和使用的类。类类似于对象的蓝图,也称为实例。就像我们可以从一个设计中建造多栋房屋一样,我们也可以从一个类中构建多个实例。Python 类提供了强大的功能,使我们能够编写更好的软件。 要使用 Python 定义类,必须定义关键字 class,后跟类的名称。 Python 提供了一整套我们可以用于类的特定方法。Python 会自动调用这些特定方法来执行实例上的许多不同任务。有特定的方法可以使我们的对象可迭代。它们可以提供对象的适当字符串表示形式、初始化实例属性等等。 最著名的技术是 .__init__()。在 Python 中,此方法称为实例初始化器。它的功能是在我们创建类时,使用适当的值设置实例的属性。 在实例中,.__init__() 方法的第一个参数称为 self。该参数包含当前实例或对象,并在该方法的调用中隐式传递。此参数由 Python 中实现的所有实例技术共享。.__init__() 中的第二个参数称为 name,它将以文本字符串的形式存储 Person 的名称。 定义类后,我们就可以开始调用它了。也就是说,我们可以开始为该特定类创建对象。要完成此操作,我们将不得不使用旧的语法。使用括号 ( ()) 调用类,其语法与我们调用任何 Python 函数时使用的语法相同。 输出 'Jonny Joe' 在 Python 中,类名就是其他语言(如 C++ 和 Java)被称为类构造函数的语言。与我们在 Person 中所做的相同,这会导致实例的类创建过程,该过程分为两个阶段。
在以下示例中,我们以参数形式输入到 Person 的数据会通过 .__init__() 内部传递,然后分配给实例属性 .name。这样,我们就可以使用正确的数据初始化 Person 实例 jonny,我们可以通过访问 .name 来验证。成功!事实上,他的名字是 Jonny Joe。 一旦用户掌握了对象的初始化机制和对象初始化的机制,他们就可以理解 Python 在实例化过程的这个阶段之前所做的事情了。现在是时候探索另一个称为 .__new__() 的方法了。此方法负责在 Python 中创建新实例。 .__new__() 特殊方法使用底层类作为第一个参数,并返回一个新对象。该对象通常是所输入类的实例。但是,在某些情况下,它可能是属于另一个类的实例。 如果 .__new__() 返回的对象是当前调用的类的实例,则该实例将立即传递给 .__init__() 来初始化类。每当我们调用类时,这两个步骤都会发生。 Python objects 类是 .__new__() 和 .__init__() 的默认或基类版本。与 .__init__() 相反,我们无需在自己的自定义类中修改 .__new__()。在大多数情况下,我们可以依赖默认实现。 总结一下,到目前为止我们学到的课程,Python 实例化过程始于使用正确的参数调用类的实例。然后,该过程通过两个阶段:使用 .__new__() 方法创建对象,以及使用其 .__init__() 方法初始化对象。 一旦我们掌握了 Python 的内部行为,就可以开始在类中提供多个构造函数了。也就是说,我们将提供多种方法来在同一个 Python 类中创建对象。 如何定义多个类构造函数有时,我们希望编写允许我们使用各种数据类型或不同数量的参数来构建对象的类。一种方法是在相关类中提供不同的构造函数。每个构造函数允许使用一套完全不同的参数来创建实例。 某些编程语言,如 C++、C# 和 Java,能够支持方法或函数重载。此功能允许我们提供多个类构造函数,因为它允许我们编写具有相同名称但实现不同的多个方法或函数。 方法重载是在调用方法时决定如何执行的过程,语言将选择最合适的实现来运行。因此,我们的方法可以根据指令的情况完成不同的任务。 不幸的是,Python 不直接支持函数重载。Python 类在称为 .__dict__ 的内部字典中存储其方法的名称,该字典保存类的 **命名空间**。与 Python 字典的其他功能类似,.__dict__ 不能包含重复的键,这意味着我们不能在同一个类中有多个同名方法。如果我们尝试这样做,Python 将只能记住该方法在时间中的最新执行。 在这个实例中,我们构建了一个 Greeterr,顾名思义,在此示例中。它是一个使用两个方法的 Python 类。这两个方法具有相同的名称。但是,它们的实现略有不同。 要找出具有相同名称和类的两个方法的后果,请将我们的类保存到工作目录中的 greetr.py 文件中,并在当前活动会话中执行以下代码。 输出 Hello, Javatpoint mappingproxy({'__module__': 'greetr', 'say_hello': 在这个实例中,我们调用 greeter 上的 .say_hello()。它是 Greeterr 类的一个实例。我们在显示中看到“hello, Javatpoint”,而不是“hello, Universe”,这证明了该方法的第二个版本胜过了第一个版本。 最后一行代码仔细检查了 .__dict__ 中的内容,发现方法的名称 say_hello 在类的命名空间中只使用了一次。这与 Python 中字典的功能一致。 在 Python 模块和交互式会话中使用函数时,也会发生同样的情况。具有相同名称的函数实现比其他实现更优。 输出 Hello, Javatpoint 在同一个解释器会话中定义了两个同名的函数 say_hello()。但第二次定义替换了上一次。当我们调用函数时,我们将得到“Hello, Javatpoint”,这证实了第一个定义是占主导地位的。 另一种某些编程语言用于提供多种调用操作或方法方式的技术是多重分派。 这项技术允许我们创建同一方法或函数的多个版本,然后根据请求中使用的参数的类型或其他属性动态分派我们要使用的实现。有可能使用标准库中的各种程序将此技术整合到我们编写的 Python 代码中。 Python 是一种非常通用且功能丰富的语言。它提供了几种使用多个构造函数的方法,使我们的类更具适应性。 在下一节中,我们将能够使用附加参数来模拟多个构造函数,并检查参数以确定初始化器中各种行为。 如何在我们的类中模拟多个构造函数在 Python 类中模拟多个构造函数的一种非常有用的方法是,通过使用默认参数为 .__init__() 提供可选参数。这样,我们就可以以多种方式使用类构造函数,并每次都获得相同的行为。 另一种选择是验证我们传递给 .__init__() 的数据格式,以便根据调用期间传递的数据提供不同的行为。此方法允许我们在同一个类中模拟多个构造函数。 本节将帮助我们发现通过为 .__init__() 方法提供正确的默认值以及检查方法参数中使用的数据类型来模拟不同对象构建方式的基础知识。这两种方法都只需要一种 .__init__() 方法。 在 .__init__() 中使用可选参数值模拟多个构造函数的简单 Pythonic 方法是实现使用可选参数的 .__init__() 方法。我们可以通过指定适当的默认参数来实现这一点。 为此,假设我们需要编写一个名为 CumulativePowerFactory 的工厂类。此类创建可调用对象,这些对象使用数值流作为输入来计算特定幂。此外,我们的类还需要跟踪连续幂的总和。此外,我们的类应该接受一个参数,该参数保存了幂之和的初始值。 在当前目录中创建一个 powers.py 文件。然后键入以下代码来实现 CumulativePowerFactory。 CumulativePowerFactory 的初始化器接受两个可选参数 exponent 和 start。第一个参数包含我们将用于计算幂集的指数。默认值为 2,这是计算幂最常用的值。 指数后面的星号 ( *) 表示 start 仅为关键字参数。为了将值传递给仅关键字参数,必须在特定位置指定参数的名称。例如,要将参数值更改为 value,必须明确输入“arg = value”字词。 Start 参数:开始参数是用于计算幂的累积和的初始值。默认值为零,这是在我们没有要设置幂和的计算值的情况下最佳值。 特殊方法 .__call__() 将 CumulativePowerFactory 的实例转换为 **可调用** 对象。换句话说,我们可以像调用任何常规函数一样调用 CumulativePowerFactory 的实例。 在 .__call__() 中,我们首先计算以指数为基数的幂。然后,我们将所得数字应用于 .total 中当前的值。最后,返回我们计算出的幂。 要尝试 CumulativePowerFactory,请在包含 powerr.py 的目录中打开 Python 交互式会话,并在以下活动会话中运行以下代码。 输出 676 输入 输出 1296 输入 输出 1972 输入 输出 29791 输入 输出 46656 输入 输出 76447 输入 输出 17576 输入 输出 97336 输入 输出 117117 这些示例展示了 CumulativePowerFactory 如何模拟多个构造函数。例如,初始构造函数不接受参数。它允许我们创建计算幂 2 的类实例,这将是指数参数的标准值。当我们工作时,.total 实例属性包含计算幂的幂。 第二个示例说明了一个接受指数作为参数的运算符。在这种情况下,.total 的工作方式与前一个示例完全相同。它会生成一个可调用实例,该实例可以计算立方。 第三个示例显示了 CumulativePowerFactory 似乎具有另一个构造函数,该构造函数允许我们通过提供指数和起始参数来创建实例。现在,.total 以 1972 这个初始数字开始,这是幂和的初始值。 在类中使用 .__init__() 时利用可选参数是创建模拟多个构造函数的类的简单 Pythonic 方法。 检查 .__init__() 中的参数类型 为了确定 Python 中变量的性质,通常使用内置的 isinstance() 函数。当一个对象是某个类的对象时,该函数将返回 True,否则返回 False。另一种模拟多个构造函数的方法是创建一个 .__init__() 方法,该方法根据参数的类型执行不同的操作。 输出 True 输入 输出 False 输入 输出 True "isinstance()" 的第一个参数是我们想要进行类型检查的对象。另一个参数将是类或引用类型。在这种情况下,也可以提供多种数据类型。如果用户使用的是 Python 3.10 或更高版本,他们还可以使用管道 ( |) 使用新的 Union 语法。 然后,假设我们想在创建类时工作,并且要求该类接受人员的出生日期。我们的代码将以时间对象的形式显示出生日期;但是,为了使用户更方便,他们可以选择使用适当格式的字符串输入出生日期。在这种情况下,我们可以实现类似这样的内容。 输出 datetime.date(1993, 12, 19) 输入 输出 datetime.date(1991, 7, 25) 在 .__init__() 中,首先必须定义标准的 .name 属性。如果条件语句中的一个子句确定出生日期是否为日期对象。如果是,则创建 .birth_date 来存储当前日期。 elif 子句测试我们是否可以确定 birth_date 参数是否为字符串类型之一。如果是,则将 .birth_date 设置为从提供的字符串派生的日期对象。请注意,birth_date 参数必须是一个包含日期(格式为 **ISO**)的字符串。例如 **YYYY-DD-MM**。 就是这样!现在我们有了一个 .__init__() 方法,该方法模拟了具有多个构造函数的类型。一个构造函数接受日期类型的参数。另一个构造函数接受字符串类型的参数。 上面示例中使用的方法存在扩展性差的缺点。如果我们使用多个可能用于表示各种数据类型的参数,我们的应用程序可能会很快变得麻烦。因此,这种方法在 Python 中被视为一种反模式。 例如,如果我们的用户输入了出生日期的日期和时间量,会发生什么?请看以下代码片段。 输出 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) 如果我们尝试访问 .birth_date,我们将收到一条错误消息,指出该属性不可用,因为我们的条件语句没有考虑日期替代格式的分支。 要解决此问题,请继续添加 elif 子句以包含用户可以使用的所有可能的日期格式。还可以包含另一个替代子句来检测不支持的日期格式。 输出 --------------------------------------------------------------------------- ValueError Traceback (most recent call last) 在这种情况下,它是 otherwise 子句。如果 birthday_date 中的数字不是实际的日期对象或包含有效 ISO 日期的字符串,则执行此子句。这样,独特的情况就不会静默发生。 如何在 Python 中使用 @classmethod 提供多个构造函数提供多个 Python 构造函数的有效方法是利用 @classmethod 装饰器。此装饰器允许我们将普通方法转换为实际的类方法。 与普通方法相反,类不使用实例 self 作为参数。相反,它们考虑整个类,通常包含在 CLS 参数中。使用 cls 一词来标识参数已成为 Python 社区中的一种常用做法。 这是定义类的初步语法。 输出 This is a class method from Demo_Class! 输入 输出 This is a class method from Demo_Class! Demo class 是一个类方法,它使用内置的 @classmethod 装饰器。.class_method() 中的第一个参数是类的名称。使用此参数,我们能够访问类内部的类。在这种情况下,我们将需要访问 .__name__ 属性,该属性以文本字符串的形式记录有关类的信息。 重要的是要知道,我们可以使用类或该类的特定实例来调用类方法。使用类作为类构造函数的主要原因是,我们不需要实例来调用类方法的实例。但是,当我们调用 .class_method() 时,我们将得到 Demo_Class 作为第一个参数。 @classmethod 的使用允许我们包含我们为特定类所需的显式构造函数的数量。这是一种 Pythonic 且常用的使用多个构造函数的方法。我们也可以将这种类型的构造函数称为 Python 中的备用构造函数,例如 Raymond Hettinger 在他的 PyCon 演示文稿 Python Class Development Toolkit 中使用的。 我们现在能做什么来使用类方法来改变 Python 的实例化过程?而不是调整 .__init__() 和对象初始化,我们可以控制这两个阶段:对象创建和初始化。在下面的示例中,我们将学习如何做到这一点。 从直径构造一个圆作为我们第一个类构造函数,使用 @classmethod,假设我们正在为一个几何相关程序编程,并且需要 Circle 类。首先,我们将按如下方式定义类。 该类实现了使用 Python 数学模块计算 Circle 的周长和面积的方法。在初始化器中,Circle 使用半径数作为参数,然后将其存储为实例属性 .radius。过程 .__repr__() 提供适合类的适当字符串表达式。 在工作目录中创建一个 circle1.py 文件。然后打开 Python 解释器并运行以下程序来测试 Circle。 输出 Circle(radius = 52) 输入 输出 8494.8665353068 输入 输出 326.7256359733385 太棒了!我们的类工作正常!现在设想我们想通过直径创建一个圆。我们可以使用 Circle (diameter + 2.) 来做到这一点,但这并不完全是 Pythonic 且直观。最好使用备用构造函数,该构造函数可以通过直接使用 Circle 的直径来创建圆。 将以下类方法添加到 Circle 的 .__init__() 中间: 在这种情况下,代码将 .from_diameter() 定义为一个类方法的示例。该方法的第一个参数将是它所属的 Class Circle 的 id。 第二个参数是我们希望创建的 Circle 的大小。在此过程中,我们首先通过使用直径中的输入值来确定半径。然后,我们通过使用直径参数计算出的半径调用 CLS 来创建一个圆。 通过这种方式,我们可以完全控制使用直径作为参数来定义和初始化 Circle 的所有实例。 调用 cls 参数的函数会执行 Python 创建类所需的初始化和创建过程。然后,.from_diameter() 将新创建的实例返回给用户。 这是如何使用我们创建的全新构造函数来创建使用半径的圆。 输出 Circle(radius=42.0) 输入 输出 5541.769440932395 输入 输出 263.89378290154264 Circle 的 .from_diameter() 方法会生成该类的一个全新的实例。创建该实例时使用的是实际直径,而不是半径。Circle 的其余函数部分与之前相同。 @classmethod,与上面的示例类似,是为我们的类指定多个构造函数的常用方法。这样,我们就可以为每个提供的构造函数选择适当的名称,这有助于使我们的代码更具可理解性和可管理性。 从笛卡尔坐标构建极坐标点为了更详细地说明使用类方法实现多个构造函数,假设我们有一个表示极坐标概念的类,用于我们的数学相关程序。有必要使我们的类更灵活,以便我们也可以使用笛卡尔坐标构建新实例。 以下是我们如何创建一个满足此需求的构造函数。 在此实例中,.from_cartesian() 接受两个参数,它们代表该区域的坐标:x 以及 Cartesian 坐标。然后,该方法计算所需的距离和角度,以创建适当的 PolarPoint 对象。然后,.from_cartesian() 生成该类的一个新创建的实例。 以下是该类如何使用两种坐标系运行。 输出 PolarPoint(distance = 16.0, angle = 21.6) 输入 输出 PolarPoint(distance = 26.6, angle = 34.3) 在这些实例中,我们将使用标准的实例化方法和我们的备用构造函数 .from_cartesian(),它允许我们生成使用概念上不同的初始化参数的 PolarPoint 实例。 探索现有 Python 类中的多个构造函数利用 @classmethod 装饰器为类提供多个构造函数是 Python 中的常见做法。标准类和内置类中有许多示例使用此方法提供多个不同的构造函数。 在本篇文章中,我们将了解这三个著名的类:dict、datetime .date 和 pathlib.Path。 从键构建字典字典是 Python 中最基本的数据类型。它们存在于 Python 代码的各个部分,无论是直接还是间接。它们也是该语言不可或缺的一部分,因为 Python 的 CPython 应用程序的许多重要部分都依赖于它们。 有许多方法可以识别 Python 中的字典实例。可以使用用花括号 ( {}) 括起来的键值对的字典字面量。也可以通过关键字参数或元组序列(例如)显式调用 "dict()"。 这个著名的类还提供了一个备用构造函数,称为 .fromkeys()。此类使用键数组的形式以及可选的键数和可选值。值参数是可选的。值默认为 None,并且它是结果字典的所有键的相同值。 用户认为 .fromkeys() 在他们的程序中会有什么帮助?如果他们经营一家动物收容所,并希望开发一个应用程序来跟踪他们收容所中的动物数量。该应用程序使用字母顺序字典来记录动物库存。 由于用户知道他们可以在收容所收容哪些动物物种,因此他们可以从头开始构建库存字典,例如在以下代码示例中。 输出 {'Tiger': 0, 'lion': 0, 'Cobra': 0, 'donkey': 0} 在此实例中,我们使用 .fromkeys() 构建了一个初始字典,该字典从允许进入系统的动物中获取键。我们创建的库存,每个动物的库存为零,方法是在 .fromkeys() 中提供此数字作为第二个参数。 如前所述,默认值为 None,在某些情况下,这是字典键的适当初始值。在下面的示例中,零是一个理想的值,因为我们处理的是每个物种的个体数量。 标准库中的其他映射(如 OrderedDict、defaultdict 和 UserDict)也具有名为 .fromkeys() 的构造函数。例如,UserDict 的软件源提供了 .fromkeys() 的这种实现。 这是因为 .fromkeys() 使用可迭代对象和数字作为参数。该方法使用 cls 方法创建一个全新的字典。然后,它遍历可迭代对象中的键,并将每个值分配给正常方式下设置为 None 的值。最后,该方法返回新创建的字典。 创建 datetime.date 对象标准库中的 datetime.date 类是另一个使用多个构造函数的类。该类有许多备用构造函数,包括 .today()、.fromtimestamp()、.fromordinal() 和 .fromisoformat()。所有这些都允许我们使用相同的参数但具有不同的概念来创建 datetime.date 对象。 以下是一些我们如何使用上述构造函数来构建 datetime.date 对象的示例。 输出 datetime.date(2021, 12, 23) 输入 输出 datetime.date(2021, 12, 23) 输入 输出 datetime.date(2021, 9, 25) 输入 输出 datetime.date(2022, 3, 16) 输入 输出 datetime.date(2021, 12, 4) 输入 输出 datetime.date(2021, 12, 23) 第一个示例使用类的标准构造函数作为引用。第二个示例演示了如何使用 .today() 来构建基于当前日期的时间对象。 其余示例演示了 datetime.date 如何使用类来提供多个构造函数。可用的构造函数种类繁多,使得实例化过程健壮且灵活,并涵盖了各种场景。通过提供显式的方法名称,这还可以提高我们代码的可访问性。 找到回家的路Python 的 pathlib 模块是标准库的一部分,它提供了现代高效的工具来有效地处理程序中的系统路径。如果用户以前从未听说过此模块,他们应该阅读 **Python 3 的 pathlib 模块:驯服文件系统的艺术。** pathlib 库中最强大的工具是 Path 类。此类允许我们在跨平台的基础上管理我们的系统路径。Path 是另一个具有多个构造函数的标准库类。例如,我们将遇到 Path.home(),它从我们的主目录创建一个名为 path 的对象。 输出 WindowsPath('C:/Users/User Name') .home() 构造函数会创建一个新的路径对象,该对象代表我们用户的主目录。在处理 Python 应用程序和项目中的配置文件时,此备用构造函数可能很有用。 结论编写具有多个构造函数的 Python 类将有助于使我们的代码更灵活和适应性强,从而实现许多不同的用例。多个构造函数是一种有效的功能,它允许我们根据用户的需求,使用不同类型、不同数量的参数或两者兼有的参数来创建核心类的实例。 在本课程中,我们已经学习了如何
下一个主题Python 代码剖析 |
我们请求您订阅我们的新闻通讯以获取最新更新。