Reputation: 3270
I am trying to attach properties dynamically to a class (Registry
) for the sake of easy access to values in a dict. I am using defaultdict
to define the dictionary, with the default value as an empty list
.
But because of the way I am accessing the list values in the dictionary while defining the property, I end up with all properties pointing to the same list object.
Gist: https://gist.github.com/subhashb/adb75a3a05a611c3d9193da695d46dd4
from collections import defaultdict
from enum import Enum
class ElementTypes(Enum):
PHONE = "PHONE"
CAR = "CAR"
class Registry:
def __new__(cls, *args, **kwargs):
cls.setup_properties()
instance = super(Registry, cls).__new__(cls, *args, **kwargs)
return instance
def __init__(self):
self._elements = {}
def register(self, element_type, item):
if element_type.value not in self._elements:
self._elements[element_type.value] = []
self._elements[element_type.value].append(item)
def get(self, element_type):
return self._elements[element_type.value]
@classmethod
def setup_properties(cls):
for item in ElementTypes:
prop_name = item.value
prop = property(lambda self: getattr(self, "get")(item))
setattr(Registry, prop_name.lower(), prop)
registry = Registry()
registry.register(ElementTypes.PHONE, "phone1")
registry.register(ElementTypes.PHONE, "phone2")
registry.register(ElementTypes.CAR, "car1")
registry.register(ElementTypes.CAR, "car2")
assert dict(registry._elements) == {
"CAR": ["car1", "car2"],
"PHONE": ["phone1", "phone2"],
}
assert hasattr(registry, "phone")
assert hasattr(registry, "car")
assert registry.car == ["car1", "car2"]
assert registry.phone == ["phone1", "phone2"] # This fails
How do I define the code withing the property to be truly dynamic and get access to the individual list values in the dict?
Upvotes: 0
Views: 591
Reputation: 10477
First, don't setup properties in __new__
, that gets called for every Registry
object created! Instead, just assign the properties outside the class definition.
Secondly, this trips a lot of people up, but if you use a lambda
inside a for-loop and you want to use the item
variable, you need to make sure that you add an argument called item
with the default value of item
, otherwise all the properties will refer to the last item
of the loop.
class Registry:
def __init__(self):
self._elements = defaultdict(list)
def register(self, element_type, item):
self._elements[element_type.value].append(item)
def get(self, element_type):
return self._elements[element_type.value]
for item in ElementTypes:
prop_name = item.value
prop = property(lambda self, item=item: self.get(item))
setattr(Registry, prop_name.lower(), prop)
Upvotes: 2