python3 create python python-2.7 metaclass python-3.5 abc

create - python interface



Usando abc.ABCMeta de una manera es compatible tanto con Python 2.7 como con Python 3.5 (4)

Me gustaría crear una clase que tenga abc.ABCMeta como una metaclase y sea compatible tanto con Python 2.7 como con Python 3.5. Hasta ahora, solo lo conseguí en 2.7 o en 3.5, pero nunca en ambas versiones simultáneamente. ¿Podría alguien darme una mano?

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

Pruebas

Si ejecutamos la siguiente prueba utilizando la versión adecuada del intérprete de Python (Python 2.7 -> Ejemplo 1, Python 3.5 -> Ejemplo 2), tiene éxito en ambos escenarios:

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)

Problema

Al ejecutar la prueba con Python 3.5, el comportamiento esperado no ocurre ( TypeError no se SomeAbstractClass instanciar 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 ----------------------------------------------------------------------

Mientras que ejecutar la prueba usando Python 2.7 plantea un SyntaxError :

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


Usando abc.ABCMeta de una manera es compatible tanto con Python 2.7 como con Python 3.5

Si solo utilizáramos Python 3 (esto es nuevo en 3.4 ) podríamos hacer:

from abc import ABC

y heredar de ABC lugar de object . Es decir:

class SomeAbstractClass(ABC): ...etc

Todavía no necesita una dependencia adicional (el módulo de seis): puede usar la metaclase para crear un elemento primario (esto es esencialmente lo que hace el módulo de seis en 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

O simplemente puede hacerlo en el lugar (pero esto es más complicado y no contribuye tanto como para volver a usarlo):

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

Tenga en cuenta que la firma parece un poco más desordenada que six.with_metaclass pero es sustancialmente la misma semántica, sin la dependencia adicional.

Cualquiera de las soluciones

y ahora, cuando intentamos crear instancias sin implementar la abstracción, obtenemos exactamente lo que esperamos:

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

Nota sobre __slots__ = ()

Acabamos de agregar __slots__ vacías a la clase de conveniencia ABC en la biblioteca estándar de Python 3, y mi respuesta se actualiza para incluirla.

No tener __dict__ y __weakref__ disponibles en el padre ABC permite a los usuarios denegar su creación para clases secundarias y guardar memoria; no hay inconvenientes, a menos que estuviera usando __slots__ en clases secundarias y confiando en la __dict__ implícita __dict__ o __weakref__ del padre ABC .

La solución rápida sería declarar __dict__ o __weakref__ en su clase secundaria, según corresponda. Mejor (para __dict__ ) podría ser declarar explícitamente a todos sus miembros.


Prefiero la respuesta de Aaron Hall , pero es importante tener en cuenta que en este caso el comentario es parte de la línea:

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

... es tan importante como el código en sí. Sin el comentario, no hay nada que evite que algún futuro vaquero sea eliminado de la lista y cambie la herencia de la clase a:

class SomeAbstractClass(abc.ABC):

... rompiendo todo antes de Python 3.4.

Un ajuste que puede ser un poco más explícito / claro para otra persona, en el sentido de que se documenta a sí mismo, con respecto a lo que está tratando de lograr:

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

Estrictamente hablando, esto no es necesario, pero está absolutamente claro, incluso sin comentarios, lo que está sucediendo.



Solo para decir que debe pasar explícitamente str(''ABC'') a abc.ABCMeta en Python 2 si usa from __future__ import unicode_literals .

De lo contrario, Python plantea TypeError: type() argument 1 must be string, not unicode .

Vea el código corregido a continuación.

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

Esto no requeriría una respuesta por separado, pero lamentablemente no puedo comentar el suyo (necesito más representantes).