58

我想创建一个具有abc.ABCMeta元类并且与 Python 2.7 和 Python 3.5 兼容的类。到目前为止,我只在 2.7 或 3.5 上成功地做到了这一点——但从来没有同时在两个版本上。有人可以帮我一把吗?

蟒蛇 2.7:

import abc
class SomeAbstractClass(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def do_something(self):
        pass

蟒蛇 3.5:

import abc
class SomeAbstractClass(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def do_something(self):
        pass

测试

如果我们使用合适版本的 Python 解释器(Python 2.7 -> 示例 1,Python 3.5 -> 示例 2)运行以下测试,它在两种情况下都成功:

import unittest
class SomeAbstractClassTestCase(unittest.TestCase):
    def test_do_something_raises_exception(self):
        with self.assertRaises(TypeError) as error:
            processor = SomeAbstractClass()
        msg = str(error.exception)
        expected_msg = "Can't instantiate abstract class SomeAbstractClass with abstract methods do_something"
        self.assertEqual(msg, expected_msg)

问题

使用 Python 3.5 运行测试时,没有发生预期的行为(TypeError实例化时不会引发SomeAbstractClass):

======================================================================
FAIL: test_do_something_raises_exception (__main__.SomeAbstractClassTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tati/sample_abc.py", line 22, in test_do_something_raises_exception
    processor = SomeAbstractClass()
AssertionError: TypeError not raised

----------------------------------------------------------------------

而使用 Python 2.7 运行测试会引发SyntaxError

 Python 2.7 incompatible
 Raises exception:
  File "/home/tati/sample_abc.py", line 24
    class SomeAbstractClass(metaclass=abc.ABCMeta):
                                     ^
 SyntaxError: invalid syntax
4

4 回答 4

66

您可以使用six.add_metaclasssix.with_metaclass

import abc, six

@six.add_metaclass(abc.ABCMeta)
class SomeAbstractClass():
    @abc.abstractmethod
    def do_something(self):
        pass

six是一个Python 2 和 3 兼容库。您可以通过运行pip install six或下载最新版本six.py到您的项目目录来安装它。

对于那些更喜欢future的人six,相关的功能是future.utils.with_metaclass

于 2016-02-27T18:13:28.263 回答
39

以与 Python 2.7 和 Python 3.5 兼容的方式使用 abc.ABCMeta

如果我们只使用 Python 3(这是3.4 中的新功能),我们可以这样做:

from abc import ABC

并继承自ABC而不是object. 那是:

class SomeAbstractClass(ABC):
    ...etc

您仍然不需要额外的依赖(六模块) - 您可以使用元类创建父级(这本质上是六模块在 with_metaclass 中所做的):

import abc

# compatible with Python 2 *and* 3:
ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()}) 

class SomeAbstractClass(ABC):

    @abc.abstractmethod
    def do_something(self):
        pass

或者您可以就地执行它(但这更混乱,并且对重用的贡献不大):

# use ABCMeta compatible with Python 2 *and* 3 
class SomeAbstractClass(abc.ABCMeta('ABC', (object,), {'__slots__': ()})):

    @abc.abstractmethod
    def do_something(self):
        pass

请注意,签名看起来有点混乱,six.with_metaclass但它的语义基本相同,没有额外的依赖。

任一解决方案

现在,当我们尝试在不实现抽象的情况下进行实例化时,我们得到的正是我们所期望的:

>>> SomeAbstractClass()
Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    SomeAbstractClass()
TypeError: Can't instantiate abstract class SomeAbstractClass with abstract methods do_something

注意事项__slots__ = ()

我们刚刚在 Python 3 的标准库中的 ABC 便利类中添加了空,我的答案已更新为包含它。__slots__

在父类中没有__dict____weakref__可用ABC允许用户拒绝他们为子类创建并节省内存 - 没有缺点,除非您已经__slots__在子类中使用并依赖于隐式__dict____weakref__ABC父类创建。

快速解决方法是酌情声明__dict____weakref__在您的子类中。更好的 (for __dict__) 可能是明确声明所有成员。

于 2016-07-29T22:30:16.277 回答
20

我更喜欢Aaron Hall 的回答,但重要的是要注意,在这种情况下,注释是该行的一部分:

ABC = abc.ABCMeta('ABC', (object,), {}) # compatible with Python 2 *and* 3 

...与代码本身一样重要。没有评论,没有什么可以阻止未来的牛仔删除该行并将类继承更改为:

class SomeAbstractClass(abc.ABC):

...因此破坏了 Python 3.4 之前的所有内容。

一个对其他人来说可能更明确/更清楚的调整——因为它是自我记录的——关于你想要完成的事情:

import sys
import abc

if sys.version_info >= (3, 4):
    ABC = abc.ABC
else:
    ABC = abc.ABCMeta('ABC', (), {})

class SomeAbstractClass(ABC):
    @abc.abstractmethod
    def do_something(self):
        pass

严格来说,这不是必须做的,但绝对清楚,即使没有评论,发生了什么。

于 2017-01-12T20:14:42.860 回答
5

只是说如果你str('ABC')使用.abc.ABCMetafrom __future__ import unicode_literals

否则 Python 会引发TypeError: type() argument 1 must be string, not unicode.

请参阅下面的更正代码。

import sys
import abc
from __future__ import unicode_literals

if sys.version_info >= (3, 4):
    ABC = abc.ABC
else:
    ABC = abc.ABCMeta(str('ABC'), (), {})

这不需要单独的答案,但遗憾的是我不能评论你的(需要更多代表)。

于 2017-06-19T08:53:44.840 回答