Dynamic Property Getter and Setter

Issue

Is there a way I can create a class with varied number of attributes and create corresponding getters and setters for these attributes?

My sample code is shown below.

class A:
    def __init__(self, **options):
        self._options = options
        for key, value in options.items():
            setattr(self, f"_{key.lower()}", value)
            setattr(self, f"get_{key.lower()}", lambda : getattr(self, f"_{key.lower()}", None))


a = A(dimension = 2, name = "Unknown Alphabet")

for key, value in a.__dict__.items():
    print(f"A[{key.lower()}] = {value}")

print(a.get_name())
print(a.get_dimension())

new_santa_clause = A(location = "North Pole", vehicle = "Teleportation", naughty_kids_list = [])
for key, value in new_santa_clause.__dict__.items():
    print(f"SantaClause[{key}] = {value}")

print(new_santa_clause.get_location())
print(new_santa_clause.get_vehicle())
print(new_santa_clause.get_naughty_kids_list())

Output of execution is shown below

A[_options] = {'dimension': 2, 'name': 'Unknown Alphabet'}
A[_dimension] = 2
A[get_dimension] = <function A.__init__.<locals>.<lambda> at 0x000002334B4EC678>
A[_name] = Unknown Alphabet
A[get_name] = <function A.__init__.<locals>.<lambda> at 0x000002334B4EC1F8>
Unknown Alphabet
Unknown Alphabet
SantaClause[_options] = {'location': 'North Pole', 'vehicle': 'Teleportation', 'naughty_kids_list': []}
SantaClause[_location] = North Pole
SantaClause[get_location] = <function A.__init__.<locals>.<lambda> at 0x000002334B4ECA68>
SantaClause[_vehicle] = Teleportation
SantaClause[get_vehicle] = <function A.__init__.<locals>.<lambda> at 0x000002334B4EC438>
SantaClause[_naughty_kids_list] = []
SantaClause[get_naughty_kids_list] = <function A.__init__.<locals>.<lambda> at 0x000002334B4ECCA8>
[]
[]
[]

The values are getting set properly and getters are also getting created properly. Its just that when the getters are executed, proper values are not returned.

Solution

Well, you can do what you want to some extent, but in Python, properties are looked-up based in the class of an object, which means you would need to create a separate class for each combination of attributes you wanted (which must be called to create an instance of it).

Explanation

In the code below defines a function named make_class() to replace the generic A class in your question. It handles mutable default arguments properly by automatically detecting them and assigning copies of their values to the class instance attributes when one is created (preventing all instance from sharing a single class-level attribute).

from collections.abc import MutableMapping, MutableSequence, MutableSet
import copy

MUTABLES = MutableMapping, MutableSequence, MutableSet  # Mutable containers

def make_class(classname, **options):
    """Return a class with the specified attributes implemented as properties.
    """
    class Class:
        def __init__(self):
            """Initialize instance attribute storage name values."""
            for key, value in options.items():
                if isinstance(value, MUTABLES):  # Mutable?
                    value = copy.deepcopy(value)  # Avoid mutable default arg.
                setattr(self, '_' + key.lower(), value)

    for key, value in options.items():  # Create class' properties.
        setattr(Class, key.lower(), managed_attribute(key))

    Class.__name__ = classname
    return Class

def managed_attribute(name):
    """Return a property that stores values under a private non-public name."""
    storage_name = '_' + name.lower()

    @property
    def prop(self):
        return getattr(self, storage_name)

    @prop.setter
    def prop(self, value):
        setattr(self, storage_name, value)

    return prop


A = make_class('A', dimension=2, name="Unknown Alphabet")
a = A()
print(a.name)       # -> Unknown Alphabet
print(a.dimension)  # -> 2


SantaClaus = make_class('SantaClaus', location="North Pole", vehicle="Teleportation",
                        naughty_kids_list=[])

santa_clause = SantaClaus()
print(santa_clause.location)           # -> North Pole
print(santa_clause.vehicle)            # -> Teleportation
print(santa_clause.naughty_kids_list)  # -> []

Answered By – martineau

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published