5.1 使用ABC可调用对象来进行设计

    在Python中有两种创建可调用对象的简单方式,如下所示。

    • 使用def语句创建一个函数。
    • 通过创建继承自collections.abc.Callable类的实例。

    也可以将一个变量赋值为lambda表达式。一个lambda表达式是一个小的匿名函数,其中只包含了一个表达式语句。我们不倾向于将lambda表达式保存在变量中,因为当使用了一个类似函数的、并未使用def语句定义的可调用对象,这种做法就会带来困惑。

    import collections.abc
    class Power1( collections.abc.Callable ):
      def call( self, x, n ):
        p= 1
        for i in range(n):
          p *= x
        return p
    pow1= Power1()

    以上的可调用对象包含了如下3个部分。

    • 类继承自abc.Callable。
    • 定义了call()方法。
    • 创建了类的实例pow1()``。

    算法的确不是很高效,我们稍后会优化这一点。

    一个完整的类定义显然是不必要的。为了逐步完成优化,比起把一个函数重构为可调用对象,从一个可调用对象作为开始更容易入手一些。

    像使用其他函数那样,现在可以使用刚刚定义的pow1()函数了。如下是如何在Python命令行中使用pow1()函数的示例。

    >>> pow1( 2, 0 )
    1
    >>> pow1( 2, 1 )
    2
    >>> pow1( 2, 2 )
    4
    >>> pow1( 2, 10 )
    1024

    我们使用了各种各样的参数值来构造可调用对象。如果仅仅是为了创建abc.Callable子类的对象,这样做是不必要的。然而,这样做可以帮助调试。考虑如下的定义。

    class Power2( collections.abc.Callable ):
      def _call( self, x, n ):
        p= 1
        for i in range(n):
          p *= x
        return p

    以上的类定义有一个错误并且不符合可调用基类的定义规则。

    是否找到了这个错误?如果没有,在本章最后会揭晓。

    当我们试图使用这个类创建实例时,会产生如下错误。

    >>> pow2= Power2()
    Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class Power2 with abstract
    methods call

    也许无法一下子发现错误的根源,但是可以通过调试来把它找出来。如果没有继承自collections.abc.Callable基类,将非常难调试。

    这里就是调试的难点,我们会跳过power3的代码。和power2一样,唯一不同的是在没有继承自collections.abc.Callable的前提下就开始了类定义:class power3

    当试图把power3作为一个类来使用时,由于此类并没有满足可调用的条件并且没有继承自abc.Callable,因此将会出现如下错误。

    >>> pow3= Power3()
    >>> pow3( 2, 5 )
    Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
    TypeError: 'Power3' object is not callable

    关于power3的类定义哪里出现了问题,以上错误只提供了少量的信息。power2的错误信息对找到问题的根源更有用一些。