简介
在这个实验中,你将学习 Python 中的元类 (metaclass)。在 Python 里,包括类在内的一切都是对象。元类是用于创建其他类的类,它为自定义类的创建提供了强大的方式。
本实验的目标是理解什么是元类,创建你的第一个元类,使用它来创建新的类,并观察元类如何影响类的继承。在实验过程中,你将创建 mymeta.py
文件。
This tutorial is from open-source community. Access the source code
💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版
在这个实验中,你将学习 Python 中的元类 (metaclass)。在 Python 里,包括类在内的一切都是对象。元类是用于创建其他类的类,它为自定义类的创建提供了强大的方式。
本实验的目标是理解什么是元类,创建你的第一个元类,使用它来创建新的类,并观察元类如何影响类的继承。在实验过程中,你将创建 mymeta.py
文件。
元类 (Metaclass) 是 Python 中一项高级但强大的特性。作为初学者,你可能会疑惑什么是元类,以及它们为什么重要。在开始创建我们的第一个元类之前,让我们花点时间来理解这些概念。
在 Python 中,一切都是对象,类也不例外。就像普通类用于创建实例一样,元类用于创建类。默认情况下,Python 使用内置的 type
元类来创建所有类。
让我们逐步分解类的创建过程:
元类让你能够自定义这个类的创建过程。你可以在类创建时对其进行修改或检查,这在某些场景下非常有用。
让我们通过可视化这种关系来更容易理解:
Metaclass → creates → Class → creates → Instance
在这个实验中,我们将创建自己的元类。通过这样做,你将能够看到这个类创建过程的实际运作,并更好地理解元类的工作原理。
现在,我们要创建自己的第一个元类。在开始编码之前,让我们先了解一下什么是元类。在 Python 中,元类是用于创建其他类的类,就像是类的蓝图。当你在 Python 中定义一个类时,Python 会使用一个元类来创建该类。默认情况下,Python 使用 type
元类。在这一步中,我们将定义一个自定义元类,它会打印出正在创建的类的相关信息。这将帮助我们理解元类在底层是如何工作的。
在 WebIDE 中打开 VSCode,并在 /home/labex/project
目录下创建一个名为 mymeta.py
的新文件。我们将在这个文件中编写元类的代码。
在文件中添加以下代码:
## mymeta.py
class mytype(type):
@staticmethod
def __new__(meta, name, bases, __dict__):
print("Creating class :", name)
print("Base classes :", bases)
print("Attributes :", list(__dict__))
return super().__new__(meta, name, bases, __dict__)
class myobject(metaclass=mytype):
pass
让我们来分析一下这段代码的作用:
mytype
的新类,它继承自 type
。由于 type
是 Python 中的默认元类,通过继承它,我们创建了自己的自定义元类。__new__
方法。在 Python 中,__new__
方法是一个特殊方法,在创建新对象时会被调用。在元类的上下文中,它在创建新类时被调用。__new__
方法内部,我们打印了一些关于正在创建的类的信息,包括类名、基类和属性。之后,我们使用 super().__new__(meta, name, bases, __dict__)
调用父类的 __new__
方法。这一点很重要,因为它实际上创建了类。myobject
的基类,并指定它应该使用我们的自定义元类 mytype
。__new__
方法接受以下参数:
meta
:这指的是元类本身。在我们的例子中,它是 mytype
。name
:这是正在创建的类的名称。bases
:这是一个包含新类所继承的基类的元组。__dict__
:这是一个包含类属性的字典。现在,我们将通过继承创建一个使用我们元类的类。这将帮助我们理解在定义类时元类是如何被调用的。
在 Python 中,元类是用于创建其他类的类。当你定义一个类时,Python 会使用一个元类来构造该类对象。通过使用继承,我们可以指定一个类应该使用哪个元类。
mymeta.py
文件,并在文件末尾添加以下代码:class Stock(myobject):
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
在这里,我们定义了一个 Stock
类,它继承自 myobject
。__init__
方法是 Python 类中的一个特殊方法,在创建类的对象时会被调用,用于初始化对象的属性。cost
方法用于计算股票的总成本,sell
方法用于减少股票的数量。
按下 Ctrl+S 保存文件。保存文件可确保你所做的更改被保存,并且稍后可以运行。
现在让我们运行这个文件,看看会发生什么。在 WebIDE 中打开一个终端并运行以下命令:
cd /home/labex/project
python3 mymeta.py
cd
命令将当前工作目录更改为 /home/labex/project
,python3 mymeta.py
则运行 Python 脚本 mymeta.py
。
你应该会看到类似以下的输出:
Creating class : myobject
Base classes : ()
Attributes : ['__module__', '__qualname__', '__doc__']
Creating class : Stock
Base classes : (<class '__main__.myobject'>,)
Attributes : ['__module__', '__qualname__', '__init__', 'cost', 'sell', '__doc__']
这个输出表明,在创建 myobject
和 Stock
类时,我们的元类都被调用了。注意以下几点:
Stock
类,其基类包含 myobject
,因为 Stock
继承自 myobject
。__init__
、cost
、sell
)以及一些默认属性。Stock
类进行交互。创建一个名为 test_stock.py
的新文件,内容如下:## test_stock.py
from mymeta import Stock
## Create a new Stock instance
apple = Stock("AAPL", 100, 154.50)
## Use the methods
print(f"Stock: {apple.name}, Shares: {apple.shares}, Price: ${apple.price}")
print(f"Total cost: ${apple.cost()}")
## Sell some shares
apple.sell(10)
print(f"After selling 10 shares: {apple.shares} shares remaining")
print(f"Updated cost: ${apple.cost()}")
在这段代码中,我们从 mymeta
模块中导入 Stock
类,然后创建一个名为 apple
的 Stock
类实例。我们使用 Stock
类的方法来打印股票信息、计算总成本、卖出一些股票,然后打印更新后的信息。
Stock
类:python3 test_stock.py
你应该会看到类似以下的输出:
Creating class : myobject
Base classes : ()
Attributes : ['__module__', '__qualname__', '__doc__']
Creating class : Stock
Base classes : (<class 'mymeta.myobject'>,)
Attributes : ['__module__', '__qualname__', '__init__', 'cost', 'sell', '__doc__']
Stock: AAPL, Shares: 100, Price: $154.5
Total cost: $15450.0
After selling 10 shares: 90 shares remaining
Updated cost: $13905.0
注意,我们的元类信息会先被打印出来,然后才是测试脚本的输出。这是因为元类在类被定义时就会被调用,而类的定义发生在测试脚本中的代码执行之前。
元类有一个很有趣的特性:它们具有“粘性”。这意味着一旦一个类使用了某个元类,其继承层次结构中的所有子类也将使用同一个元类。换句话说,元类属性会沿着继承链传播。
让我们来实际看看这种情况:
mymeta.py
文件。在这个文件的末尾,我们要添加一个新类。这个名为 MyStock
的类将继承自 Stock
类。__init__
方法用于初始化对象的属性,我们使用 super().__init__
调用父类的 __init__
方法来初始化公共属性。info
方法用于返回一个包含股票信息的格式化字符串。添加以下代码:class MyStock(Stock):
def __init__(self, name, shares, price, category):
super().__init__(name, shares, price)
self.category = category
def info(self):
return f"{self.name} ({self.category}): {self.shares} shares at ${self.price}"
添加代码后,保存 mymeta.py
文件。保存文件可确保我们所做的更改被保存,并且稍后可以使用。
现在,我们将创建一个名为 test_inheritance.py
的新文件,以测试元类的继承行为。在这个文件中,我们将从 mymeta.py
文件中导入 MyStock
类。然后,我们将创建一个 MyStock
类的实例,调用其方法,并打印结果,以查看元类如何通过继承发挥作用。在 test_inheritance.py
中添加以下代码:
## test_inheritance.py
from mymeta import MyStock
## Create a MyStock instance
tech_stock = MyStock("MSFT", 50, 305.75, "Technology")
## Test the methods
print(tech_stock.info())
print(f"Total cost: ${tech_stock.cost()}")
## Sell some shares
tech_stock.sell(5)
print(f"After selling: {tech_stock.shares} shares remaining")
print(f"Updated cost: ${tech_stock.cost()}")
test_inheritance.py
文件,看看元类如何通过继承发挥作用。打开终端,导航到 test_inheritance.py
文件所在的目录,然后运行以下命令:python3 test_inheritance.py
你应该会看到类似以下的输出:
Creating class : myobject
Base classes : ()
Attributes : ['__module__', '__qualname__', '__doc__']
Creating class : Stock
Base classes : (<class 'mymeta.myobject'>,)
Attributes : ['__module__', '__qualname__', '__init__', 'cost', 'sell', '__doc__']
Creating class : MyStock
Base classes : (<class 'mymeta.Stock'>,)
Attributes : ['__module__', '__qualname__', '__init__', 'info', '__doc__']
MSFT (Technology): 50 shares at $305.75
Total cost: $15287.5
After selling: 45 shares remaining
Updated cost: $13758.75
注意,即使我们没有为 MyStock
类显式指定元类,元类仍然会被应用。这清楚地展示了元类如何通过继承传播。
在我们的示例中,元类只是打印类的信息。然而,元类在实际编程中有很多实际应用:
元类非常强大,但应该谨慎使用。正如著名的《Python 之禅》作者 Tim Peters 所说:“元类是比 99% 的用户应该担心的更深层次的魔法。”
在这个实验中,你学习了什么是元类以及它们在 Python 中是如何工作的。你成功创建了第一个自定义元类来监控类的创建,并使用它来生成新的类。此外,你还观察到了元类如何在继承层次结构中传播。
元类是 Python 的一项高级特性,它可以让你控制类的创建过程。虽然你可能不会每天都创建元类,但理解它们能让你更深入地了解 Python 的对象系统,并为框架和库的开发开启强大的可能性。若想了解更多内容,可以查阅 Python 官方文档以及关于元编程的高级 Python 书籍。