2010-10-25 11 views
13

Estoy escribiendo una API de Python Library y a menudo me encuentro con el escenario en el que mis usuarios quieren múltiples nombres diferentes para las mismas funciones y variables.¿Cómo puedo crear un alias para un atributo miembro no funcional en una clase Python?

Si tengo una clase de Python con la función foo() y quiero crear un alias para que llamó bar(), eso es muy fácil:

class Dummy(object): 

    def __init__(self): 
     pass 

    def foo(self): 
     pass 

    bar = foo 

ahora que puedo hacer esto sin ningún problema:

d = Dummy() 
d.foo() 
d.bar() 

Lo que me pregunto es ¿cuál es la mejor manera de hacer esto con un atributo de clase que es una variable regular (por ejemplo, una cadena) en lugar de una función? Si tuviera este pedazo de código:

d = Dummy() 
print d.x 
print d.xValue 

quiero d.x y d.xValue a SIEMPRE impresión de la misma cosa. Si d.x cambia, también debería cambiar d.xValue (y viceversa).

puedo pensar en un número de maneras de hacer esto, pero ninguno de ellos parecen tan suave como me gustaría:

  • Escribir una anotación personalizada
  • Uso del @property anotación y meterse con el colocador
  • Anulación de las funciones __setattr__ clase

¿Cuál de estas formas es el mejor? ¿O hay otra manera? No puedo evitar sentir que si es tan fácil hacer alias para funciones, debería ser igual de fácil para variables arbitrarias ...

FYI: Estoy usando Python 2.7.x, no Python 3.0, entonces Necesito una solución compatible con Python 2.7.x (aunque me interesaría si Python 3.0 hace algo para abordar esta necesidad directamente).

Gracias!

+6

Considere: "Debería haber una, y preferiblemente solo una, forma obvia de hacerlo". - El Zen de Python – delnan

+2

¿Por qué tus usuarios quieren esto malo malo? –

+1

Se debe principalmente a que mi API de scripting se usa en múltiples subsistemas y dominios, por lo que cambia el vocabulario predeterminado. Lo que se conoce como "X" en un dominio se conoce como "Y" en otro dominio. Además, porque son malos seres humanos. :-) –

Respuesta

12

le puede proporcionar una __setattr__ y __getattr__ que hacen referencia a un mapa de alias:

class Dummy(object): 
    aliases = { 
     'xValue': 'x', 
     'another': 'x', 
     } 

    def __init__(self): 
     self.x = 17 

    def __setattr__(self, name, value): 
     name = self.aliases.get(name, name) 
     object.__setattr__(self, name, value) 

    def __getattr__(self, name): 
     if name == "aliases": 
      raise AttributeError # http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html 
     name = self.aliases.get(name, name) 
     #return getattr(self, name) #Causes infinite recursion on non-existent attribute 
     return object.__getattribute__(self, name) 


d = Dummy() 
assert d.x == 17 
assert d.xValue == 17 
d.x = 23 
assert d.xValue == 23 
d.xValue = 1492 
assert d.x == 1492 
+0

Gracias por http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html ; es una lectura muy interesante! – unutbu

+0

Eso es algo muy parecido a lo que imaginaba, aunque no encontré el diccionario de alias la primera vez. Eso lo convierte en una buena manera de evitar las escalas if/else y las funciones largas, precisas de setattr/getattr. –

+1

Addendum Importante: Usar 'getattr' dentro de' __getattr__' en realidad causa una recursión infinita para attribus no existentes (por ejemplo, para el ejemplo anterior, intente hacer 'print d.asdf'). Creo que usar 'object .__ getattribute__' en su lugar resuelve el problema. –

-1

Reemplace el método __getattr__() y devuelva el valor apropiado.

+0

No creo ver cómo reemplazar '' gestor__' va a resolver el problema de la propagación de cambios cuando el usuario dice 'dx = 5' contra' d.xValue = 6' –

+0

Nunca mencionaste nada acerca de * setting * 'd.xValue'. Cuando solicitan el valor de 'd.xValue', simplemente busque y devuelva' d.x' en su lugar. –

+0

Las preguntas originales dicen 'Si dx cambia, también debería cambiar d.xValue (y viceversa) .' Estaba intentando entender el hecho de que quería hacer' dx = 5' o 'd. xValue = 5'. Disculpas si no estaba claro. –

5

¿Qué vas a hacer cuando la mitad de sus usuarios deciden utilizar d.x y la otra mitad d.xValue? ¿Qué sucede cuando intentan compartir el código? Claro, funcionará, si conoce todos los alias, pero ¿será obvio? ¿Te resultará obvio cuando guardes tu código durante un año?

Al final, creo que este tipo de bondad o lujo es una trampa malvada que eventualmente causará más confusión que bien.


Es más que nada porque mi scripting API se utiliza a través de múltiples subsistemas & dominios, por lo que el vocabulario predeterminado cambios. Lo que se conoce como "X" en un dominio se conoce como "Y" en otro dominio .

Se podría hacer alias con propiedades de esta manera:

class Dummy(object): 
    def __init__(self): 
     self.x=1 
    @property 
    def xValue(self): 
     return self.x 
    @xValue.setter 
    def xValue(self,value): 
     self.x=value 

d=Dummy() 
print(d.x) 
# 1 
d.xValue=2 
print(d.x) 
# 2 

Pero por las razones mencionadas anteriormente, no creo que este es un buen diseño. Es hace que Dummy sea más difícil de leer, comprender y usar. Para cada usuario, ha duplicado el tamaño de la API que el usuario debe conocer para comprender a Dummy.

Una mejor alternativa es utilizar el Adapter design pattern. Esto le permite mantener buen maniquí, compacta y sucinta:

class Dummy(object): 
    def __init__(self): 
     self.x=1 

Mientras que aquellos usuarios en el subdominio que deseen utilizar un vocabulario diferente pueden hacerlo mediante el uso de una clase de adaptador:

class DummyAdaptor(object): 
    def __init__(self): 
     self.dummy=Dummy() 
    @property 
    def xValue(self): 
     return self.dummy.x 
    @xValue.setter 
    def xValue(self,value): 
     self.dummy.x=value  

Para cada método y atributo en Dummy, simplemente conecta métodos y propiedades similares que delegan el trabajo pesado a una instancia de Dummy.

Podría ser más líneas de código, pero se le permitirá conservar un diseño limpio para simulada, más fácil de mantener, documentar y prueba unitaria. La gente escribirá código que tenga sentido porque la clase restringirá qué API está disponible, y solo habrá un nombre para cada concepto dada la clase que han elegido.

+2

No necesariamente estoy en desacuerdo contigo, pero he peleado esa batalla y perdido. Lo mejor que puedo hacer es tratar de implementarlo y documentarlo bien, así que es tan claro como puede ser dentro de un año. –

3

Puede usar algunas de las ideas que se muestran en la receta de ActiveState Python titulada Caching and aliasing with descriptors. Aquí hay una versión concisa del código que se muestra allí que proporciona la funcionalidad que busca.

Editar: Una clase contiene atributos Alias se podría hacer para eliminar automáticamente cualquier atributo de destino asociado al del uno (y viceversa). El código para mi respuesta ahora ilustra una forma fácil de hacerlo, usando un decorador de clase conveniente que agrega un __delattr__() personalizado para hacer la gestión de eliminación especializada cuando el atributo Alias's podría estar involucrado.

class Alias(object): 
    """ Descriptor to give an attribute another name. """ 
    def __init__(self, name): 
     self.name = name 
    def __get__(self, inst, cls): 
     if inst is None: 
      return self # a class attribute reference, return this descriptor 
     return getattr(inst, self.name) 
    def __set__(self, inst, value): 
     setattr(inst, self.name, value) 
    def __delete__(self, inst): 
     delattr(inst, self.name) 

def AliasDelManager(cls): 
    """ Class decorator to auto-manage associated Aliases on deletion. """ 
    def __delattr__(self, name): 
     """ Deletes any Aliases associated with a named attribute, or 
      if attribute is itself an Alias, deletes the associated target. 
     """ 
     super(cls, self).__delattr__(name) # use base class's method 
     for attrname in dir(self): 
      attr = getattr(Dummy, attrname) 
      if isinstance(attr, Alias) and attr.name == name: 
       delattr(Dummy, attrname) 

    setattr(cls, '__delattr__', __delattr__) 
    return cls 

if __name__=='__main__': 
    @AliasDelManager 
    class Dummy(object): 
     def __init__(self): 
      self.x = 17 
     xValue = Alias('x') # create an Alias for attr 'x' 

    d = Dummy() 
    assert d.x == 17 
    assert d.xValue == 17 
    d.x = 23 
    assert d.xValue == 23 
    d.xValue = 1492 
    assert d.x == 1492 
    assert d.x is d.xValue 
    del d.x # should also remove any associated Aliases 
    assert 'xValue' not in dir(d) 
    print 'done - no exceptions were raised' 
+0

Eso es realmente inteligente. Lo único que me molesta es que si hago 'del d.x' y luego hago' dir (d) ',' xValue' seguirá apareciendo en la lista resultante. –

+0

@Brent Nash: he agregado un decorador de clases para tratar el problema de eliminación de atributos que usted señaló. – martineau

4

A menos que esté malinterpretando la pregunta, esto se puede resolver casi de la misma manera que con los métodos de Clase.

Por ejemplo,

class Dummy(object): 

    def __init__(self): 
     self._x = 17 

    @property 
    def x(self): 
     return self._x 

    @x.setter 
    def x(self, inp): 
     self._x = inp 

    # Alias 
    xValue = x 

d = Dummy() 
print(d.x, d.xValue) 
#=> (17, 17) 
d.x = 0 
print(d.x, d.xValue) 
#=> (0, 0) 
d.xValue = 100 
print(d.x, d.xValue) 
#=> (100, 100) 

Los dos valores siempre permanecerán sincronizados. Usted escribe el código de propiedad real con el nombre del atributo que prefiera, y luego lo alía con los nombres heredados que necesite.

En mi opinión, este código es mucho más fácil de leer y comprender que todas las sobreescrituras __setattr__ y __getattr__.

+0

Estoy de acuerdo. Este código es mucho más fácil de leer. –

+0

Funciona bien y parece mucho más simple para mí. – BrendanSimon

Cuestiones relacionadas