-1

Im learning Python here so please spare me for silly questions. I Encounter an issue with adding attribute to a class instance

I have a dictionary of people with name,age and strength, i.e

{
"Mary": {"age":25, "strength": 80},
"John": {"age": 40, "strength": 70},
...
}

and a class that will get in list of people as constructor input and add them as its own attribute, and when that attribute is called, it will return the age

i.e:

group = Person({dictionary of person})

# call person name as attribute, get back age

first_person = group.Mary  # return 25 here
group.John # return 40 here

However, each attribute will also need to maintain its behavior as a dict/object

group.Mary["strength"] # return 80 here

I tried __get()__ but it seems to work only on class variables which is not this case since I need to create multiple group instances of class Person and they don't share variables.

Also tried setattr() but it will keep each attribute as a dict and therefore cannot directly call like group.Maryto get age

May I know is there any way in Python to implement this requirement?

2 Answers2

0

I don't think this is possible. The expression group.Mary["strength"] essentially consists of 2 steps:

  1. retrieving the attribute named "Mary" from the object group, and;
  2. calling the method __getitem__ on the retrieved attribute with argument "strength".

However, note that in your example you require Step 1 (group.Mary) to return 25, which is an integer. Unfortunately, integers can't also be a mapping (objects that implement __getitem__).

kmkurn
  • 611
  • 1
  • 13
0

Added to class a method to create dynamically the properties but, for example, it can also be refactor as global function.

Each attribute is private and its access is ruled by the descriptors. Notice the private attributes created dynamically required a bit more of care, see this for details and references.

class Person:
    @classmethod
    def property_factory(cls, name):
        # dynamical descriptors, -> private name mangling!
        p = property(fget=lambda self: getattr(self, f'_{cls.__name__}__{name}'), 
                     fset=lambda self, v: setattr(self, f'_{cls.__name__}__{name}', v))
        
        setattr(cls, name, p)

    def __init__(self, **p):
        # dynamically add properties
        for name in p.keys():
            self.property_factory(name)

        # initialization descriptors
        for name, d in p.items():
            setattr(self, name, d)


data = {"Mary": {"age":25, "strength": 80}, "John": {"age": 40, "strength": 70}}

p = Person(**data)

# check property
print(type(p).Mary)
#<property object at 0x7f5e5d469e00>
print(type(p.Mary))
#<class 'dict'>

# descriptors in action
p.Mary['age'] += 666
p.John['strength'] -= 666
print(p.John)
#{'age': 40, 'strength': -596}
print(p.Mary)
#{'age': 691, 'strength': 80}
cards
  • 3,936
  • 1
  • 7
  • 25
  • If I understand correctly, OP wants `p.Mary` to (a) return the age, i.e. 40, and not a dict, while also (b) behave like a dict supporting `__getitem__`. Your answer does (b) but not (a). – kmkurn Dec 11 '22 at 21:27
  • @kmkurn yes, **a posteriori** I also noticed it. The problem is that the question is bad posed, see the title and __"I Encounter an issue with adding attribute to a class instance"__ and the vague __"Also tried ..."__ Further, there is no trace of any attempts which instead could be useful to get an idea of the encountered difficulties. For (a) see my comment to the question – cards Dec 12 '22 at 15:44
  • hi, thanks for your replies, it seems not possible to achieve all desired behaviors at once so I must accept to find another way – viet nguyen Dec 28 '22 at 14:25
  • @viet nguyen I think properties are not the right way. Have a look to [`enum`](https://docs.python.org/3/library/enum.html) (notice that with python 3.11 this package was updated and there are new features such as `EnumType`) – cards Dec 29 '22 at 18:58