Deseo definir propiedades en una clase desde una función miembro. A continuación se muestra un código de prueba que muestra cómo me gustaría que esto funcione. Sin embargo, no obtengo el comportamiento esperado.

class Basket(object):

  def __init__(self):
    # add all the properties
    for p in self.PropNames():
      setattr(self, p, property(lambda : p) )

  def PropNames(self):
    # The names of all the properties
    return ['Apple', 'Pear']

  # normal property
  Air = property(lambda s : "Air")

if __name__ == "__main__":
  b = Basket()
  print b.Air # outputs: "Air"
  print b.Apple # outputs: <property object at 0x...> 
  print b.Pear # outputs: <property object at 0x...> 

¿Cómo podría hacer que esto funcione?

5
pkit 21 sep. 2009 a las 19:04

3 respuestas

La mejor respuesta

Debe establecer las propiedades en la clase (es decir: self.__class__), no en el objeto (es decir: self). Por ejemplo:

class Basket(object):

  def __init__(self):
    # add all the properties
    setattr(self.__class__, 'Apple', property(lambda s : 'Apple') )
    setattr(self.__class__, 'Pear', property(lambda s : 'Pear') )

  # normal property
  Air = property(lambda s : "Air")

if __name__ == "__main__":
  b = Basket()
  print b.Air # outputs: "Air"
  print b.Apple # outputs: "Apple"
  print b.Pear # outputs: "Pear"

Por lo que vale, su uso de p al crear lamdas en el bucle no da el comportamiento que esperaría. Dado que el valor de p cambia mientras se recorre el bucle, las dos propiedades establecidas en el bucle devuelven el mismo valor: el último valor de p.

13
Stef 21 sep. 2009 a las 15:10

¿Por qué estás definiendo propiedades en el momento __init__? Es confuso e inteligente, por lo que es mejor que tenga una buena razón. El problema del bucle que Stef señaló es solo un ejemplo de por qué esto debería evitarse.

Si necesita redefinir qué propiedades tiene una subclase, puede hacer del self.<property name> en el método de la subclase __init__ o definir nuevas propiedades en la subclase.

Además, algunos nitpicks de estilo:

  • Sangría a 4 espacios, no 2
  • No mezcle tipos de cotizaciones innecesariamente
  • Utilice guiones bajos en lugar de mayúsculas y minúsculas para los nombres de los métodos. PropNames -> prop_names
  • PropNames realmente no necesita ser un método
0
Christian Oudard 21 sep. 2009 a las 16:29

Esto hace lo que querías:

class Basket(object):
  def __init__(self):
    # add all the properties

    def make_prop( name ):
        def getter( self ):
            return "I'm a " + name
        return property(getter)

    for p in self.PropNames():
        setattr(Basket, p, make_prop(p) )

  def PropNames(self):
    # The names of all the properties
    return ['Apple', 'Pear', 'Bread']

  # normal property
  Air = property(lambda s : "I'm Air")

if __name__ == "__main__":
  b = Basket()
  print b.Air 
  print b.Apple 
  print b.Pear 

Otra forma de hacerlo sería una metaclase ... pero confunden a mucha gente ^^.

Porque estoy aburrida:

class WithProperties(type):
    """ Converts `__props__` names to actual properties """
    def __new__(cls, name, bases, attrs):
        props = set( attrs.get('__props__', () ) )
        for base in bases:
            props |= set( getattr( base, '__props__', () ) )

        def make_prop( name ):
            def getter( self ):
                return "I'm a " + name
            return property( getter )

        for prop in props:
            attrs[ prop ] = make_prop( prop )

        return super(WithProperties, cls).__new__(cls, name, bases, attrs)       

class Basket(object):
    __metaclass__ = WithProperties
    __props__ = ['Apple', 'Pear']

    Air = property(lambda s : "I'm Air")

class OtherBasket(Basket):
    __props__ = ['Fish', 'Bread']

if __name__ == "__main__":
    b = Basket()
    print b.Air 
    print b.Apple 
    print b.Pear 

    c = OtherBasket()
    print c.Air 
    print c.Apple 
    print c.Pear
    print c.Fish 
    print c.Bread 
3
Jochen Ritzel 21 sep. 2009 a las 15:46