Korzystanie z abc.ABCMeta w pewnym sensie jest kompatybilna zarówno z Pythonem 2.7 jak i Pythonem 3.5

Chciałbym stworzyć klasę, która ma abc.ABCMeta jako metaklasę i jest kompatybilna zarówno z Pythonem 2.7, jak i Pythonem 3.5. Do tej pory udało mi się to zrobić tylko na 2.7 lub 3.5 - ale nigdy na obu wersjach jednocześnie. Może mi ktoś pomóc?

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

Testowanie

Jeśli wykonamy następujący test przy użyciu odpowiedniej wersji interpretera Pythona (Python 2.7 - > przykład 1, Python 3.5 -> Przykład 2), odnosi sukces w obu scenariuszach:

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

Podczas uruchamiania testu przy użyciu Pythona 3.5, oczekiwane zachowanie nie występuje (TypeError nie jest wywoływane podczas tworzenia instancji 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

----------------------------------------------------------------------

Podczas gdy uruchomienie testu przy użyciu Pythona 2.7 podnosi SyntaxError:

 Python 2.7 incompatible
 Raises exception:
  File "/home/tati/sample_abc.py", line 24
    class SomeAbstractClass(metaclass=abc.ABCMeta):
                                     ^
 SyntaxError: invalid syntax
Author: Aaron Hall, 2016-02-27

4 answers

You could use six.add_metaclass lub six.with_metaclass:

import abc, six

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

six jest Biblioteka kompatybilności Pythona 2 i 3. Możesz go zainstalować uruchamiając pip install six lub pobierając najnowszą wersję six.py do katalogu projektu.

Dla tych z Was, którzy wolą future nad six, odpowiednią funkcją jest future.utils.with_metaclass.

 63
Author: vaultah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-28 00:34:01

Za pomocą abc.ABCMeta w pewnym sensie jest kompatybilny zarówno z Pythonem 2.7, jak i Pythonem 3.5

Gdybyśmy używali tylko Pythona 3 (to jest nowość w 3.4 ) moglibyśmy zrobić:

from abc import ABC

I dziedziczyć od ABC zamiast object. Czyli:

class SomeAbstractClass(ABC):
    ...etc

Nadal nie potrzebujesz dodatkowej zależności (moduł sześciu) - możesz użyć metaclass, aby utworzyć rodzica (jest to zasadniczo to, co moduł sześciu robi w 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 na miejscu (ale jest to bardziej brudne i nie przyczynia się tak bardzo do ponownego użycia): {]}

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

    @abc.abstractmethod
    def do_something(self):
        pass

Zauważ, że sygnatura wygląda trochę messier niż six.with_metaclass jest to jednak zasadniczo ta sama semantyka, bez dodatkowej zależności.

Albo rozwiązanie

A teraz, kiedy próbujemy utworzyć instancję bez implementacji abstrakcji, otrzymujemy dokładnie to, czego oczekujemy: {27]}

>>> 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

Uwaga na __slots__ = ()

We just added empty __slots__ do ABC wygody klasy w standardowej bibliotece Pythona 3, a moja odpowiedź jest zaktualizowana, aby ją dołączyć.

Brak __dict__ i __weakref__ dostępnych w rodzicu ABC pozwala użytkownikom odmówić ich tworzenia dla klas potomnych i zapisać pamięć - nie ma żadnych wad, chyba że używasz już __slots__ w klasach potomnych i polegasz na niejawnym __dict__ lub __weakref__ tworzeniu od rodzica ABC.

Szybką poprawką byłoby zadeklarowanie __dict__ lub __weakref__ w klasie dziecięcej, odpowiednio. Better (for __dict__) might być, aby zadeklarować wszystkich swoich członków wyraźnie.

 38
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-22 13:45:14

Wolę odpowiedź Aarona Halla , ale ważne jest, aby pamiętać, że w tym przypadku komentarz, który jest częścią linii:

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

...jest równie ważny jak sam kod. Bez komentarza nic nie stoi na przeszkodzie, aby jakiś przyszły kowboj usunął linię I zmienił dziedziczenie klasy na:

class SomeAbstractClass(abc.ABC):

...w ten sposób łamie wszystko przed Pythonem 3.4.

Jeden tweak, który może być nieco bardziej wyraźny / jasny dla kogoś innego - w tym, że jest jaźń dokumentowanie - odnośnie tego, co próbujesz osiągnąć:

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
Ściśle mówiąc, nie jest to konieczne, ale jest absolutnie jasne, nawet bez komentarza, co się dzieje.
 19
Author: Rick supports Monica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-14 14:59:33

Aby powiedzieć, że musisz jawnie przekazać str('ABC') do abc.ABCMeta w Pythonie 2, Jeśli używasz from __future__ import unicode_literals.

Inaczej Python podnosi TypeError: type() argument 1 must be string, not unicode.

Patrz poprawiony kod poniżej.

import sys
import abc
from __future__ import unicode_literals

if sys.version_info >= (3, 4):
    ABC = abc.ABC
else:
    ABC = abc.ABCMeta(str('ABC'), (), {})

Nie wymagałoby to osobnej odpowiedzi, ale niestety nie mogę skomentować twojego (potrzebuję więcej rep).

 5
Author: FabienP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-19 08:53:44