提交 b2c66b1a 编写于 作者: W wizardforcel

2.7.3

上级 9af998a2
......@@ -78,7 +78,7 @@ For most object types, eval(repr(object)) == object.
为了选取一个简单的示例,复数可以用两种几乎等价的方式来表示:直角坐标(虚部和实部)以及极坐标(模和角度)。有时直角坐标形式更加合适,而有时极坐标形式更加合适。复数以两种方式表示,而操作复数的函数可以处理每种表示,这样一个系统确实比较合理。
更重要的是,大型软件系统工程通常由许多人设计,并花费大量时间,需求的主题随时间而改变。在这样的环境中,每个人都事先同意数据表示的方案是不可能的。除了隔离使用和表示的数据抽象的界限,我们需要隔离不同设计方案的界限,以及允许不同方案在一个程序中共存。进一步,由于大型程序通常通过组合已存在的模块创建,这些模块在隔离中设计,我们需要一种惯例,让程序员将模块递增地组合为大型系统。也就是说,没有不需要重复设计或实现这些模块。
更重要的是,大型软件系统工程通常由许多人设计,并花费大量时间,需求的主题随时间而改变。在这样的环境中,每个人都事先同意数据表示的方案是不可能的。除了隔离使用和表示的数据抽象的界限,我们需要隔离不同设计方案的界限,以及允许不同方案在一个程序中共存。进一步,由于大型程序通常通过组合已存在的模块创建,这些模块会单独设计,我们需要一种惯例,让程序员将模块递增地组合为大型系统。也就是说,没有不需要重复设计或实现这些模块。
我们以最简单的复数示例开始。我们会看到,消息传递在维持“复数”对象的抽象概念时,如何让我们为复数的表示设计出分离的视角坐标和极坐标表示。我们会通过使用泛用选择器为复数定义算数函数(`add_complex``mul_complex`)来完成它。泛用选择器可访问复数的一部分,独立于数值表示的方式。所产生的复数系统包含两种不同类型的抽象界限。它们隔离了高阶操作和低阶表示。此外,也有一个垂直的界限,它使我们能够独立设计替代的表示。
......@@ -194,9 +194,9 @@ ComplexMA(1.0, 3.141592653589793)
我们到目前为止已定义的操作将不同的数据类型独立对待。所以,存在用于加法的独立的包,比如两个有理数或者两个复数。我们没有考虑到的是,定义类型界限之间的操作很有意义,比如将复数与有理数相加。我们经历了巨大的痛苦,引入了程序中各个部分的界限,便于让它们可被独立开发和理解。
我们希望以某种精确控制的方式引入跨类型的操作。便于在不严重违反抽象界限的情况下支持它们。在我们希望的结果之间可能有些矛盾:我们希望能够将有理数与复数相加,也希望能够使用泛用的`add`函数,正确处理所有数值类型。同时,我们希望隔离对复数和有理数的关注,为了维持程序的模块化。
我们希望以某种精确控制的方式引入跨类型的操作。便于在不严重违反抽象界限的情况下支持它们。在我们希望的结果之间可能有些矛盾:我们希望能够将有理数与复数相加,也希望能够使用泛用的`add`函数,正确处理所有数值类型。同时,我们希望隔离复数和有理数的细节,来维持程序的模块化。
让我们使用 Python 内建的对象熊重新审视有理数的实现。像之前一样,我们在较低层级将有理数储存为分子和分母。
让我们使用 Python 内建的对象系统重新编写有理数的实现。像之前一样,我们在较低层级将有理数储存为分子和分母。
```py
>>> from fractions import gcd
......@@ -279,9 +279,9 @@ ComplexRI(3.0, 0)
Rational(13, 6)
```
**数据向编程。**我们基于字典的`add`实现并不是特定于加法的;它不包含任何加法的直接逻辑。它只实现了加法操作,因为我们碰巧将`implementations`字典和函数放到一起来执行加法。
**数据向编程。**我们基于字典的`add`实现并不是特定于加法的;它不包含任何加法的直接逻辑。它只实现了加法操作,因为我们碰巧将`implementations`字典和函数放到一起来执行加法。
更通用的泛用算数操作版本会将任意运算符作用于任意类型,并且使用字典来储存多种组合的实现。这个完全泛用的实现方法的方式叫做数据向编程。在我们这里,我们可以实现泛用加法和乘法,而不带任何重复的逻辑。
更通用的泛用算数操作版本会将任意运算符作用于任意类型,并且使用字典来储存多种组合的实现。这个完全泛用的实现方法的方式叫做数据向编程。在我们这里,我们可以实现泛用加法和乘法,而不带任何重复的逻辑。
```py
>>> def apply(operator_name, x, y):
......@@ -318,5 +318,65 @@ ComplexRI(3.0, 0)
ComplexMA(5.0, 1)
```
这个数据导向的方式管理了跨类型运算符的复杂性,但是十分麻烦。使用这个一个系统,引入新类型的开销不仅仅是为类型编程方法,还有实现跨类型操作的函数的构造和安装。这个负担比如定义类型本身的操作需要更多代码。
当类型分发机制和数据导向编程的确能创造泛用函数的递增实现时,它们就不能有效隔离实现的细节。独立数值类型的实现者需要在编程跨类型操作时考虑其他类型。组合有理数和复数严格上并不是每种类型的范围。在类型中制定一致的责任分工政策,在带有多种类型和跨类型操作的系统设计中是大势所趋。
**强制转换。**在完全不相关的类型执行完全不相关的操作的一般情况中,实现显式的跨类型操作,尽管可能非常麻烦,是人们所希望的最佳方案。幸运的是,我们有时可以通过利用类型系统中隐藏的额外结构来做得更好。不同的数据类通常并不是完全独立的,可能有一些方式,一个类型的对象通过它会被看做另一种类型的对象。这个过程叫做强制转换。例如,如果我们被要求将一个有理数和一个复数通过算术来组合,我们可以将有理数看做虚部为零的复数。通过这样做,我们将问题转换为组合两个复数的问题,这可以通过`add_complex``mul_complex`由经典的方法处理。
通常,我们可以通过设计强制转换函数来实现这个想法。强制转换函数将一个类型的对象转换为另一个类型的等价对象。这里是一个典型的强制转换函数,它将有理数转换为虚部为零的复数。
```py
>>> def rational_to_complex(x):
return ComplexRI(x.numer/x.denom, 0)
```
现在,我们可以定义强制转换函数的字典。这个字典可以在更多的数值类型引入时扩展。
```py
>>> coercions = {('rat', 'com'): rational_to_complex}
```
任意类型的数据对象不可能转换为每个其它类型的对象。例如,没有办法将任意的复数强制转换为有理数,所以在`coercions`字典中应该没有这种转换的实现。
使用`coercions`字典,我们可以编写叫做`coerce_apply`的函数,它试图将参数强制转换为相同类型的值,之后仅仅调用运算符。`coerce_apply `的实现字典不包含任何跨类型运算符的实现。
```py
>>> def coerce_apply(operator_name, x, y):
tx, ty = type_tag(x), type_tag(y)
if tx != ty:
if (tx, ty) in coercions:
tx, x = ty, coercions[(tx, ty)](x)
elif (ty, tx) in coercions:
ty, y = tx, coercions[(ty, tx)](y)
else:
return 'No coercion possible.'
key = (operator_name, tx)
return coerce_apply.implementations[key](x, y)
```
`coerce_apply``implementations`仅仅需要一个类型标签,因为它们假设两个值都共享相同的类型标签。所以,我们仅仅需要四个实现来支持复数和有理数上的泛用算数。
```py
>>> coerce_apply.implementations = {('mul', 'com'): mul_complex,
('mul', 'rat'): mul_rational,
('add', 'com'): add_complex,
('add', 'rat'): add_rational}
```
就地使用这些实现,`coerce_apply `可以代替`apply`
```py
>>> coerce_apply('add', ComplexRI(1.5, 0), Rational(3, 2))
ComplexRI(3.0, 0)
>>> coerce_apply('mul', Rational(1, 2), ComplexMA(10, 1))
ComplexMA(5.0, 1.0)
```
这个强制转换的模式比起显式定义跨类型运算符的方式具有优势。虽然我们让然需要编程强制函数来关联类型,我们仅仅需要对每对类型编写一个函数,而不是为每个类型组合和每个泛用方法编写不同的函数。我们要继续做的事情是,类型间的合理转换仅仅依赖于类型本身,而不是要调用的特定操作。
强制转换的扩展会带来进一步的优势。一些更复杂的强制转换模式并不仅仅视图将一个类型强制转换为另一个,而是将两个不同类型强制转换为第三个。考虑菱形和长方形:每个都不是另一个的特例,但是两个都可以看做平行四边形。另一个强制转换的扩展是迭代强制转换,其中一个数据类型通过媒介类型被强制转换为另一种。考虑一个整数可以转换为一个实数,通过首先转换为有理数,接着将有理数转换为实数。这种方式的链式强制转换降低了程序所需的转换函数总数。
虽然它具有优势,强制转换也有潜在的缺陷。例如,强制转换函数在调用时会丢失信息。在我们的例子中,有理数是精确表示,但是当它们转换为复数时会变得近似。
一些编程语言拥有内建的强制转换函数。实际上,Python 的早期版本拥有对象上的`__coerce__`特殊方法。最后,内建强制转换系统的复杂性并不能支持它的使用,所以被移除了。反之,特定的操作按需强制转换它们的参数。运算符被实现为用户定义类上的特殊方法,比如`__add__``__mul__`。这完全取决于你,取决于用户来决定是否使用类型分发,数据导向编程,消息传递,或者强制转换来在你的程序中实现泛用函数。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册