我想创建一个具有abc.ABCMeta
作为元类的类,并且与Python 2.7和Python 3.5兼容。到目前为止,我只能在2.7或3.5上成功实现这一点 - 但从不同时在两个版本上。有人可以帮我一把吗?
Python 2.7:
import abc
class SomeAbstractClass(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def do_something(self):
pass
Python 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
答案 0 :(得分:49)
您可以使用six.add_metaclass
或six.with_metaclass
:
import abc, six
@six.add_metaclass(abc.ABCMeta)
class SomeAbstractClass():
@abc.abstractmethod
def do_something(self):
pass
six
是Python 2 and 3 compatibility library。您可以通过运行pip install six
或将最新版本的six.py
下载到项目目录来安装它。
对于那些喜欢future
而不是six
的人,相关的功能是future.utils.with_metaclass
。
答案 1 :(得分:29)
以某种方式使用abc.ABCMeta,它与Python 2.7和Python 3.5
兼容
如果我们只使用Python 3(这是new in 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__ = ()
我们{3}到Python 3标准库中的ABC便利类,我的答案已更新为包含它。
在__dict__
父级中没有__weakref__
和ABC
允许用户拒绝为子类创建并节省内存 - 没有缺点,除非您使用{{1已经在子类中,依赖于__slots__
父级的隐式__dict__
或__weakref__
创建。
快速解决方法是根据需要在您的子类中声明ABC
或__dict__
。更好(对于__weakref__
)可能是明确声明所有成员。
答案 2 :(得分:13)
我更喜欢Aaron Hall's answer,但重要的是要注意,在这种情况下,评论是该行的一部分:
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
严格地说,这没有必要这样做,但即使没有评论,它也是绝对清楚的。
答案 3 :(得分:3)
只是说如果您使用str('ABC')
,则必须在Python 2中明确地将abc.ABCMeta
传递给from __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'), (), {})
这不需要单独的答案,但遗憾的是我不能评论你的(需要更多代表)。