以某种方式使用abc.ABCMeta,它与Python 2.7和Python 3.5兼容

Tat*_*eyr 45 python metaclass abc python-2.7 python-3.5

我想创建一个具有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
Run Code Online (Sandbox Code Playgroud)

Python 3.5:

import abc
class SomeAbstractClass(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def do_something(self):
        pass
Run Code Online (Sandbox Code Playgroud)

测试

如果我们使用合适版本的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)
Run Code Online (Sandbox Code Playgroud)

问题

在使用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

----------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

而使用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
Run Code Online (Sandbox Code Playgroud)

vau*_*tah 54

你可以使用six.add_metaclasssix.with_metaclass:

import abc, six

@six.add_metaclass(abc.ABCMeta)
class SomeAbstractClass():
    @abc.abstractmethod
    def do_something(self):
        pass
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我只是想知道,一旦项目完全迁移到python3,清理这6 /未来的混乱将是多么困难。 (2认同)

Aar*_*all 33

以某种方式使用abc.ABCMeta,它与Python 2.7和Python 3.5兼容

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

from abc import ABC
Run Code Online (Sandbox Code Playgroud)

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

class SomeAbstractClass(ABC):
    ...etc
Run Code Online (Sandbox Code Playgroud)

您仍然不需要额外的依赖(六个模块) - 您可以使用元类来创建父类(这实际上是六个模块在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
Run Code Online (Sandbox Code Playgroud)

或者您可以就地执行此操作(但这更麻烦,并且对重用的贡献不大):

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

    @abc.abstractmethod
    def do_something(self):
        pass
Run Code Online (Sandbox Code Playgroud)

请注意,签名看起来有点混乱,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
Run Code Online (Sandbox Code Playgroud)

注意 __slots__ = ()

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

由于没有__dict____weakref__可在ABC父母允许用户拒绝他们的创作儿童类和节省内存-没有缺点,除非你正在使用__slots__的子类已经和依赖隐__dict____weakref__从创建ABC父.

快速修复将适当地声明__dict____weakref__在您的子类中.更好(for __dict__)可能是明确声明所有成员.


Ric*_*ica 16

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

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

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

class SomeAbstractClass(abc.ABC):
Run Code Online (Sandbox Code Playgroud)

...因此在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
Run Code Online (Sandbox Code Playgroud)

严格地说,这没有必要这样做,但即使没有评论,它也是绝对清楚的.

  • 您可以更简洁地比较版本:`sys.version_info> =(3,4)`. (3认同)

Fab*_*enP 5

只是说,你必须明确地传递str('ABC')abc.ABCMeta在Python 2,如果你使用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'), (), {})
Run Code Online (Sandbox Code Playgroud)

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