Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python Object-Oriented Python (retired) Inheritance Intro to Inheritance

Is using setattr() with **kwargs a good idea?

In the previous lesson I started messing around with ways to eliminate the redundancy of self.hit_points = kwargs.get('hit_points', 5) and ended up doing the exact same thing Kenneth is teaching in this lesson (setattr(self, key, value)).

In #python on IRC, I was told by someone this is a bad practice though. I just took his word for it since he seemed like an experienced programmer (and I've learned to just trust best practices), but after seeing Kenneth do this, I'm not sure what to think.

A few of the problems he mentioned were:

  • typos break things (which I suppose they break stuff anyway)
  • it's harder to debug
  • makes code harder to understand
  • lose autocompletion support

Kenneth Love care to chime in?

Actually, typos would fail silently.

For instance: colours='red' would just add the attribute colours when really we were looking for colors.

1 Answer

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,454 Points

As you mentioned, using setattr(self, key, value) can eliminate redundancy especially if there are a lot of kwargs to work through. One way to catch typos and rogue value would to use a "whitelist":

class MyClass():
    def __init__(self, **kwargs):
        whitelist = ['username', 'email', 'color', 'cheese']
        for key, value in kwargs.items():
            if key in whitelist:
                setattr(self, key, value)

Testing:

$ python3
Python 3.4.3 (default, Oct 14 2015, 20:28:29) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class MyClass():
...     def __init__(self, **kwargs):
...         whitelist = ['username', 'email', 'color', 'cheese']
...         for key, value in kwargs.items():
...             if key in whitelist:
...                 setattr(self, key, value)
... 
>>> c1 = MyClass()
>>> c1.username
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'username'

>>> c2 = MyClass(username='bob')
>>> c2.username
'bob'

>>> c3 = MyClass(car='Tesla')
>>> c3.car
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'car'

That's a pretty cool trick!

Maybe I'm being paranoid, but it still seems like some things might fall through the cracks. For instance, I can set car, but if I don't call it for a while, it might not show up until it's a problem.

class MyClass():
    def __init__(self, **kwargs):
        whitelist = ['username', 'email', 'color', 'cheese']
        for key, value in kwargs.items():
            if key in whitelist:
                setattr(self, key, value)

c1 = MyClass(username="Foo", car="Telsa")
print(c1.username)  # Nothing to see here


class MyClass2():
    def __init__(self, username, email, color, cheese):
        return

c2 = MyClass2(username="Foo", car="Telsa")  # Error'd!
print(c2.username)  # Never gets here

Am I being overly cautious?

Chris Freeman
Chris Freeman
Treehouse Moderator 68,454 Points

You can add an else in the check to raise an error or print a warning:

class MyClass():
    def __init__(self, **kwargs):
        whitelist = ['username', 'email', 'color', 'cheese']
        for key, value in kwargs.items():
            if key in whitelist:
                setattr(self, key, value)
            else:
                raise ValueError("unexpected kwarg value", key)