views:

224

answers:

3

I whish to define properties in a class from a member function. Below is some test code showing how I would like this to work. However I don't get the expected behaviour.

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...>

How could I get this to work?

+7  A: 

You need to set the properties on the class (ie: self.__class__), not on the object (ie: self). For example:

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"

For what it's worth, your usage of p when creating lamdas in the loop, doesn't give the behavior that you would expect. Since the value of p is changed while going through the loop, the two properties set in the loop both return the same value: the last value of p.

Stef
+1  A: 

This does what you wanted:

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

Another way to do it would be a metaclass ... but they confuse a lot of people ^^.

Because I'm bored:

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
THC4k
I disagree that the right way to do it would be a metaclass. Metaclasses are too powerful for this, because this can be accomplished using less powerful means.
Christian Oudard
Thanks for the update. I actually never went as deep as Metaclasses in Python. Maybe this is a good reason to start reading about them.
pkit
+1  A: 

Why are you defining properties at __init__ time? It's confusing and clever, so you better have a really good reason. The loop problem that Stef pointed out is just one example of why this should be avoided.

If you need to redifine which properties a subclass has, you can just do del self.<property name> in the subclass __init__ method, or define new properties in the subclass.

Also, some style nitpicks:

  • Indent to 4 spaces, not 2
  • Don't mix quote types unnecessarily
  • Use underscores instead of camel case for method names. PropNames -> prop_names
  • PropNames doesn't really need to be a method
Christian Oudard
The main reason was that I could inheret from Basket and redefine PropNames. P.S. The original example code was actually indenten 4 spaces but when I copied it to SO it lost the indentation so I re-indented on 2 spaces.
pkit
@pkit: Edited answer to talk about subclassing.
Christian Oudard