简介
在本次实验中,你将了解混入类(mixin classes)及其在提高代码可复用性方面的作用。你将明白如何实现混入类,在不修改现有代码的情况下扩展类的功能。
你还将掌握 Python 中的协作式继承技术。在实验过程中,你将对 tableformat.py
文件进行修改。
This tutorial is from open-source community. Access the source code
💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版
在本次实验中,你将了解混入类(mixin classes)及其在提高代码可复用性方面的作用。你将明白如何实现混入类,在不修改现有代码的情况下扩展类的功能。
你还将掌握 Python 中的协作式继承技术。在实验过程中,你将对 tableformat.py
文件进行修改。
在这一步中,我们将探究当前表格格式化实现中的一个局限性。我们还将研究针对此问题的一些可能解决方案。
首先,让我们明确要做什么。我们将打开 VSCode 编辑器,查看项目目录中的 tableformat.py
文件。这个文件很重要,因为它包含的代码能让我们以不同方式格式化表格数据,比如文本、CSV 或 HTML 格式。
要打开该文件,我们将在终端中使用以下命令。cd
命令用于将目录更改为项目目录,code
命令用于在 VSCode 中打开 tableformat.py
文件。
cd ~/project
code tableformat.py
当你打开文件时,会注意到定义了几个类。这些类在格式化表格数据方面发挥着不同的作用。
TableFormatter
:这是一个抽象基类(abstract base class)。它包含用于格式化表格标题和行的方法。可以将其视为其他格式化器类的蓝图。TextTableFormatter
:此类用于以纯文本格式输出表格。CSVTableFormatter
:它负责将表格数据格式化为 CSV(逗号分隔值,Comma-Separated Values)格式。HTMLTableFormatter
:此类将表格数据格式化为 HTML 格式。文件中还有一个 print_table()
函数。该函数使用我们刚刚提到的格式化器类来显示表格数据。
现在,让我们通过运行一些 Python 代码来看看这些类是如何工作的。打开一个终端并启动 Python 会话。以下代码从 tableformat.py
文件中导入必要的函数和类,创建一个 TextTableFormatter
对象,然后使用 print_table()
函数显示投资组合数据。
python3 -c "
from tableformat import print_table, TextTableFormatter, portfolio
formatter = TextTableFormatter()
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"
运行代码后,你应该会看到类似以下的输出:
name shares price
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
现在,让我们找出问题所在。注意,price
列中的值格式不一致。有些值有一位小数,如 32.2,而有些值有两位小数,如 51.23。在金融数据中,我们通常希望格式保持一致。
我们期望的输出如下:
name shares price
---------- ---------- ----------
AA 100 32.20
IBM 50 91.10
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.10
IBM 100 70.44
解决这个问题的一种方法是修改 print_table()
函数,使其接受格式规范。以下代码展示了我们如何实现这一点。我们定义了一个新的 print_table()
函数,它接受一个额外的 formats
参数。在函数内部,我们使用这些格式规范来格式化行中的每个值。
python3 -c "
from tableformat import TextTableFormatter, portfolio
def print_table(records, fields, formats, formatter):
formatter.headings(fields)
for r in records:
rowdata = [(fmt % getattr(r, fieldname))
for fieldname, fmt in zip(fields, formats)]
formatter.row(rowdata)
formatter = TextTableFormatter()
print_table(portfolio,
['name','shares','price'],
['%s','%d','%0.2f'],
formatter)
"
这个解决方案可行,但有一个缺点。更改函数的接口可能会破坏使用旧版本 print_table()
函数的现有代码。
另一种方法是通过子类化创建自定义格式化器。我们可以创建一个继承自 TextTableFormatter
的新类,并覆盖 row()
方法以应用所需的格式。
python3 -c "
from tableformat import TextTableFormatter, print_table, portfolio
class PortfolioFormatter(TextTableFormatter):
def row(self, rowdata):
formats = ['%s','%d','%0.2f']
rowdata = [(fmt % d) for fmt, d in zip(formats, rowdata)]
super().row(rowdata)
formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"
这个解决方案也可行,但不太方便。每次需要不同的格式时,我们都必须创建一个新类。而且我们仅限于从特定的格式化器类型进行子类化,在这种情况下是 TextTableFormatter
。
在下一步中,我们将探索一种使用混入类(mixin classes)的更优雅的解决方案。
在这一步中,你将学习混入类(mixin classes)。混入类是 Python 中非常实用的技术。它能让你在不改变类原有代码的情况下,为类添加额外的功能。这很棒,因为它有助于保持代码的模块化,便于管理。
混入类是一种特殊类型的类。其主要目的是提供一些可以被其他类继承的功能。不过,混入类并非单独使用。你不会直接创建混入类的实例。相反,你可以用它以一种可控且可预测的方式为其他类添加特定功能。这是多重继承(multiple inheritance)的一种形式,即一个类可以从多个父类继承。
现在,让我们在 tableformat.py
文件中实现两个混入类。首先,在编辑器中打开该文件。你可以在终端中运行以下命令来完成此操作:
cd ~/project
code tableformat.py
文件打开后,在文件末尾、现有函数之前添加以下类定义:
class ColumnFormatMixin:
formats = []
def row(self, rowdata):
rowdata = [(fmt % d) for fmt, d in zip(self.formats, rowdata)]
super().row(rowdata)
这个 ColumnFormatMixin
类提供列格式化功能。formats
类变量是一个列表,用于存储格式代码。这些代码用于格式化每列的数据。row()
方法接收行数据,将格式代码应用于行中的每个元素,然后使用 super().row(rowdata)
将格式化后的行数据传递给父类。
接下来,添加另一个混入类,使表格标题以大写形式显示:
class UpperHeadersMixin:
def headings(self, headers):
super().headings([h.upper() for h in headers])
这个 UpperHeadersMixin
类将标题文本转换为大写。它接收标题列表,将每个标题转换为大写,然后使用 super().headings()
将修改后的标题传递给父类的 headings()
方法。
让我们测试一下新的混入类。运行一些 Python 代码,看看它们是如何工作的。
python3 -c "
from tableformat import TextTableFormatter, ColumnFormatMixin, portfolio, print_table
class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter):
formats = ['%s', '%d', '%0.2f']
formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"
运行此代码时,你应该会看到格式良好的输出。由于 ColumnFormatMixin
提供的格式化功能,价格列将具有一致的小数位数。
name shares price
---------- ---------- ----------
AA 100 32.20
IBM 50 91.10
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.10
IBM 100 70.44
现在,让我们尝试使用 UpperHeadersMixin
。运行以下代码:
python3 -c "
from tableformat import TextTableFormatter, UpperHeadersMixin, portfolio, print_table
class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter):
pass
formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"
此代码应将标题显示为大写。
NAME SHARES PRICE
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
注意,在混入类中,我们使用了 super().method()
。这被称为“协作式继承(cooperative inheritance)”。在协作式继承中,继承链中的每个类都协同工作。当一个类调用 super().method()
时,它是在请求链中的下一个类执行其任务。这样,一系列类可以各自为整个过程添加自己的行为。
继承顺序非常重要。当我们定义 class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter)
时,Python 首先在 ColumnFormatMixin
中查找方法,然后在 TextTableFormatter
中查找。因此,当在 ColumnFormatMixin
中调用 super().row()
时,它指的是 TextTableFormatter.row()
。
我们甚至可以将两个混入类结合使用。运行以下代码:
python3 -c "
from tableformat import TextTableFormatter, ColumnFormatMixin, UpperHeadersMixin, portfolio, print_table
class PortfolioFormatter(ColumnFormatMixin, UpperHeadersMixin, TextTableFormatter):
formats = ['%s', '%d', '%0.2f']
formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"
此代码将同时显示大写标题和格式化后的数字。
NAME SHARES PRICE
---------- ---------- ----------
AA 100 32.20
IBM 50 91.10
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.10
IBM 100 70.44
在下一步中,我们将通过增强 create_formatter()
函数,让这些混入类更易于使用。
混入类是 Python 中强大的特性,但对于初学者来说可能有点棘手,因为它涉及多重继承,这可能会变得相当复杂。在这一步中,我们将通过改进 create_formatter()
函数,让用户使用起来更加轻松。这样,用户就不必过多担心多重继承的细节。
首先,你需要打开 tableformat.py
文件。你可以在终端中运行以下命令来完成此操作。cd
命令用于将目录更改为你的项目文件夹,code
命令用于在代码编辑器中打开 tableformat.py
文件。
cd ~/project
code tableformat.py
文件打开后,找到 create_formatter()
函数。目前,它的代码如下:
def create_formatter(name):
"""
Create an appropriate formatter based on the name.
"""
if name == 'text':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise RuntimeError(f'Unknown format {name}')
这个函数接受一个名称作为参数,并返回相应的格式化器。但我们希望让它更灵活。我们将对其进行修改,使其能够接受混入类的可选参数。
将现有的 create_formatter()
函数替换为下面增强后的版本。这个新函数允许你指定列格式以及是否将标题转换为大写。
def create_formatter(name, column_formats=None, upper_headers=False):
"""
Create a formatter with optional enhancements.
Parameters:
name : str
Name of the formatter ('text', 'csv', 'html')
column_formats : list, optional
List of format strings for column formatting
upper_headers : bool, optional
Whether to convert headers to uppercase
"""
if name == 'text':
formatter_cls = TextTableFormatter
elif name == 'csv':
formatter_cls = CSVTableFormatter
elif name == 'html':
formatter_cls = HTMLTableFormatter
else:
raise RuntimeError(f'Unknown format {name}')
## Apply mixins if requested
if column_formats and upper_headers:
class CustomFormatter(ColumnFormatMixin, UpperHeadersMixin, formatter_cls):
formats = column_formats
return CustomFormatter()
elif column_formats:
class CustomFormatter(ColumnFormatMixin, formatter_cls):
formats = column_formats
return CustomFormatter()
elif upper_headers:
class CustomFormatter(UpperHeadersMixin, formatter_cls):
pass
return CustomFormatter()
else:
return formatter_cls()
这个增强后的函数首先根据 name
参数确定基本的格式化器类。然后,根据是否提供了 column_formats
和 upper_headers
,创建一个包含适当混入类的自定义格式化器类。最后,返回自定义格式化器类的一个实例。
现在,让我们使用不同的选项组合来测试这个增强后的函数。
首先,让我们尝试使用列格式化。在终端中运行以下命令。这个命令从 tableformat.py
文件中导入必要的函数和数据,创建一个带有列格式化的格式化器,然后使用该格式化器打印一个表格。
python3 -c "
from tableformat import create_formatter, portfolio, print_table
formatter = create_formatter('text', column_formats=['%s', '%d', '%0.2f'])
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"
你应该会看到列已格式化的表格。输出如下:
name shares price
---------- ---------- ----------
AA 100 32.20
IBM 50 91.10
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.10
IBM 100 70.44
接下来,让我们尝试使用大写标题。运行以下命令:
python3 -c "
from tableformat import create_formatter, portfolio, print_table
formatter = create_formatter('text', upper_headers=True)
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"
你应该会看到带有大写标题的表格。输出如下:
NAME SHARES PRICE
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
最后,让我们将两个选项结合起来。运行以下命令:
python3 -c "
from tableformat import create_formatter, portfolio, print_table
formatter = create_formatter('text', column_formats=['%s', '%d', '%0.2f'], upper_headers=True)
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"
这应该会显示一个列已格式化且标题为大写的表格。输出如下:
NAME SHARES PRICE
---------- ---------- ----------
AA 100 32.20
IBM 50 91.10
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.10
IBM 100 70.44
这个增强后的函数也适用于其他类型的格式化器。例如,让我们尝试使用 CSV 格式化器。运行以下命令:
python3 -c "
from tableformat import create_formatter, portfolio, print_table
formatter = create_formatter('csv', column_formats=['\\"%s\\"', '%d', '%0.2f'])
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"
这应该会生成列已格式化的 CSV 输出。输出如下:
name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44
通过增强 create_formatter()
函数,我们创建了一个用户友好的 API。现在,用户可以轻松使用混入类,而不必了解多重继承的复杂细节。这让他们能够根据自己的需求灵活定制格式化器。
在本次实验中,你学习了 Python 中的混入类(mixin classes)和协作式继承(cooperative inheritance),它们是在不修改现有代码的情况下扩展类功能的强大技术。你探索了一些关键概念,例如理解单继承的局限性、创建具有特定功能的混入类,以及使用 super()
进行协作式继承来构建方法链。
这些技术对于编写可维护和可扩展的 Python 代码非常有价值,尤其适用于框架和库。它们允许你提供定制点,而无需用户重写现有代码,并且能够将多个混入类组合起来以实现复杂的行为,同时将继承的复杂性隐藏在用户友好的 API 中。