【问题标题】:How to write a Python ABC with a concrete initializer in python 2.6 to 3.5?如何在 python 2.6 到 3.5 中编写带有具体初始化程序的 Python ABC?
【发布时间】:2014-08-09 17:11:56
【问题描述】:

背景

我有一个具有相对复杂的类层次结构的 python 应用程序。它需要与 python 2.6 到 python 3.5 一起工作(我知道,范围很大!),而且我在 ABC 方面遇到了特别的问题。 我正在使用six 库的with_metaclass 来减轻一些伤害,但这仍然存在问题。

一组特定的课程给我带来了麻烦。以下是它的简化形式:

from abc import ABCMeta
from six import with_metaclass

# SomeParentABC is another ABC, in case it is relevant
class MyABC(with_metaclass(ABCMeta, SomeParentABC)):
    def __init__(self, important_attr):
        self.important_attr = important_attr
    def gamma(self):
         self.important_attr += ' gamma'

class MyChild1(MyABC):
    def __repr__(self):
        return "MyChild1(imporant_attr=%s)" % important_attr
    def alpha(self):
         self.important_attr += ' alpha'

class MyChild2(MyABC):
    def __repr__(self):
        return "MyChild2(imporant_attr=%s)" % important_attr
    def beta(self):
         self.important_attr += ' beta'

MyABC 中捆绑了许多类似 gamma 的函数,以及一些特定于子类的函数,例如 alphabeta。我希望MyABC 的所有子类都继承相同的__init__gamma 属性,然后叠加它们自己的特定特征。

问题

问题在于,为了让MyChild1MyChild2 共享__init__ 的代码,MyABC 需要有一个具体的初始化程序。 在 Python 3 中,一切正常,但在 Python 2 中,当初始化程序具体时,我在实例化 MyABC 时无法获得 TypeErrors

我的测试套件中有一个片段看起来像这样

def test_MyABC_really_is_abstract():
    try:
        MyABC('attr value')
    # ideally more sophistication here to get the right kind of TypeError,
    # but I've been lazy for now
    except TypeError:
        pass
    else:
        assert False

不知何故,在 Python 2.7(我假设是 2.6,但没有费心检查)中,这个测试失败了。

MyABC 没有任何其他抽象属性,但是实例化一个具有gamma 而没有alphabeta 的类是没有意义的。 目前,我通过在 MyChild1MyChild2 中复制 __init__ 函数来解决 DRY 违规问题,但随着时间的推移,这变得越来越繁重。

如何在不使其可实例化的情况下为 Python 2 ABC 提供具体的初始化程序,同时保持 Python 3 的兼容性? 换句话说,我想在 Python 2 和 Python 3 中尝试实例化 MyABC 以抛出 TypeErrors,但它只在 Python 3 中抛出它们。

with_metaclass

我相信在这里查看with_metaclass 的代码是相关的。 这是根据six 项目的现有许可和版权提供的,(c) 2010-2014 Bejamin Peterson

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(meta):
        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)
    return type.__new__(metaclass, 'temporary_class', (), {})

【问题讨论】:

  • 在 Python 2 中抛出 TypeError 时的完整回溯是什么?
  • @MartijnPieters 我不确定你在这里要求什么。 Python2 中没有抛出TypeError。这正是问题所在:我想要一个被抛出,因为MyABC 是抽象的。
  • 对,当时还不清楚;您希望它会被抛出,但事实并非如此。
  • type.__new__处理;我在这里看不到 __new__ 的任何覆盖; SomeParentABC 可能会这样做吗?
  • @MartijnPieters 我刚刚编辑了问题以包含with_metaclass 代码,它确实编辑了__new__。我承认这让我读起来头疼。

标签: python python-2.7 python-3.x unit-testing abc


【解决方案1】:

six.with_metaclass() 元类可能与 ABC 不兼容,因为它会覆盖 type.__new__;这可能会干扰测试具体方法的正常程序。

尝试改用@six.add_metaclass() class decorator

从 abc 导入 ABCMeta from 6 import add_metaclass

@add_metaclass(ABCMeta)
class MyABC(SomeParentABC):
    def __init__(self, important_attr):
        self.important_attr = important_attr
    def gamma(self):
         self.important_attr += ' gamma'

演示:

>>> from abc import ABCMeta, abstractmethod
>>> from six import add_metaclass
>>> @add_metaclass(ABCMeta)
... class MyABC(object):
...     @abstractmethod
...     def gamma(self): pass
... 
>>> MyABC()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyABC with abstract methods gamma

请注意,您确实需要有没有具体实现的抽象方法才能提升TypeError

【讨论】:

  • 如果方向正确,这绝对是有帮助的,但我仍然能够实例化一个使用@add_metaclass(ABCMeta) 的类。具体来说,如果我用add_metaclass 装饰并从object 继承,那么MyABC('some value') 仍然有效。
  • @sirosen:我不小心离开了with_metaclass() 通话;现已删除。
  • @sirosen:也就是说,我无法使用six.with_metaclass 重现您的案例。
  • 我不认为with_metaclass 的使用是问题所在。在 python 2.7 和 3.2(我的本地测试版本)中,如果 MyABC 与上述相同,我仍然可以实例化它,但直接继承自 object。是否需要有抽象属性或抽象方法来引发TypeError? (另外,非常感谢您为此花费大量时间和精力!)
  • @sirosen:是的,我假设你的 parent ABC 元有这些。
猜你喜欢
  • 1970-01-01
  • 2014-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多