fredrik
fredrik

Reputation: 10281

How to create @property on class, generated from base class?

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

Answers (2)

Mandera
Mandera

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

Response to edited question

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

Another example where we store unique objects in each instance

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

mapf
mapf

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

Related Questions