Python :subclasse `tipo` para criar tipos especializados (e.g.uma "lista de int")
Pergunta
Eu estou tentando subclasse type
para criar uma classe, permitindo a construção de tipos especializados.exemplo:um ListType
:
>>> ListOfInt = ListType(list, value_type=int)
>>> issubclass(ListOfInt, list)
True
>>> issubclass(list, ListOfInt)
False
>>> # And so on ...
No entanto, este ListOfInt
nunca vai ser usado para criar instâncias !Eu só usá-lo como uma instância de type
que eu possa manipular comparar com outros tipos de ...Em particular, no meu caso eu preciso olhar-se para um adequado funcionamento, de acordo com o tipo de entrada, e eu preciso do tipo de conter mais precisão (como list of int
ou XML string
, etc ...).
Então aqui está o que eu encontrei :
class SpzType(type):
__metaclass__ = abc.ABCMeta
@classmethod
def __subclasshook__(cls, C):
return NotImplemented
def __new__(cls, base, **features):
name = 'SpzOf%s' % base.__name__
bases = (base,)
attrs = {}
return super(SpzType, cls).__new__(cls, name, bases, attrs)
def __init__(self, base, **features):
for name, value in features.items():
setattr(self, name, value)
O uso de abc
não é óbvio que o código acima ...no entanto, se eu quiser escrever uma subclasse ListType
como no exemplo em cima, então torna-se útil ...
A funcionalidade básica realmente funciona :
>>> class SimpleType(SpzType): pass
>>> t = SimpleType(int)
>>> issubclass(t, int)
True
>>> issubclass(int, t)
False
Mas quando tento verificar se t
é uma instância de SpzType
, Python freaks out :
>>> isinstance(t, SpzType)
TypeError: __subclasscheck__() takes exactly one argument (0 given)
Eu explorado com pdb.pm()
o que estava acontecendo e descobri que, o seguinte código gera o erro :
>>> SpzType.__subclasscheck__(SimpleType)
TypeError: __subclasscheck__() takes exactly one argument (0 given)
Estranho ?!Obviamente, há um argumento ...Então o que isso significa ?Alguma idéia ?Eu uso indevido abc
?
Solução 3
Graças ao comentário de kindall , refatorei o código para o seguinte:
class SpzType(abc.ABCMeta):
def __subclasshook__(self, C):
return NotImplemented
def __new__(cls, base, **features):
name = 'SpzOf%s' % base.__name__
bases = (base,)
attrs = {}
new_spz = super(SpzType, cls).__new__(cls, name, bases, attrs)
new_spz.__subclasshook__ = classmethod(cls.__subclasshook__)
return new_spz
def __init__(self, base, **features):
for name, value in features.items():
setattr(self, name, value)
Então, basicamente, SpzType
agora é uma subclasse de abc.ABCMeta
e subclasshook é implementado como um método de instância.Funciona muito bem e é (IMO) elegante !!!
EDIT: Houve uma coisa complicada ... porque __subclasshook__
precisa ser um método de classe, então eu tenho que chamar a função classmethod manualmente ... caso contrário, não funcionará se eu quiser implementar __subclasshook__
.
Outras dicas
Não tenho certeza do que você deseja alcançar.Talvez seja melhor usar o módulo collections
em vez de usar o abc
diretamente?
Há mais informações sobre classes de coleção genéricas em PEP 3119
O tipo de coisa que você deseja fazer provavelmente poderia ser feito mais facilmente usando uma função de fábrica de classe como a seguinte.Pelo menos para mim, é mais simples manter os vários níveis nos quais estou tentando operar.
def listOf(base, types={}, **features):
key = (base,) + tuple(features.items())
if key in types:
return types[key]
else:
if not isinstance(base, type):
raise TypeError("require element type, got '%s'" % base)
class C(list):
def __init__(self, iterable=[]):
for item in iterable:
try: # try to convert to desired type
self.append(self._base(item))
except ValueError:
raise TypeError("value '%s' not convertible to %s"
% (item, self._base.__name__))
# similar methods to type-check other list mutations
C.__name__ = "listOf(%s)" % base.__name__
C._base = base
C.__dict__.update(features)
types[key] = C
return C
Observe que estou usando um dict
como um cache aqui para que você obtenha o mesmo objeto de classe para uma determinada combinação de tipo de elemento e recursos.Isso faz com que listOf(int) is listOf(int)
sempre True
.
Aqui está um decorador versão da minha outra resposta que funciona com qualquer classe.O decorador retorna uma fábrica função que retorna uma subclasse da classe original com os atributos desejados.A coisa agradável sobre esta abordagem é que ela não o mandato de uma metaclasse, então você pode usar uma metaclasse (e.g. ABCMeta
) se desejado, sem conflitos.
Observe também que se a classe base usa uma metaclasse, que metaclasse será usada para instanciar o gerado subclasse.Você poderia, se quisesse, rígido código desejado metaclasse, ou, você sabe, escrever um profissional que faz uma metaclasse em um decorador para o modelo de classes...é decoradores de todo o caminho para baixo!
Se ele existir, um método de classe __classinit__()
é passado os argumentos passados para a fábrica, assim que a própria classe pode ter um código para validar argumentos e definir seus atributos.(Isso seria chamado depois que a metaclasse do __init__()
.) Se __classinit__()
retorna uma classe, essa classe é retornado pela fábrica em lugar de a gerado um, então você pode até mesmo estender a geração de procedimento desta maneira (e.g.para um tipo-verificada a classe lista, você pode retornar uma das duas classes internas, dependendo se os itens devem ser forçado para o tipo de elemento ou não).
Se __classinit__()
não existe, os argumentos passados para a fábrica de são simplesmente definidos como atributos de classe na nova classe.
Para facilitar a criação de tipo restrito de classes de contêiner, eu tenho tratado o tipo de elemento separadamente do recurso dict.Se não passou, vai ser ignorado.
Como antes, as classes geradas pela fábrica são armazenados em cache, de modo que cada vez que você chamar para uma classe com as mesmas características, você obtém a mesma classe de instância do objeto.
def template_class(cls, classcache={}):
def factory(element_type=None, **features):
key = (cls, element_type) + tuple(features.items())
if key in classcache:
return classcache[key]
newname = cls.__name__
if element_type or features:
newname += "("
if element_type:
newname += element_type.__name__
if features:
newname += ", "
newname += ", ".join(key + "=" + repr(value)
for key, value in features.items())
newname += ")"
newclass = type(cls)(newname, (cls,), {})
if hasattr(newclass, "__classinit__"):
classinit = getattr(cls.__classinit__, "im_func", cls.__classinit__)
newclass = classinit(newclass, element_type, features) or newclass
else:
if element_type:
newclass.element_type = element_type
for key, value in features.items():
setattr(newclass, key, value)
classcache[key] = newclass
return newclass
factory.__name__ = cls.__name__
return factory
Um exemplo de tipo restrito (tipo de conversão, na verdade) classe de lista:
@template_class
class ListOf(list):
def __classinit__(cls, element_type, features):
if isinstance(element_type, type):
cls.element_type = element_type
else:
raise TypeError("need element type")
def __init__(self, iterable):
for item in iterable:
try:
self.append(self.element_type(item))
except ValueError:
raise TypeError("value '%s' not convertible to %s"
% (item, self.element_type.__name__))
# etc., to provide type conversion for items added to list
A geração de novas classes:
Floatlist = ListOf(float)
Intlist = ListOf(int)
Em seguida, instanciar:
print FloatList((1, 2, 3)) # 1.0, 2.0, 3.0
print IntList((1.0, 2.5, 3.14)) # 1, 2, 3
Ou apenas criar a classe e instanciar em uma única etapa:
print ListOf(float)((1, 2, 3))
print ListOf(int)((1.0, 2.5, 3.14))