1

I have class with a lot of fields that can be changed while my program is running, but when I create new object in my init I only can change some of them, I want to save those changes to JSON file and later be able to create new object with those variables. Is there any other way to do it other than making my init taking like 100 arguments?

In other words I would love it to be sth like that:

class MyClass:
    def __init__(self, q, w):
        self.q = q
        self.w = w
        self.e = 30
        self.r = 40
        
a = MyClass(10,20)
dct = {'q': 100, 'w': 200, 'e': 300, 'r': 400}
print('before:', tmp.q, tmp.w, tmp.e, tmp.r)
for i in dct:
    #do sth here
print('after:', tmp.q, tmp.w, tmp.e, tmp.r)
before: 10 20 30 40
after: 100 200 300 400
  • related, but not an actual dupe: https://stackoverflow.com/questions/25150955/python-iterating-through-object-attributes/38834900 – user120242 Jul 15 '20 at 13:18

2 Answers2

2

Here is how you can do that with keyword arguments:

class MyClass:
    def __init__(self, **q):
        self.__dict__.update(q)

a = MyClass(a=10, b=20, c=50, d=69)

print(a.a)
print(a.b)
print(a.c)
print(a.d)

Output:

10
20
50
69


With the dictionary:

class MyClass:
    def __init__(self, **q):
        self.__dict__.update(q)
        
dct = {'q': 100, 'w': 200, 'e': 300, 'r': 400}

a = MyClass(**dct)

print(a.q, a.w, a.e, a.r)

Output:

100 200 300 400
Red
  • 26,798
  • 7
  • 36
  • 58
  • I see, so if I want to create "fresh" instance I'd have to create it with dct that has all "starting" values, and if I create one that was already changed I use another one... that works, thank you :) – MrVernitrax Jul 15 '20 at 13:27
  • Arguable which is better and is contextual and subjective, but omitting ** in front of **q to avoid having to unpack a dict (and also allowing [[k,v],[k,v]...] form input) may make more sense, not only to avoid unpacking/packing, but also because logically MyClass is basically a wrapper class around the dict (or a level higher the JSON data), and it is only natural that the constructor take input in a form closer to an actual dict (and thus also the JSON) rather than just arbitrary named parameters, unnecessarily using the packing of arbitrary parameters and requirnig unpacking of input data. – user120242 Jul 15 '20 at 15:57
  • (Important to note that this will not use getters setters if they are defined). And an alternative syntax available here using vars(): https://stackoverflow.com/questions/9204671/pythonic-use-of-dict-in-the-function-self-init-of-a-class/9205029#9205029 I still don't really see any major problem with this implementation, but these things should all be noted. – user120242 Jul 15 '20 at 16:06
1

Instead of manipulating the class instances __dict__, I'd recommend using the setattr method.

class MyClass:
    def __init__(self, new_vals):
        for k, v in new_vals.items():
            setattr(self, k, v)

dct = {'q': 100, 'w': 200, 'e': 300, 'r': 400}

a = MyClass(dct)

print(a.q, a.w, a.e, a.r)

Imho there is a reason why internals such as __dict__ have two leading underscores. My general recommendation would be to avoid accessing any attributes/mehods with two leading underscores as long as possible. Also see below for a reference to the Python documentation considering this topic.

And if you want to reduce it to a one-liner, you could also use a "dummy" list comprehension. The underscore catches the None return, but it is not necessarily required.

class MyClass:
    def __init__(self, new_vals):
        _ = [setattr(self, k, v) for k, v in new_vals.items()]

A short reference to the Python documentation on manipulating the built in __dict__ attribute:

A special attribute of every module is __dict__. This is the dictionary containing the module’s symbol table. Modifying this dictionary will actually change the module’s symbol table, but direct assignment to the __dict__ attribute is not possible (you can write m.__dict__['a'] = 1, which defines m.a to be 1, but you can’t write m.__dict__ = {}). Modifying __dict__ directly is not recommended.

see Python documentation on built in types

JE_Muc
  • 5,403
  • 2
  • 26
  • 41
  • 1
    sooo with that one I can have my old constructor, but after I assign all values I can check if it has more keys than the "fresh" instance needed, and if it does I can update others... I like it, thank you :) – MrVernitrax Jul 15 '20 at 13:34
  • 1
    https://softwareengineering.stackexchange.com/questions/208047/is-modifying-an-objects-dict-to-set-its-properties-considered-pythonic and https://stackoverflow.com/questions/9728243/is-self-dict-updatekwargs-good-or-poor-style – user120242 Jul 15 '20 at 14:02
  • Yep, I've seen the first one before. But since the reference to the doc in the first link was for python 2, I thought that an "updated" link to the python 3 doc is a nice addition. :) – JE_Muc Jul 15 '20 at 14:07
  • I was also referring to it pointing out that the warning is for a different context that probably doesn't apply to this usage, and that it claims that its inclusion in the official docs is akin to endorsement. The second link also has the same usage, only pointing out the preferral for not depending on **kwargs style input unless it is actually necessary (which would apply to the other answer, but does not seem to be saying that using `__dict__.update` is inherently problematic). – user120242 Jul 15 '20 at 14:19
  • Do you have any other reasonings for why `__dict__` should be left untouched even in cases where it simplifies implementation like this one, other than that it's named (and exposed) in such a way that implies that it is internal or private (which is a reason in itself, but not as compelling or convincing as a more authoritive source or a concrete example of failure or inferior elegance in syntax)? – user120242 Jul 15 '20 at 14:22
  • For example, chaining of `__init__` is a pretty common practice. I'd like to see more evidence that touching `__dict__` is a problem – user120242 Jul 15 '20 at 14:25
  • No, honestly I don't know of any other reasonings (which doesn't mean that there are no other reasonings...) Maybe to yield reproducible results and compilable code if you use jit compilers like pypy or numba (once numba has all class methods implemented)? Besides that I just prefer the readability and style of `setattr` over `__dict__`, even if it means using loops or list comprehensions. – JE_Muc Jul 15 '20 at 14:28