Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get attributes of a class without knowing their names

Tags:

python

I have the following class:

class TestClass(object):
def __init__(self, **kwargs):
    for key, value in kwargs.items(): #items return list of dict
        setattr(self, key, value)

Examplary use:

obj = MessageItem(**{"testkey1":"tval1", "tkey2":"tval2", "tkey3":"tval3"})

How can I iterate on this structure without knowing the names of the attributes? Python provides us with the built-in method __getattribute__, but I still need to know the name of the requested attribute:

print(obj.__getattribute__("testkey1"))
like image 436
Robert Pawlak Avatar asked Aug 28 '17 12:08

Robert Pawlak


1 Answers

The __dict__ attribute holds what you want. The class has it:

>>> class Foo:
...     def __init__(self, x):
...             self.x = x
...
>>> Foo.__dict__
mappingproxy({'__module__': '__main__', '__init__': <function Foo.__init__ at
0x000001CC1821EEA0>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__':
<attribute '__weakref__' of 'Foo' objects>, '__doc__': None})

And any instance has it as well:

>>> f = Foo(2)
>>> f.__dict__
{'x': 2}

You should access this attribute through the vars builtin function. Calling vars(foo) will return foo.__dict__. See this related post: Use __dict__ or vars()?.

Documentation for vars:

vars([object])

Return the __dict__ attribute for a module, class, instance, or any other object with a __dict__ attribute.

Objects such as modules and instances have an updateable __dict__ attribute; however, other objects may have write restrictions on their __dict__ attributes (for example, classes use a types.MappingProxyType to prevent direct dictionary updates).

Without an argument, vars() acts like locals(). Note, the locals dictionary is only useful for reads since updates to the locals dictionary are ignored.


In addition, I tried and wrote a decorator that might interest you. This is a class decorator, that adds a initKwargs to the class it decorates. Besides, it wraps the __init__ method of that class as well, so as to have it append the kwargs dictionary it receives to the class' initKwargs attribute.

def class_wrapper(cls):
    cls.initKwargs = []
    f = cls.__init__

    def wrapped_init(instance, **kwargs):
        cls.initKwargs.append(kwargs)
        return f(instance, **kwargs)            
    cls.__init__ = wrapped_init

    return cls

@class_wrapper
class Foo:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

Demonstration:

>>> f1 = Foo()
>>> f2 = Foo(a=1, b=2)
>>> f3 = Foo(a=1, b=2, c=3, d=4)
>>> Foo.initKwargs
[{}, {'a': 1, 'b': 2}, {'a': 1, 'b': 2, 'c': 3, 'd': 4}]

I find this approach much cleaner that using vars, because you know what you're accessing since you define it yourself. It gives you more control on the class' behaviour.

like image 113
Right leg Avatar answered Oct 18 '22 18:10

Right leg