5

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"))
Right leg
  • 16,080
  • 7
  • 48
  • 81
Robert Pawlak
  • 529
  • 7
  • 23
  • 3
    Why would you want to deliberately put yourself in such a situation instead of storing it as a `dict` ? – Jon Clements Aug 28 '17 at 12:07
  • @JonClements Personaly, i haven't idea, my leader planed this in that way, and i must implement :D – Robert Pawlak Aug 28 '17 at 12:10
  • 1
    Well, feel free to tell them it's not good design and it's somewhat dangerous... People will be able to clobber names you've already used and break things and debugging it later on is going to be horrible. – Jon Clements Aug 28 '17 at 12:11
  • 1
    (Or inversely, you may clobber assignment to a variable they've passed...) – Jon Clements Aug 28 '17 at 12:17
  • @RobertPawlak is this a school/Uni assignment or work? If it's work, you might want to talk to your leader and rethink the design. For school/uni, it's an interesting problem to think about, but in actual software you shouldn't do this if you can avoid it. – Zinki Aug 28 '17 at 12:25
  • Possible duplicate of [looping over all member variables of a class in python](https://stackoverflow.com/questions/1398022/looping-over-all-member-variables-of-a-class-in-python) – Philippe Oger Aug 28 '17 at 12:29
  • 1
    And just to note (on top of this being a bad idea in most use cases): you wouldn't really want to use `obj.__getattribute__("testkey1")` - you'd use `getattr(obj, 'testkey1')` - not only is it easier on the eyes and keyboard, it also allows providing a default value to return if the attribute isn't found. – Jon Clements Aug 28 '17 at 12:35
  • @JonClements Mabye You rememebr my last question, which was also combined with python. This question is continuation those solution. I talked about this with my leader, and he said that is correct solution, becouse he want access to attributes per name, parse from file. Ex: class msg.id, msg.type – Robert Pawlak Aug 29 '17 at 06:52

1 Answers1

4

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.

Right leg
  • 16,080
  • 7
  • 48
  • 81
  • 2
    Which if you're going to suggest, then suggest using `vars(f)` - there's little need to access `__dict__` here directly and it may not always be as expected. – Jon Clements Aug 28 '17 at 12:10
  • @JonClements Considering what the doc says about `vars`, how can it be different from `__dict__`? I think I remember reading that `__dict__` was not quite good, but I can't remember why... – Right leg Aug 28 '17 at 12:14
  • I can't remember *exactly* off the top of my head... but `vars(...)` will do the right thing while directly accessing `__dict__` may fail in cases. Although I'm slightly doubting myself now... but something rings a bell :) Besides - at the very least - not having dunder method access in your code where not necessary is always good. – Jon Clements Aug 28 '17 at 12:15
  • 1
    @JonClements I found [this post](https://stackoverflow.com/questions/21297203/use-dict-or-vars). So basically the argument is that since Python provides a wrapper for it, it should be accessed through that wrapper instead of directly. It makes sense to me. – Right leg Aug 28 '17 at 12:17
  • Nice find. Thanks. Now - the only problem with this is that you can't actually tell what arguments were supplied unless you take away the ones that were already there or know you've set... But if the OPs going to take this approach then, well, on their head be it and all that. – Jon Clements Aug 28 '17 at 12:19
  • @JonClements What arguments were supplied to what? to `Foo.__init__`? – Right leg Aug 28 '17 at 12:20
  • Yeah... you're going to potentially end up clashing your class' interface with variables as well as not having an idea what was supplied and what was already present... see https://gist.github.com/anonymous/c82fcb0b4e772220e3c1b60c64499648 for a quick example – Jon Clements Aug 28 '17 at 12:26