2011-03-01 20 views
10

En Python, ¿cómo es posible reutilizar objetos existentes inmutables iguales (como se hace para str)? ¿Se puede hacer esto simplemente definiendo un método __hash__, o requiere medidas más complicadas?¿Reutilizar objetos existentes para objetos inmutables?

+0

+1 Interesante pregunta. Sé por cadenas que el proceso se llama "internamiento", pero no estoy seguro de si ese término es específicamente para cadenas, ya que nunca he oído hablar de otra cosa siendo internado. – Davy8

+0

A menudo, la función hash se implementará como la dirección del objeto, por lo que se obtiene un problema de huevo/gallina; el hash no será igual hasta que ya hayas decidido que los objetos son iguales. –

Respuesta

11

Si desea crear mediante el constructor de la clase y hacer que devuelva un objeto creado previamente, deberá proporcionar un método __new__ (porque para cuando llegue a __init__ el objeto ya se ha creado).

Aquí está un ejemplo sencillo - si el valor que se utiliza para inicializar se ha visto antes de esa fecha un objeto creado previamente se devuelve en lugar de crear uno nuevo:

class Cached(object): 
    """Simple example of immutable object reuse.""" 

    def __init__(self, i): 
     self.i = i 

    def __new__(cls, i, _cache={}): 
     try: 
      return _cache[i] 
     except KeyError: 
      # you must call __new__ on the base class 
      x = super(Cached, cls).__new__(cls) 
      x.__init__(i) 
      _cache[i] = x 
      return x 

Tenga en cuenta que para este ejemplo se puede usar cualquier cosa para inicializar mientras sea hashable Y solo para mostrar que los objetos realmente se están reutilizando:

>>> a = Cached(100) 
>>> b = Cached(200) 
>>> c = Cached(100) 
>>> a is b 
False 
>>> a is c 
True 
+3

Gracias.Esto funcionó, la única diferencia que usé fue así: 'clase a (objeto): _cached = {} '. La razón por la que lo hice de esta manera es porque creo que la creación de instancias de objetos en los parámetros de funciones puede ser confusa, porque en algunos casos la gente puede no darse cuenta de que se está instanciando solo una vez, y que se refiere al mismo objeto para todas las llamadas de ese función. – Abbafei

+0

@Abafei: Sí, su cambio es bueno. A mucha gente le gusta evitar los argumentos predeterminados mutables, ya que a menudo confunden a los demás. No soy tan considerado :) –

+2

Tenga en cuenta que '__init__' se llamará * dos veces * cuando hay una falta de caché y una vez si hay un golpe de caché . Necesita proteger explícitamente contra múltiples llamadas de constructor, p. establece una bandera al final de '__init__' y regresa inmediatamente si se establece la bandera. – kynan

3

Creo que tendrías que mantener un dict {args: object} de instancias ya creadas, luego anular el método de clase '__new__ para verificar ese diccionario y devolver el objeto relevante si ya existía. Tenga en cuenta que no he implementado o probado esta idea. Por supuesto, las cadenas se manejan en el nivel C.

+0

Sí esto funciona: he utilizado este método con éxito anteriormente. Puedo publicar un código de ejemplo mañana si nadie me gana. –

3

Existen dos soluciones de "ingeniería de software" para esto que no requieren ningún conocimiento de bajo nivel de Python. Se aplican en las siguientes situaciones:

Primer escenario: Los objetos de su clase son 'iguales' si están construidos con los mismos parámetros de constructor, y la igualdad no cambiará con el tiempo después de la construcción. Solución: Uso una fábrica que hashses los parámetros del constructor:

class MyClass: 
    def __init__(self, someint, someotherint): 
    self.a = someint 
    self.b = someotherint 

cachedict = { } 
def construct_myobject(someint, someotherint): 
    if (someint, someotherint) not in cachedict: 
    cachedict[(someint, someotherint)] = MyClass(someint, someotherint) 
    return cachedict[(someint, someotherint)] 

Este enfoque limita esencialmente las instancias de la clase a un objeto único para cada par de entrada distinta. También hay inconvenientes obvios: no todos los tipos son fáciles de manipular, etc.

Segundo escenario: Los objetos de su clase son mutables y su "igualdad" puede cambiar con el tiempo. Solución: definen un registro de nivel de clase de la igualdad de casos:.

class MyClass: 
    registry = { } 

    def __init__(self, someint, someotherint, third): 
    MyClass.registry[id(self)] = (someint, someotherint) 
    self.someint = someint 
    self.someotherint = someotherint 
    self.third = third 

    def __eq__(self, other): 
    return MyClass.registry[id(self)] == MyClass.registry[id(other)] 

    def update(self, someint, someotherint): 
    MyClass.registry[id(self)] = (someint, someotherint) 

En este ejemplo, los objetos con el mismo someint, someotherint par son iguales, mientras que el tercer parámetro no tiene en cuenta El truco es mantener los parámetros en registry en sincronía. Como alternativa al update, puede reemplazar getattr y setattr para su clase; esto garantizaría que cualquier asignación foo.someint = y se mantendría sincronizada con su diccionario de nivel de clase. Vea un ejemplo here.

Cuestiones relacionadas