Reputation: 2912
Hi I have the following code, which attempts to create an instance of a class and assign argument values to it. I am trying to use *args to do this as follows:
def main():
testdata = ['FDR', False, 4, 1933]
apresident = President(testdata)
print apresident
print apresident.alive
class President:
id_iter = itertools.count(1)
#def __init__(self, president, alive, terms, firstelected):
def __init__(self, *args):
self.id = self.id_iter.next()
self.president = args[0]
self.alive = args[1]
self.terms = args[2]
self.firstelected = args[3]
I get a "tuple index out of range" error. As you can see from the commented line, I was previously using positional arguments to accomplish this (which worked), and used lines like the following to do it:
self.president = president
What is the right way to use *args in this case? should I be using *kwargs?
Upvotes: 2
Views: 5175
Reputation: 1614
You should only use *args
if you do not know how many arguments will be passed to function.
In your case, it looks like you need all of president, alive, terms, firstelected
. There is nothing wrong with having a constructor that takes all of those as parameters.
*kwargs
is used for a few reasons. One is if you have default values that should be used, unless the user wants to specify them.
For more info, see this question, this question, and the official documentation.
I would recommend you do have the datedied
property for each president. If they haven't died yet, then the value should be None
. When you extend only certain instances with functionality, it becomes harder (but not impossible) to reason about the code.
Having said that, if you wanted arbitrary properties for each president that clearly won't be applicable to each instance, then you could use keyword arguments. But adding properties isn't limited to the constructor. I would simply use setattr() and getattr().
class President(object):
def __init__(self, *args, **kwargs):
for name, value in kwargs.items():
# Make each keyword-argument a property of the class.
setattr(self, name, value)
tVar = President(is_cool=True)
print tVar.is_cool # Returns True
Upvotes: 2
Reputation: 59128
You're passing only one argument to President()
, which is the list
['FDR', False, 4, 1933]
If you want to pass the items in that list as separate arguments, you do it like this:
apresident = President(*testdata) # note the * character
As Colonel Panic points out, in your example the use of argument-unpacking is a little pointless - presumably your actual use case is more complex, and justifies its use.
UPDATE:
Your comment is really a follow-up question which would be better as a separate question, but:
def main():
testdata = {
"president": "FDR",
"alive": False,
"terms": 4,
"firstelected": 1933,
}
apresident = President(**testdata)
anotherpresident = President(president="BHO", terms=2, firstelected=2008)
print apresident
print apresident.alive
print anotherpresident
print anotherpresident.alive
class President:
id_iter = itertools.count(1)
#def __init__(self, president, alive, terms, firstelected):
def __init__(self, **kwargs):
self.id = self.id_iter.next()
self.president = kwargs.get("president", None)
self.alive = kwargs.get("alive", True)
self.terms = kwargs.get("president", 1)
self.firstelected = kwargs.get("president", None)
This shows how you can define default values as well.
Upvotes: 4
Reputation: 55197
You're calling President(testdata)
, when you should be doing President(*testdata)
in order to unpack the list as you call the constructor.
Right now, you're essentially passing a single argument (the list), hence the IndexError
: you're passing a single argument, so args
is equal to [testdata]
and not testdata
.
As mentioned in the other answer though, it's not very Pythonic to use *args
in your constructor here. You know which arguments you expect, so just use these.
It's OK-ish to use it when you call the function, though.
Upvotes: 1