以与 Python 2.7 和 Python 3.5 兼容的方式使用 abc.ABCMeta
Using abc.ABCMeta in a way it is compatible both with Python 2.7 and Python 3.5
我想创建一个 class,它以 abc.ABCMeta
作为元 class,并且与 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 进行测试,但预期的行为没有发生(实例化 SomeAbstractClass
时未引发 TypeError
):
======================================================================
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
您可以使用 six.add_metaclass
or 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
。
Using abc.ABCMeta in a way it is compatible both with Python 2.7 and Python 3.5
如果我们只使用 Python 3(这是 new in 3.4),我们可以这样做:
from abc import ABC
并继承自 ABC
而不是 object
。即:
class SomeAbstractClass(ABC):
...etc
你仍然不需要额外的依赖(第六模块)——你可以使用元class创建一个parent(这基本上就是第六模块在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
或者你可以这样做 in-place(但这更混乱,并且对重用的贡献不大):
# 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__ = ()
我们 just added empty __slots__
Python 3 的标准库中的 ABC 便利 class,我的答案已更新以包含它。
在 ABC
parent 中没有 __dict__
和 __weakref__
允许用户拒绝他们为 child class 和节省内存 - 没有缺点,除非您已经在 child class 中使用 __slots__
并且依赖于隐式 __dict__
或 __weakref__
从 [= 创建15=] parent。
快速解决方法是在 child class 中适当地声明 __dict__
或 __weakref__
。更好的方法(对于 __dict__
)可能是显式声明所有成员。
我更喜欢 ,但重要的是要注意,在这种情况下,注释是该行的一部分:
ABC = abc.ABCMeta('ABC', (object,), {}) # compatible with Python 2 *and* 3
...与代码本身一样重要。没有评论,没有什么可以阻止未来的牛仔删除该行并将 class 继承更改为:
class SomeAbstractClass(abc.ABC):
...因此破坏了 Python 3.4 之前的一切。
对其他人来说可能 explicit/clear 多一点的调整 - 因为它是自我记录的 - 关于你想要完成的事情:
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
严格来说,这不是必须要做的,但即使没有评论,也绝对清楚发生了什么。
只是说如果你使用 from __future__ import unicode_literals
.
,你必须在 Python 2 中显式地将 str('ABC')
传递给 abc.ABCMeta
否则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'), (), {})
这不需要单独回答,但遗憾的是我不能评论你的(需要更多代表)。
我想创建一个 class,它以 abc.ABCMeta
作为元 class,并且与 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 进行测试,但预期的行为没有发生(实例化 SomeAbstractClass
时未引发 TypeError
):
======================================================================
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
您可以使用 six.add_metaclass
or 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
。
Using abc.ABCMeta in a way it is compatible both with Python 2.7 and Python 3.5
如果我们只使用 Python 3(这是 new in 3.4),我们可以这样做:
from abc import ABC
并继承自 ABC
而不是 object
。即:
class SomeAbstractClass(ABC):
...etc
你仍然不需要额外的依赖(第六模块)——你可以使用元class创建一个parent(这基本上就是第六模块在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
或者你可以这样做 in-place(但这更混乱,并且对重用的贡献不大):
# 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__ = ()
我们 just added empty __slots__
Python 3 的标准库中的 ABC 便利 class,我的答案已更新以包含它。
在 ABC
parent 中没有 __dict__
和 __weakref__
允许用户拒绝他们为 child class 和节省内存 - 没有缺点,除非您已经在 child class 中使用 __slots__
并且依赖于隐式 __dict__
或 __weakref__
从 [= 创建15=] parent。
快速解决方法是在 child class 中适当地声明 __dict__
或 __weakref__
。更好的方法(对于 __dict__
)可能是显式声明所有成员。
我更喜欢
ABC = abc.ABCMeta('ABC', (object,), {}) # compatible with Python 2 *and* 3
...与代码本身一样重要。没有评论,没有什么可以阻止未来的牛仔删除该行并将 class 继承更改为:
class SomeAbstractClass(abc.ABC):
...因此破坏了 Python 3.4 之前的一切。
对其他人来说可能 explicit/clear 多一点的调整 - 因为它是自我记录的 - 关于你想要完成的事情:
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
严格来说,这不是必须要做的,但即使没有评论,也绝对清楚发生了什么。
只是说如果你使用 from __future__ import unicode_literals
.
str('ABC')
传递给 abc.ABCMeta
否则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'), (), {})
这不需要单独回答,但遗憾的是我不能评论你的(需要更多代表)。