3

I have a class with many instance variables with default values, which optionally can be overridden in instantiantion (note: no mutable default arguments).

Since it's quite redundant to write self.x = x etc. many times, I initialise the variables programmatically.

To illustrate, consider this example (which has, for the sake of brevity, only 5 instance variables and any methods omitted):

Example:

# The "painful" way
class A:
    def __init__(self, a, b=2, c=3, d=4.5, e=5):
        self.a = a
        self.b = b
        self.c = c
        self.d = d
        self.e = e

# The "lazy" way
class B:
    def __init__(self, a, b=2, c=3, d=4.5, e=5):
        self.__dict__.update({k: v for k, v in locals().items() if k!='self'})

# The "better lazy" way suggested
class C:
    def __init__(self, a, b=2, c=3, d=4.5, e=5):
            for k, v in locals().items():
                if k != 'self':
                    setattr(self, k, v)

x = A(1, c=7)
y = B(1, c=7)
z = C(1, c=7)

print(x.__dict__)  # {'d': 4.5, 'c': 7, 'a': 1, 'b': 2, 'e': 5}
print(y.__dict__)  # {'d': 4.5, 'c': 7, 'a': 1, 'b': 2, 'e': 5}
print(z.__dict__)  # {'d': 4.5, 'c': 7, 'a': 1, 'b': 2, 'e': 5}

So to make my life easier, I use the idiom shown in class B, which yields the same result as A.

Is this bad practice? Are there any pitfalls?

Addendum: Another reason to use this idiom was to save some space - I intended to use it in MicroPython. For whatever reason Because locals work differently there, only the way shown in class A works in it.

phoibos
  • 3,900
  • 2
  • 26
  • 26
  • 1
    Yes. It bad practice. Why not use a `dict()`? Must they be class attributes? – Christian Dean Nov 30 '16 at 23:07
  • 4
    Related: http://stackoverflow.com/q/1389180/3001761. – jonrsharpe Nov 30 '16 at 23:08
  • 2
    I can't think of any obvious pitfalls, but I do find it to be quite gross. – wim Nov 30 '16 at 23:08
  • As I write I DO have methods, I just left them out for the example. – phoibos Nov 30 '16 at 23:09
  • 1
    I guess autocomplete over the class' attributes won't be very useful in IDEs using this method – sirfz Nov 30 '16 at 23:12
  • Using `setattr` rather than a `__dict__` update will let you interface with `__slots__` or properties – donkopotamus Nov 30 '16 at 23:14
  • This works OK until you use other local variables in `__init__`. You can extract the exact argument names using `co_argcount` and `co_varnames` attributes of the function`s code object instead. – kindall Nov 30 '16 at 23:20
  • @kindall How do you get other local variables in `__init__`, when `locals()` is used on the first line? (serious question) – wim Nov 30 '16 at 23:22
  • The other variables are still locals. But actually, I tried it and you're right, `locals()` doesn't pick them up if they haven't been assigned values. Never mind! – kindall Nov 30 '16 at 23:24
  • @sirfz: autocomplete in IDEs is indeed a good point! – phoibos Nov 30 '16 at 23:50

2 Answers2

3

I would actually suggest using the code shown in class A. What you have is repetitive code, not redundant code, and repetitive isn't always bad. You only have to write __init__ once, and keeping one assignment per instance variable is good documentation (explicit and clear) for what instance variables your class expects.

One thing to keep in mind, though, is that too many variables that you can initialize as distinct parameters may be a sign that your class needs to be redesigned. Would some of the individual parameters make more sense being grouped into separate lists, dicts, or even additional classes?

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    Alex Martelli said something [should be in the stdlib](http://stackoverflow.com/questions/1389180/python-automatically-initialize-instance-variables#comment1230512_1389224) to avoid this repetition. And yet, 7 years later, there is nothing. So..... ¯\\_(ツ)_/¯ .... here, have a +1. – wim Dec 01 '16 at 00:25
1

Try a more pythonic approach:

class C:
  def __init__(self,a,b=2,c=3,d=4.5,e=5):
    for k,v in locals().iteritems():
      setattr(self,k,v)
c = C(1)
print c.a, c.b
1 2

This approach may be a line or two longer, but the line lengths are shorter, and your intent less convoluted. In addition, anyone who may try to reuse your code will be able to access your objects' attributes as expected.

Hope this helps.

Edit: removed second approach using kwargs bc it does not address default variable requirement.

the important take-away here is that a user of your code would not be able to access your object's attributes as expected if done like your example class B shows.

Anna B Nana
  • 39
  • 1
  • 5
  • `kwargs` is not useful in this case: it is not guaranteed that all needed variables are passed in, and unneeded variables could be passed in and stored as instance variables. – phoibos Nov 30 '16 at 23:41
  • 3
    Your second approach doesn't allow for defaults, which are clearly wanted in the question. In your first approach, exclude `self` from your iteration – donkopotamus Nov 30 '16 at 23:41
  • Good point, use the first approach if defaults are required. @donkopotamus: setattr expects 3 arguments. the first needs to be an instance of the object. if operating within the class definition, this instance must be self. – Anna B Nana Dec 01 '16 at 00:14
  • @donkopotamus oops, my bad, I see what you mean. good point. – Anna B Nana Dec 01 '16 at 00:15
  • he means that `self` is included in `locals()` since it is part of the method signature, so effectively you're creating the instance variable `self.self` pointing to, well, `self` – phoibos Dec 01 '16 at 00:19
  • @phoibos yeah I realized that right after I wrote the response lol :D – Anna B Nana Dec 01 '16 at 00:24