2010-01-30 15 views
17

¿Hay alguna forma de evitar llamar a __init__ en una clase mientras se inicializa, como por ejemplo desde un método de clase?Python: crear una instancia de clase sin invocar el inicializador

Intento crear una clase de cadena insensible a mayúsculas y minúsculas en Python, utilizada para fines de comparación eficiente, pero tengo problemas para crear una nueva instancia sin llamar al __init__.

>>> class String: 

    def __init__(self, string): 
     self.__string = tuple(string.split()) 
     self.__simple = tuple(self.__simple()) 

    def __simple(self): 
     letter = lambda s: ''.join(filter(lambda s: 'a' <= s <= 'z', s)) 
     return filter(bool, map(letter, map(str.lower, self.__string))) 

    def __eq__(self, other): 
     assert isinstance(other, String) 
     return self.__simple == other.__simple 

    def __getitem__(self, key): 
     assert isinstance(key, slice) 
     string = String() 
     string.__string = self.__string[key] 
     string.__simple = self.__simple[key] 
     return string 

    def __iter__(self): 
     return iter(self.__string) 

>>> String('Hello, world!')[1:] 
Traceback (most recent call last): 
    File "<pyshell#2>", line 1, in <module> 
    String('Hello, world!')[1:] 
    File "<pyshell#1>", line 17, in __getitem__ 
    string = String() 
TypeError: __init__() takes exactly 2 positional arguments (1 given) 
>>> 

¿Qué debería reemplazar con string = String(); string.__string = self.__string[key]; string.__simple = self.__simple[key] para inicializar el nuevo objeto con las rebanadas?

EDIT:

Como se inspira en la respuesta escrita a continuación, el inicializador ha sido editado para comprobar rápidamente para ningún argumento.

def __init__(self, string=None): 
    if string is None: 
     self.__string = self.__simple =() 
    else: 
     self.__string = tuple(string.split()) 
     self.__simple = tuple(self.__simple()) 
+0

posible duplicado de [¿Hay una manera de crear una instancia de una clase sin tener que llamar \ _ \ _ init \ _ \ _?] (Http: // stackoverflow.com/questions/6383914/is-there-a-way-to-instanceiate-a-class-without-calling-init) – kay

Respuesta

1

Usar una metaclase proporciona una buena solución en este ejemplo. La metaclase tiene un uso limitado pero funciona bien.

>>> class MetaInit(type): 

    def __call__(cls, *args, **kwargs): 
     if args or kwargs: 
      return super().__call__(*args, **kwargs) 
     return cls.__new__(cls) 

>>> class String(metaclass=MetaInit): 

    def __init__(self, string): 
     self.__string = tuple(string.split()) 
     self.__simple = tuple(self.__simple()) 

    def __simple(self): 
     letter = lambda s: ''.join(filter(lambda s: 'a' <= s <= 'z', s)) 
     return filter(bool, map(letter, map(str.lower, self.__string))) 

    def __eq__(self, other): 
     assert isinstance(other, String) 
     return self.__simple == other.__simple 

    def __getitem__(self, key): 
     assert isinstance(key, slice) 
     string = String() 
     string.__string = self.__string[key] 
     string.__simple = self.__simple[key] 
     return string 

    def __iter__(self): 
     return iter(self.__string) 

>>> String('Hello, world!')[1:] 
<__main__.String object at 0x02E78830> 
>>> _._String__string, _._String__simple 
(('world!',), ('world',)) 
>>> 
0

pasar otro argumento para el constructor, así:

def __init__(self, string, simple = None): 
    if simple is None: 
     self.__string = tuple(string.split()) 
     self.__simple = tuple(self.__simple()) 
    else: 
     self.__string = string 
     self.__simple = simple 

A continuación, puede llamar así:

def __getitem__(self, key): 
    assert isinstance(key, slice) 
    return String(self.__string[key], self.__simple[key]) 

Además, no estoy seguro de que se le permite a nombre tanto el campo como el método __simple. Si solo fuera por la legibilidad, deberías cambiar eso.

+0

Me gustó más tu primera respuesta. Teniendo en cuenta 'str() == ''', creo que cambiaré el código a 'def __init __ (self, string = ''):'. ¡Gracias por tu ayuda y tus pensamientos! Esperaba evitar '__init__' por completo, pero llamar a' String() 'con el cambio mencionado no debería ser una operación muy costosa. Esa parece ser la mejor solución en este momento. –

+0

Lo siento, pensé que mi primera respuesta realmente no abordaba la pregunta :) Parece que no hay forma de que pueda recuperarla ...? – Thomas

21

Cuando sea posible, es preferible dejar que __init__ se llame (y hacer que la llamada sea inocua por los argumentos adecuados). Sin embargo, si eso requiere demasiada contorsión, tiene una alternativa, siempre y cuando evite la opción desastrosa de usar clases antiguas (hay no buena razón para usar clases antiguas en código nuevo, y varias buenas razones no a) ...:

class String(object): 
     ... 

    bare_s = String.__new__(String) 

Este idioma se utiliza generalmente en classmethod s que están destinados a trabajar como constructores "alternativas", por lo que se suele ver lo que solía en formas tales como ...:

@classmethod 
def makeit(cls): 
    self = cls.__new__(cls) 
    # etc etc, then 
    return self 

(de esta manera, la clase de método se heredará correctamente y generará instancias de subclase cuando se invoque a una subclase en lugar de a la clase base).

+6

¿Cómo reescribirías esto para Python 3.1? –

+0

Si String fuera una subclase, ¿cómo llamarías al constructor de la superclase? – gozzilli

+0

Oh, lo encontré, harías '' SuperClass .__ init __ (self) '' después de crear '' self''. – gozzilli

5

Un truco la salmuera estándar y módulos de copia de uso es crear una clase vacía, utilizando una instancia del objeto que, a continuación, asignar __class__ a la clase "real" de esa instancia. p.ej.

>>> class MyClass(object): 
...  init = False 
...  def __init__(self): 
...   print 'init called!' 
...   self.init = True 
...  def hello(self): 
...   print 'hello world!' 
... 
>>> class Empty(object): 
...  pass 
... 
>>> a = MyClass() 
init called! 
>>> a.hello() 
hello world! 
>>> print a.init 
True 
>>> b = Empty() 
>>> b.__class__ = MyClass 
>>> b.hello() 
hello world! 
>>> print b.init 
False 

Pero tenga en cuenta que este enfoque es muy poco frecuente. Pasar por alto el __init__ puede tener algunos efectos secundarios inesperados, especialmente si no está familiarizado con la clase original, así que asegúrese de saber lo que está haciendo.

Cuestiones relacionadas