web-dev-qa-db-ja.com

abc.ABCMetaを、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

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
44

six.add_metaclass または six.with_metaclass を使用できます。

import abc, six

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

sixPython 2および3互換性ライブラリ です。 pip install sixを実行するか、six.pyの最新バージョンをプロジェクトディレクトリにダウンロードしてインストールできます。

futureよりもsixを好む人にとって、関連する関数は future.utils.with_metaclass です。

52
vaultah

Aaron Hallの答え が好きですが、この場合、行の一部であるコメントに注意することが重要です:

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

...コード自体と同じくらい重要です。コメントがなければ、将来のカウボーイが行を削除してクラスの継承を次のように変更するのを防ぐことはできません:

class SomeAbstractClass(abc.ABC):

...したがって、pre Python 3.4。

あなたが達成しようとしていることに関して、それは自己文書化であるという点で、他の誰かにもう少し明示的/明確であるかもしれない1つの微調整:

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

厳密に言えば、これは実行する必要はありませんが、コメントがなくても何が起こっているかは明確です。

15
Rick Teachey

_abc.ABCMeta_を使用する場合、Python 2)で明示的にstr('ABC')を_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'), (), {})
_

これには個別の回答は必要ありませんが、残念ながら私はあなたのものにコメントすることはできません(さらに担当者が必要です)。

4
FabienP