以兼容 Python 2.7 和 Python 3.5 的方式使用 abc.ABCMeta
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35673474/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using abc.ABCMeta in a way it is compatible both with Python 2.7 and Python 3.5
提问by Tatiana Al-Chueyr
I'd like to create a class which has abc.ABCMeta
as a metaclass and is compatible both with Python 2.7 and Python 3.5. Until now, I only succeeded doing this either on 2.7 or on 3.5 - but never on both versions simultaneously. Could someone give me a hand?
我想创建一个具有abc.ABCMeta
元类并且与 Python 2.7 和 Python 3.5 兼容的类。到目前为止,我只在 2.7 或 3.5 上成功地做到了这一点——但从来没有同时在两个版本上做到这一点。有人可以帮我一把吗?
Python 2.7:
Python 2.7:
import abc
class SomeAbstractClass(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def do_something(self):
pass
Python 3.5:
蟒蛇 3.5:
import abc
class SomeAbstractClass(metaclass=abc.ABCMeta):
@abc.abstractmethod
def do_something(self):
pass
Testing
测试
If we run the following test using the suitable version of the Python interpreter (Python 2.7 -> Example 1, Python 3.5 -> Example 2), it succeeds in both scenarios:
如果我们使用合适的 Python 解释器版本(Python 2.7 -> Example 1,Python 3.5 -> Example 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)
Problem
问题
While running the test using Python 3.5, the expected behavior doesn't happen (TypeError
is not raised while instantiating SomeAbstractClass
):
在使用 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
----------------------------------------------------------------------
Whereas running the test using Python 2.7 raises a SyntaxError
:
而使用 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
采纳答案by vaultah
You could use six.add_metaclass
or six.with_metaclass
:
您可以使用six.add_metaclass
或six.with_metaclass
:
import abc, six
@six.add_metaclass(abc.ABCMeta)
class SomeAbstractClass():
@abc.abstractmethod
def do_something(self):
pass
six
is a Python 2 and 3 compatibility library. You can install it by running pip install six
or by downloading the latest version of six.py
to your project directory.
six
是一个Python 2 和 3 兼容库。您可以通过运行pip install six
或将最新版本下载six.py
到您的项目目录来安装它。
For those of you who prefer future
over six
, the relevant function is future.utils.with_metaclass
.
对于那些喜欢future
以上的人six
,相关功能是future.utils.with_metaclass
。
回答by Aaron Hall
Using abc.ABCMeta in a way it is compatible both with Python 2.7 and Python 3.5
以兼容 Python 2.7 和 Python 3.5 的方式使用 abc.ABCMeta
If we were only using Python 3 (this is new in 3.4) we could do:
如果我们只使用 Python 3(这是3.4 中的新功能),我们可以这样做:
from abc import ABC
and inherit from ABC
instead of object
. That is:
并继承自ABC
而不是object
. 那是:
class SomeAbstractClass(ABC):
...etc
You still don't need an extra dependence (the six module) - you can use the metaclass to create a parent (this is essentially what the six module does in with_metaclass):
你仍然不需要额外的依赖(六模块)——你可以使用元类来创建父类(这本质上是六模块在 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
Or you could just do it in-place (but this is more messy, and doesn't contribute as much to reuse):
或者您可以就地完成(但这更麻烦,并且对重用的贡献不大):
# use ABCMeta compatible with Python 2 *and* 3
class SomeAbstractClass(abc.ABCMeta('ABC', (object,), {'__slots__': ()})):
@abc.abstractmethod
def do_something(self):
pass
Note that the signature looks a little messier than six.with_metaclass
but it is substantially the same semantics, without the extra dependence.
请注意,签名看起来有点混乱,six.with_metaclass
但它的语义基本相同,没有额外的依赖。
Either solution
任一解决方案
and now, when we try to instantiate without implementing the abstraction, we get precisely what we expect:
现在,当我们尝试在不实现抽象的情况下实例化时,我们得到的正是我们所期望的:
>>> 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
Note on __slots__ = ()
注意 __slots__ = ()
We just added empty __slots__
to the ABC convenience class in Python 3's standard library, and my answer is updated to include it.
我们刚刚__slots__
在 Python 3 标准库中的 ABC 便利类中添加了空,我的答案已更新以包含它。
Not having __dict__
and __weakref__
available in the ABC
parent allows users to deny their creation for child classes and save memory - there are no downsides, unless you were using __slots__
in child classes already and relying on implicit __dict__
or __weakref__
creation from the ABC
parent.
在父类中没有__dict__
和__weakref__
可用ABC
允许用户拒绝为子类创建并节省内存 - 没有缺点,除非您已经__slots__
在子类中使用并且依赖于父类的隐式__dict__
或__weakref__
创建ABC
。
The fast fix would be to declare __dict__
or __weakref__
in your child class as appropriate. Better (for __dict__
) might be to declare all your members explicitly.
快速解决方法是根据需要在您的子类中声明__dict__
或__weakref__
。更好的 (for __dict__
) 可能是明确声明您的所有成员。
回答by Rick supports Monica
I prefer Aaron Hall's answer, but it's important to note that in this case the comment that is part of the line:
我更喜欢Aaron Hall 的回答,但重要的是要注意,在这种情况下,注释是该行的一部分:
ABC = abc.ABCMeta('ABC', (object,), {}) # compatible with Python 2 *and* 3
...is every bit as important as the code itself. Without the comment, there is nothing to prevent some future cowboy down the road deleting the line and changing the class inheritance to:
...与代码本身一样重要。没有评论,没有什么可以阻止一些未来的牛仔删除该行并将类继承更改为:
class SomeAbstractClass(abc.ABC):
...thus breaking everything pre Python 3.4.
...从而打破了 Python 3.4 之前的一切。
One tweak that may be a little more explicit/clear to someone else- in that it is self documenting- regarding what it is you are trying to accomplish:
一个对其他人来说可能更明确/更清楚的调整 - 因为它是自我记录的 - 关于你想要完成的事情:
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
Strictly speaking, this isn't necessary to do, but it is absolutely clear, even without commentary, what is going on.
严格来说,这不是必须要做的,但即使没有评论,也绝对清楚发生了什么。
回答by FabienP
Just to say that you must explicitly pass str('ABC')
to abc.ABCMeta
in Python 2 if you use from __future__ import unicode_literals
.
只是说,你必须明确地传递str('ABC')
给abc.ABCMeta
在Python 2,如果你使用from __future__ import unicode_literals
。
Otherwise Python raises TypeError: type() argument 1 must be string, not unicode
.
否则 Python 会引发TypeError: type() argument 1 must be string, not unicode
.
See corrected code below.
请参阅下面的更正代码。
import sys
import abc
from __future__ import unicode_literals
if sys.version_info >= (3, 4):
ABC = abc.ABC
else:
ABC = abc.ABCMeta(str('ABC'), (), {})
This would not require a separate answer but sadly I cannot comment yours (need more rep).
这不需要单独的答案,但遗憾的是我不能评论你的(需要更多代表)。