类创建的底层机制

PythonPythonBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在这个实验中,你将了解在 Python 中创建类所涉及的底层步骤。理解如何使用 type() 函数来构造类,可以让你更深入地了解 Python 的面向对象特性。

你还将实现自定义类的创建技术。在这个实验过程中,你将修改 validate.pystructure.py 文件,从而能够在实际场景中应用你所学的新知识。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/constructor("Constructor") subgraph Lab Skills python/conditional_statements -.-> lab-132517{{"类创建的底层机制"}} python/function_definition -.-> lab-132517{{"类创建的底层机制"}} python/classes_objects -.-> lab-132517{{"类创建的底层机制"}} python/constructor -.-> lab-132517{{"类创建的底层机制"}} end

手动创建类

在 Python 编程中,类是一个基本概念,它允许你将数据和函数组合在一起。通常,我们使用标准的 Python 语法来定义类。例如,下面是一个简单的 Stock 类。这个类表示一支股票,具有 namesharesprice 等属性,并且有计算成本和卖出股票的方法。

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, nshares):
        self.shares -= nshares

但你是否想过 Python 在幕后是如何实际创建类的呢?如果我们不想使用标准的类语法来创建这个类,该怎么办呢?在本节中,我们将探索 Python 类是如何在底层构建的。

启动 Python 交互式 shell

要开始手动创建类的实验,我们需要打开一个 Python 交互式 shell。这个 shell 允许我们逐行执行 Python 代码,非常适合学习和测试。

在 WebIDE 中打开一个终端,并通过输入以下命令启动 Python 交互式 shell。第一个命令 cd ~/project 将当前目录更改为项目目录,第二个命令 python3 启动 Python 3 交互式 shell。

cd ~/project
python3

将方法定义为普通函数

在手动创建类之前,我们需要定义将成为类一部分的方法。在 Python 中,方法只是与类关联的函数。因此,让我们将类中需要的方法定义为普通的 Python 函数。

def __init__(self, name, shares, price):
    self.name = name
    self.shares = shares
    self.price = price

def cost(self):
    return self.shares * self.price

def sell(self, nshares):
    self.shares -= nshares

这里,__init__ 函数是 Python 类中的一个特殊方法。它被称为构造函数,用于在创建类的实例时初始化对象的属性。cost 方法计算股票的总成本,sell 方法减少股票的数量。

创建方法字典

现在我们已经将方法定义为普通函数,需要以一种 Python 在创建类时能够理解的方式来组织它们。我们通过创建一个包含类所有方法的字典来实现这一点。

methods = {
    '__init__': __init__,
    'cost': cost,
    'sell': sell
}

在这个字典中,键是方法在类中使用的名称,值是我们之前定义的实际函数对象。

使用 type() 构造函数创建类

在 Python 中,type() 函数是一个内置函数,可用于在底层创建类。type() 函数接受三个参数:

  1. 类的名称:这是一个字符串,表示我们要创建的类的名称。
  2. 基类的元组:在 Python 中,类可以继承自其他类。这里,我们使用 (object,),这意味着我们的类继承自基类 object,它是 Python 中所有类的基类。
  3. 包含方法和属性的字典:这是我们之前创建的包含类所有方法的字典。
Stock = type('Stock', (object,), methods)

这行代码使用 type() 函数创建了一个名为 Stock 的新类。该类继承自 object 类,并具有 methods 字典中定义的方法。

测试我们手动创建的类

现在我们已经手动创建了类,让我们测试一下,确保它能按预期工作。我们将创建新类的一个实例并调用其方法。

s = Stock('GOOG', 100, 490.10)
print(s.name)
print(s.cost())
s.sell(25)
print(s.shares)

在第一行中,我们创建了一个 Stock 类的实例,股票名称为 GOOG,有 100 股,价格为 490.10。然后我们打印股票的名称,计算并打印成本,卖出 25 股,最后打印剩余的股票数量。

你应该会看到以下输出:

GOOG
49010.0
75

这个输出表明我们手动创建的类与使用标准 Python 语法创建的类的工作方式相同。这证明了类从根本上来说只是一个名称、一个基类元组以及一个包含方法和属性的字典。type() 函数只是从这些组件构建一个对象。

完成后退出 Python shell:

exit()

创建类型化结构辅助函数

在这一步中,我们将构建一个更实用的示例。我们将实现一个函数,用于创建带有类型验证的类。类型验证至关重要,因为它能确保分配给类属性的数据满足特定标准,比如属于特定的数据类型或处于特定的范围内。这有助于尽早捕获错误,使我们的代码更加健壮。

理解 Structure

首先,你需要在 WebIDE 编辑器中打开 structure.py 文件。该文件包含一个基本的 Structure 类。这个类为初始化和表示结构化对象提供了基本功能。初始化指的是用提供的数据设置对象,而表示则是指当我们打印对象时它的显示方式。

要打开文件,你可以在终端中使用以下命令:

cd ~/project

运行此命令后,你将进入 structure.py 文件所在的正确目录。打开文件后,你会看到基本的 Structure 类。我们的目标是扩展这个类以支持类型验证。

实现 typed_structure 函数

现在,让我们将 typed_structure 函数添加到 structure.py 文件中。这个函数将创建一个新类,该类继承自 Structure 类并包含指定的验证器。继承意味着新类将拥有 Structure 类的所有功能,并且还可以添加自己的特性。验证器用于检查分配给类属性的值是否有效。

以下是 typed_structure 函数的代码:

def typed_structure(clsname, **validators):
    """
    Create a Structure class with type validation.

    Parameters:
    - clsname: Name of the class to create
    - validators: Keyword arguments mapping attribute names to validator objects

    Returns:
    - A new class with the specified name and validators
    """
    cls = type(clsname, (Structure,), validators)
    return cls

clsname 参数是我们要给新类起的名称。validators 参数是一个字典,其中键是属性名,值是验证器对象。type() 函数用于动态创建新类。它接受三个参数:类名、基类元组(在这种情况下,只有 Structure 类)以及类属性字典(即验证器)。

添加此函数后,你的 structure.py 文件应如下所示:

## Structure class definition

class Structure:
    _fields = ()

    def __init__(self, *args, **kwargs):
        if len(args) > len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')

        ## Set all of the positional arguments
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        ## Set the remaining keyword arguments
        for name, value in kwargs.items():
            setattr(self, name, value)

    def __repr__(self):
        attrs = ', '.join(f'{name}={getattr(self, name)!r}' for name in self._fields)
        return f'{type(self).__name__}({attrs})'

def typed_structure(clsname, **validators):
    """
    Create a Structure class with type validation.

    Parameters:
    - clsname: Name of the class to create
    - validators: Keyword arguments mapping attribute names to validator objects

    Returns:
    - A new class with the specified name and validators
    """
    cls = type(clsname, (Structure,), validators)
    return cls

测试 typed_structure 函数

让我们使用 validate.py 文件中的验证器来测试我们的 typed_structure 函数。这些验证器用于检查分配给类属性的值是否为正确的类型并满足其他标准。

首先,打开一个 Python 交互式 shell。你可以在终端中使用以下命令:

cd ~/project
python3

第一个命令将你带到正确的目录,第二个命令启动 Python 交互式 shell。

现在,导入必要的组件并创建一个类型化结构:

from validate import String, PositiveInteger, PositiveFloat
from structure import typed_structure

## Create a Stock class with type validation
Stock = typed_structure('Stock', name=String(), shares=PositiveInteger(), price=PositiveFloat())

## Create a stock instance
s = Stock('GOOG', 100, 490.1)

## Test the instance
print(s.name)
print(s)

## Test validation
try:
    invalid_stock = Stock('AAPL', -10, 150.25)  ## Should raise an error
except ValueError as e:
    print(f"Validation error: {e}")

我们从 validate.py 文件中导入 StringPositiveIntegerPositiveFloat 验证器。然后使用 typed_structure 函数创建一个带有类型验证的 Stock 类。我们创建 Stock 类的一个实例并通过打印其属性来测试它。最后,我们尝试创建一个无效的股票实例来测试验证功能。

你应该会看到类似于以下的输出:

GOOG
Stock('GOOG', 100, 490.1)
Validation error: Expected a positive value

测试完成后,退出 Python shell:

exit()

这个示例展示了我们如何使用 type() 函数创建具有特定验证规则的自定义类。这种方法非常强大,因为它允许我们以编程方式生成类,这可以节省大量时间并使我们的代码更加灵活。

✨ 查看解决方案并练习

高效的类生成

既然你已经了解了如何使用 type() 函数创建类,我们将探索一种更高效的方法来生成多个相似的类。这种方法可以节省你的时间并减少代码重复,让你的编程过程更加顺畅。

理解现有的验证器类

首先,你需要在 WebIDE 中打开 validate.py 文件。该文件已经包含了几个验证器类,用于检查值是否满足特定条件。这些类包括 ValidatorPositivePositiveIntegerPositiveFloat。我们将在这个文件中添加一个 Typed 基类和几个特定类型的验证器。

要打开文件,请在终端中运行以下命令:

cd ~/project

添加 Typed 验证器类

让我们从添加 Typed 验证器类开始。这个类将用于检查一个值是否为预期的类型。

class Typed(Validator):
    expected_type = object  ## Default, will be overridden in subclasses

    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        super().check(value)

在这段代码中,expected_type 默认设置为 object。子类将用它们要检查的特定类型覆盖这个值。check 方法使用 isinstance 函数来检查值是否为预期的类型。如果不是,则会引发一个 TypeError

传统上,我们会像这样创建特定类型的验证器:

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

然而,这种方法存在重复。我们可以使用 type() 构造函数动态生成这些类,从而做得更好。

动态生成类型验证器

我们将用一种更高效的方法替换单个类的定义。

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str)
]

globals().update((name, type(name, (Typed,), {'expected_type': ty}))
                 for name, ty in _typed_classes)

这段代码的作用如下:

  1. 定义一个元组列表。每个元组包含一个类名和对应的 Python 类型。
  2. 使用带有 type() 函数的生成器表达式来创建每个类。type() 函数接受三个参数:类名、基类元组和类属性字典。
  3. 使用 globals().update() 将新创建的类添加到全局命名空间中。这使得这些类在整个模块中都可以访问。

你完成后的 validate.py 文件应该类似于以下内容:

## Basic validator classes

class Validator:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        self.check(value)
        instance.__dict__[self.name] = value

    @classmethod
    def check(cls, value):
        pass

class Positive(Validator):
    @classmethod
    def check(cls, value):
        if value <= 0:
            raise ValueError('Expected a positive value')
        super().check(value)

class PositiveInteger(Positive):
    @classmethod
    def check(cls, value):
        if not isinstance(value, int):
            raise TypeError('Expected an integer')
        super().check(value)

class PositiveFloat(Positive):
    @classmethod
    def check(cls, value):
        if not isinstance(value, float):
            raise TypeError('Expected a float')
        super().check(value)

class Typed(Validator):
    expected_type = object  ## Default, will be overridden in subclasses

    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        super().check(value)

## Generate type validators dynamically
_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str)
]

globals().update((name, type(name, (Typed,), {'expected_type': ty}))
                 for name, ty in _typed_classes)

测试动态生成的类

现在,让我们测试我们动态生成的验证器类。首先,打开一个 Python 交互式 shell。

cd ~/project
python3

进入 Python shell 后,导入并测试我们的验证器。

from validate import Integer, Float, String

## Test the Integer validator
i = Integer()
i.__set_name__(None, 'test_int')
try:
    i.check("not an integer")
    print("Error: Check passed when it should have failed")
except TypeError as e:
    print(f"Integer validation: {e}")

## Test the String validator
s = String()
s.__set_name__(None, 'test_str')
try:
    s.check(123)
    print("Error: Check passed when it should have failed")
except TypeError as e:
    print(f"String validation: {e}")

## Add a new validator class to the list
import sys
print("Current validator classes:", [cls for cls in dir() if cls in ['Integer', 'Float', 'String']])

你应该会看到显示类型验证错误的输出。这表明我们动态生成的类正在正确工作。

测试完成后,退出 Python shell:

exit()

扩展动态类生成

如果你想添加更多的类型验证器,只需更新 validate.py 中的 _typed_classes 列表。

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str),
    ('List', list),
    ('Dict', dict),
    ('Bool', bool)
]

这种方法提供了一种强大而高效的方式来生成多个相似的类,而无需编写重复的代码。它允许你在需求增长时轻松扩展你的应用程序。

✨ 查看解决方案并练习

总结

在本次实验中,你学习了 Python 中类创建的底层机制。首先,你掌握了如何使用 type() 构造函数手动创建类,这需要一个类名、一个基类元组和一个方法字典。其次,你实现了一个 typed_structure 函数,用于动态创建具有验证功能的类。

此外,你结合使用 type() 构造函数和 globals().update() 高效地生成了多个相似的类,从而避免了代码重复。这些技术为以编程方式创建和定制类提供了强大的方法,在框架、库和元编程中非常有用。理解这些底层机制可以加深你对 Python 面向对象特性的理解,并使你能够进行更高级的编程。