Reputation: 10281
I would like to dynamically generate properties on a class and I wish to generate them on a "base" class. To illustrate the problem, I am here using pseudo code so to show how I don't know the names of the methods.
class Base:
def __init__(self):
for n in range(1,3):
setattr(self, f"func{n}", property(lambda self_=self: {f"func{n}": self_}))
class A(Base):
pass
Then I run:
>>> a = A()
>>> a.func1
<property object at 0x7f67c1342598>
However, I was expecting to get the following:
>>> a = A()
>>> a.func1
{'func1': <A object at 0x7f9ccec77198>}
It seems to me that the property()
call is not working as expected. What am I doing wrong?
Upvotes: 1
Views: 75
Reputation: 2992
I'm not sure why you're not using it as a decorator, here's an example where we don't call property directly:
class Base:
@property
def func(self):
return {"self": self}
class A(Base):
pass
print(A().func)
{'self': <__main__.A object at 0x0000018CC01DDC10>}
Sure that's possible, but we have to bind the properties to the class, not the instance. We also cannot set the default value for self_
as we actually want each instance's self
.
class Base:
def __init__(self):
for n in range(1, 3):
setattr(self.__class__, f"func{n}", property(lambda self_, n_=n: {f"func{n_}": self_}))
class A(Base):
pass
print(A().func1)
print(A().func2)
{'func1': <__main__.A object at 0x00000139C12B5FD0>}
{'func2': <__main__.A object at 0x00000139C12B5FD0>}
Some assertions to make sure the arguments are correct:
a1 = A()
a2 = A()
assert a1.func1["func1"] is a1
assert a1.func2["func2"] is a1
assert a2.func1["func1"] is a2
assert a2.func2["func2"] is a2
I'm concerned about the previous answer as it's only overwriting the class methods. In this example we still overwrite the class methods with each instance creation, but we also store a unique object in each instance for each func, which I believe is closer to what you're looking for. A simple counter stored in the class is used to visualize it.
class Base:
count = 0
def __init__(self):
for n in range(1, 3):
func_name = f"func{n}"
var_name = f"_{func_name}_var"
setattr(self, var_name, {func_name: self, "count": Base.count})
setattr(self.__class__, func_name, property(lambda self_, name=var_name: getattr(self_, name)))
Base.count += 1
class A(Base):
pass
a1 = A()
a2 = A()
print(a1.func1)
print(a1.func2)
print(a2.func1)
print(a2.func2)
{'func1': <__main__.A object at 0x0000019298925FD0>, 'count': 0}
{'func2': <__main__.A object at 0x0000019298925FD0>, 'count': 1}
{'func1': <__main__.A object at 0x0000019298925F70>, 'count': 2}
{'func2': <__main__.A object at 0x0000019298925F70>, 'count': 3}
Upvotes: 2
Reputation: 2058
Here is a way to do what you want, but it's probably not recommended:
def property_generator(i):
return f"""
@property
def func{i}(self):
return {{'self': self}}
"""
class_generator = f"""
class Base:
def __init__(self):
pass
{''.join(property_generator(i) for i in range(3))}
class A(Base):
pass
"""
print(class_generator)
exec(class_generator)
print(A().func1)
Upvotes: 0