Reputation: 8301
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?
Upvotes: 5
Views: 4868
Reputation: 49926
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:
PropNames
-> prop_names
PropNames
doesn't really need to be a methodUpvotes: 0
Reputation: 107608
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
Upvotes: 3
Reputation: 6981
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
.
Upvotes: 13