6

I want the class to do the same as the following:

class Player:
    def __init__(self, **kwargs):
        try:
            self.last_name = kwargs['last_name']
        except:
            pass
        try:
            self.first_name = kwargs['first_name']
        except:
            pass
        try:
            self.score = kwargs['score']
        except:
            pass

But this looks really sloppy to me. Is there a better way to define this __init__ method? I would like all of the keyword arguments to remain optional.

John Doh
  • 73
  • 1
  • 1
  • 4
  • 4
    You normally would want to set `self.score` to a default even if `kwargs['score']` isn't available. And *don't use a blanket `except`* (see [Why is "except: pass" a bad programming practice?](http://stackoverflow.com/q/21553327)); at most use `except KeyError`, but you are hardcoding the names anyway, so why use `**kwargs` at all? – Martijn Pieters Jan 01 '15 at 16:28
  • only 3 keyword args ? then `def __init__(self, last_name = Null, first_name = Null, score = Null):` – Bhargav Rao Jan 01 '15 at 16:28
  • @BhargavRao you should post it as an answer (it's a very valid answer). – thebjorn Jan 01 '15 at 16:30
  • 1
    @BhargavRao: *Null*? What language are you confusing Python with here? – Martijn Pieters Jan 01 '15 at 16:33

6 Answers6

25

If you only have 3 arguments, then Bhargav Rao's solution is more appropriate, but if you have a lot of potential arguments then try:

class Player:
    def __init__(self, **kwargs):
        self.last_name = kwargs.get('last_name')
        # .. etc.

kwargs.get('xxx') will return the xxx key if it exists, and return None if it doesn't. .get takes an optional second argument that is returned if xxx is not in kwargs (instead of None), e.g. to set the attribute to the empty string use kwargs.get('xxx', "").

If you really want the attribute to be undefined if it isn't in the kwargs, then this will do it:

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

it would be surprising behavior so I would suggest not doing it this way.

thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • I've added an initial sentence about applicability. – thebjorn Jan 01 '15 at 16:39
  • 1
    This approach is dangerous, and may override class methods when colliding with keywords in kwargs. Consider adding `__slots__` or some kind of validity of the keywords you want to assign, as described below by @martineau https://stackoverflow.com/a/27732607/4369617 – redlus Aug 21 '18 at 16:46
  • @redlus that might be the intended semantics, i.e. class vars are defaults that can be overridden by `__init__` arguments. If I wrote this today I would spell it as `self.__dict__.update(kw)` . We're all adults here, so this method is fine. If I wanted to introduce a limitation to only change existing vars I would insert a line before the `for`-loop: `if len(self.__dict__) != len(set(kwargs) | set(self.__dict__)): raise ValueError('...')`. – thebjorn Aug 21 '18 at 19:26
  • @thebjorn you're right, but I meant a deeper effect than this. You can accidentally override a class method and get an exception. Here is a quick example: https://pastebin.com/GqyCycf2 – redlus Aug 22 '18 at 08:39
  • @redlus well... don't do that then. Python is not a bdsm language, it let's you do many things that seem abhorrent to followers of such bdsm'ish languages(!) E.g. you can assign to any instance variable (like `a.method = 'blah'`) -- and even more fun, you could instantiate Player as `p = Player(__class__=NotAPlayer)`, that's just Python being Python ;-) – thebjorn Aug 22 '18 at 11:41
  • @thebjorn lol on bdsming the language :)) If you're creating objects based on arbitrary input such defenses and assertions are necessary to prevent accidental mistakes which can take down the program. I'm all for having these defenses anyways :) – redlus Aug 22 '18 at 12:10
17

If you have only only 3 keyword args, Then this would be better.

class Player:

    def __init__(self, last_name=None, first_name=None, score=None):
        self.last_name = last_name
        self.first_name = first_name
        self.score = score
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
6

Here's one way to do it that would make it easy to change:

class Player:
    _VALID_KEYWORDS = {'last_name', 'first_name', 'score'}

    def __init__(self, **kwargs):
        for keyword, value in kwargs.items():
            if keyword in self._VALID_KEYWORDS:
                setattr(self, keyword, value)
            else:
                raise ValueError(
                    "Unknown keyword argument: {!r}".format(keyword))

Sample usage:

Player(last_name="George", attitude="snarky")

Results:

Traceback (most recent call last):
  File "keyword_checking.py", line 13, in <module>
    Player(last_name="George", attitude="snarky")
  File "keyword_checking.py", line 11, in __init__
    raise ValueError("Unknown keyword argument: {!r}".format(keyword))
ValueError: Unknown keyword argument: 'attitude'
martineau
  • 119,623
  • 25
  • 170
  • 301
5

You can use keyword arguments:

class Player:

    def __init__(self, last_name=None, first_name=None, score=None):
        self.last_name = last_name
        self.first_name = first_name
        self.score = score

obj = Player('Max', 'Jhon')
print obj.first_name, obj.last_name


Jhon Max

With arguments **kwargs

class Player:
    def __init__(self, **args):

        self.last_name = args.get('last_name')

        self.first_name = args.get('first_name')

        self.score = args.get('score', 0) # 0 is the default score.

obj = Player(first_name='Max', last_name='Jhon')

print obj.first_name, obj.last_name, obj.score


Max Jhon 0
Community
  • 1
  • 1
Tanveer Alam
  • 5,185
  • 4
  • 22
  • 43
0

Can i try this way:

#!/usr/bin/python

class Player(object):
    def __init__(self, **kwargs):
        for key, val in kwargs.items():
            self.__dict__[key] = val

obj = Player(first_name='First', last_name='Last')
print obj.first_name
print obj.last_name

newobj = Player(first_name='First')
print newobj.first_name

Output:

First
Last
First
James Sapam
  • 16,036
  • 12
  • 50
  • 73
0

It depends on the end result you want. If you want to create a class where the attributes are defined in the dictionary you can use setattr.

class Player:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)             


In [1]: player = Player(first_name='john', last_name='wayne', score=100)

In [2]: player.first_name
Out[2]: 'john'

In [3]: player.last_name
Out[3]: 'wayne'

In [4]: player.score
Out[4]: 100

In [5]: player.address
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-0f7ee474d904> in <module>()
----> 1 player.address

AttributeError: 'Player' object has no attribute 'address'
noxdafox
  • 14,439
  • 4
  • 33
  • 45