optional argument to a subclass of a class

A

Alex Hall

Hi all,
I am now trying to allow my classes, all of which subclass a single
class (if that is the term), to provide optional arguments. Here is
some of my code:

class Craft():
def __init__(self,
name,
isAircraft=False,
id=helpers.id(),
hits=0,
weapons=[]):
self.name=name
self.id=id
self.hits=hits
self.weapons=weapons
self.isAircraft=isAircraft
#end def
#end class


#now a class for each type of craft in the game
#each will subclass the Craft class, but each also has its own settings

class Battleship(Craft):
def __init__(self,
name,
maxHits):
Craft.__init__(self, name)
self.maxHits=maxHits
self.length=maxHits #in Battleship, each ship's length is the same
as how many times it can be hit
#end def
#end class


I want to be able to say something like
b=Battleship("battleship1", 4, weapons=["missile1","missile2"])
When I do that, though, I get a traceback on the above line saying
"type error: __init__() got an unexpected keyword argument 'weapons'".
What did I do wrong / what do I need to change so that any Craft
(Battleship, Carrier, and so on) can optionally provide a list of
weapons, or any other arguments to which I assign default values in my
Craft class? I hope this makes sense.
 
P

Patrick Maupin

Hi all,
I am now trying to allow my classes, all of which subclass a single
class (if that is the term), to provide optional arguments. Here is
some of my code:

class Craft():
 def __init__(self,
 name,
 isAircraft=False,
 id=helpers.id(),
 hits=0,
 weapons=[]):
  self.name=name
  self.id=id
  self.hits=hits
  self.weapons=weapons
  self.isAircraft=isAircraft
 #end def
#end class

#now a class for each type of craft in the game
#each will subclass the Craft class, but each also has its own settings

class Battleship(Craft):
 def __init__(self,
 name,
 maxHits):
  Craft.__init__(self, name)
  self.maxHits=maxHits
  self.length=maxHits #in Battleship, each ship's length is the same
as how many times it can be hit
 #end def
#end class

I want to be able to say something like
b=Battleship("battleship1", 4, weapons=["missile1","missile2"])
When I do that, though, I get a traceback on the above line saying
"type error: __init__() got an unexpected keyword argument 'weapons'".
What did I do wrong / what do I need to change so that any Craft
(Battleship, Carrier, and so on) can optionally provide a list of
weapons, or any other arguments to which I assign default values in my
Craft class? I hope this makes sense.

You overrode the __init__method from the superclass.

One thing you can do is in battleship, you can accept additional
keyword arguments:

def __init__(self, name, ..., **kw):

Then you could invoke the superclass's init:

Craft.__init__(self, name, **kw)

Regards,
Pat
 
A

Alex Hall

Hi all,
I am now trying to allow my classes, all of which subclass a single
class (if that is the term), to provide optional arguments. Here is
some of my code:

class Craft():
def __init__(self,
name,
isAircraft=False,
id=helpers.id(),
hits=0,
weapons=[]):
self.name=name
self.id=id
self.hits=hits
self.weapons=weapons
self.isAircraft=isAircraft
#end def
#end class

#now a class for each type of craft in the game
#each will subclass the Craft class, but each also has its own settings

class Battleship(Craft):
def __init__(self,
name,
maxHits):
Craft.__init__(self, name)
self.maxHits=maxHits
self.length=maxHits #in Battleship, each ship's length is the same
as how many times it can be hit
#end def
#end class

I want to be able to say something like
b=Battleship("battleship1", 4, weapons=["missile1","missile2"])
When I do that, though, I get a traceback on the above line saying
"type error: __init__() got an unexpected keyword argument 'weapons'".
What did I do wrong / what do I need to change so that any Craft
(Battleship, Carrier, and so on) can optionally provide a list of
weapons, or any other arguments to which I assign default values in my
Craft class? I hope this makes sense.

You overrode the __init__method from the superclass.
I know, I thought I had to in order to make all of Craft's attribs
available to the Battleship. Is that not the case?
One thing you can do is in battleship, you can accept additional
keyword arguments:

def __init__(self, name, ..., **kw):

Then you could invoke the superclass's init:

Craft.__init__(self, name, **kw)
So how do I get at anything inside **kw? Is it a list?
 
A

alex23

Patrick Maupin said:
One thing you can do is in battleship, you can accept additional
keyword arguments:

    def __init__(self, name, ..., **kw):

Then you could invoke the superclass's init:

    Craft.__init__(self, name, **kw)

_All_ of which is covered in the tutorial. At what point do we expect
people to actually put some effort into learning the language
themselves? Is this why we're constantly spammed with "write my code
for me" requests?

If you're asking _basic_ "how do I" questions, and you don't even have
the common sense to read through the support docs before doing so,
then you really should be directing questions here:
http://mail.python.org/mailman/listinfo/tutor
 
P

Patrick Maupin

So how do I get at anything inside **kw? Is it a list?

It's a dictionary. Use *args for a list. (As with self, the name is
whatever you want to use, but some sort of consistency is nice.)

One of the cool things about Python is how easy it is to play at the
command prompt:
.... print x,y
........ print kw
.... foo(**kw)
....
{'x': 5}
5 7
{'y': 'hi there'}
3 hi there
{'z': 42}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>


Regards,
Pat
 
A

Alex Hall

_All_ of which is covered in the tutorial. At what point do we expect
people to actually put some effort into learning the language
themselves? Is this why we're constantly spammed with "write my code
for me" requests?
I am certainly NOT asking anyone to write my code for me. In fact, I
almost never copy and paste code, since writing it myself helps me to
both remember it and examine it to see what is going on and figure out
why it works. I am basically teaching myself Python; my experience is
in Java (basic), JS (somewhat advanced), and a smattering of other
languages. I have rarely dealt with classes before, and certainly not
sub-classing. I have googled all of this and have read the docs and
tutorials, but nothing made sense enough for me to figure out why I
would be getting my particular error. I have since updated each ship's
__init__ to accept all the arguments that Craft accepts so that I can
support all optional arguments, but I thought subclassing was supposed
to take care of this for me... Suppose not.
If you're asking _basic_ "how do I" questions, and you don't even have
the common sense to read through the support docs before doing so,
then you really should be directing questions here:
http://mail.python.org/mailman/listinfo/tutor
Again, I have read the docs. I often find them quite useful, but there
are times where a new concept, in a relatively new language, just will
not work and a doc is not going to say "at this point in your code,
you should to A instead of B, because..."
 
S

Steven D'Aprano

I know, I thought I had to in order to make all of Craft's attribs
available to the Battleship. Is that not the case?

No, the opposite in fact.

One of the basic features of objects (at least as implemented by Python,
and many other languages) is inheritance. That is, a class inherits
behaviour and state (methods and attributes) from its parent class (or
classes).

So if you have a class A and a subclass B, B inherits behaviour from A:
.... def method(self):
.... print("method in A")
........ pass
.... method in A


But if you override method, then Python doesn't *automatically* call the
parent's method. How can it know where to call it, and what to do with
the result? Should it call it before the new method, or after, or
somewhere in the middle? It's impossible to tell.

.... def method(self):
.... print("called from C")
....called from C


If you want both behaviours, then you have to explicitly call the parent
class, and pass the current instance as an argument:
.... def method(self):
.... print("called from D")
.... A.method(self)
....called from D
method in A



Exactly the same rules apply to __init__, with the added complication
that frequently you want to modify the function signature:

.... def __init__(self, filling, bread='white'):
.... self.filling = filling
.... self.bread = bread
.... def __str__(self):
.... return ("%s on two slices of %s bread" %
.... (self.filling, self.bread))
........ def __init__(self):
.... Sandwich.__init__(self, "bacon, lettuce and tomato",
.... "sour dough")
....bacon, lettuce and tomato on two slices of sour dough bread



Hope this helps.
 
P

Peter Otten

Alex said:
Hi all,
I am now trying to allow my classes, all of which subclass a single
class (if that is the term), to provide optional arguments. Here is
some of my code:

class Craft():
def __init__(self,
name,
isAircraft=False,
id=helpers.id(),
hits=0,
weapons=[]):
self.name=name
self.id=id
self.hits=hits
self.weapons=weapons
self.isAircraft=isAircraft
#end def
#end class


#now a class for each type of craft in the game
#each will subclass the Craft class, but each also has its own settings

class Battleship(Craft):
def __init__(self,
name,
maxHits):
Craft.__init__(self, name)
self.maxHits=maxHits
self.length=maxHits #in Battleship, each ship's length is the same
as how many times it can be hit
#end def
#end class


I want to be able to say something like
b=Battleship("battleship1", 4, weapons=["missile1","missile2"])
When I do that, though, I get a traceback on the above line saying
"type error: __init__() got an unexpected keyword argument 'weapons'".
What did I do wrong / what do I need to change so that any Craft
(Battleship, Carrier, and so on) can optionally provide a list of
weapons, or any other arguments to which I assign default values in my
Craft class? I hope this makes sense.

You have to repeat the arguments for the base class in the Battleship
initializer. Battleship(...) will only invoke Battleship.__init__(...), not
Craft.__init__(...) -- so you have to do that explicitly inside
Battleship.__init__() while passing along all parameters it is supposed to
process. As simplified example:

class Craft(object):
def __init__(self, name, id=None, weapons=None):
if id is None:
id = helpers.id()
if weapons is None:
weapons = []
self.name = name
self.id = id
self.weapons = weapons

class Battleship(Craft):
def __init__(self, name, id=None, weapons=None, max_hits=None):
Craft.__init__(self, name, id, weapons)
self.max_hits = max_hits

Notice how using None to signal that a value was not provided simplifies
things; without it you'd have to put a meanigful default for weapons into
both the Craft and Battleship class. If you want a unique id for every Craft
providing helpers.id() as a default is just wrong; default arguments are
calculated once and you would end up with the same id for every Craft
instance.

Peter
 
A

Alex Hall

Alex said:
Hi all,
I am now trying to allow my classes, all of which subclass a single
class (if that is the term), to provide optional arguments. Here is
some of my code:

class Craft():
def __init__(self,
name,
isAircraft=False,
id=helpers.id(),
hits=0,
weapons=[]):
self.name=name
self.id=id
self.hits=hits
self.weapons=weapons
self.isAircraft=isAircraft
#end def
#end class


#now a class for each type of craft in the game
#each will subclass the Craft class, but each also has its own settings

class Battleship(Craft):
def __init__(self,
name,
maxHits):
Craft.__init__(self, name)
self.maxHits=maxHits
self.length=maxHits #in Battleship, each ship's length is the same
as how many times it can be hit
#end def
#end class


I want to be able to say something like
b=Battleship("battleship1", 4, weapons=["missile1","missile2"])
When I do that, though, I get a traceback on the above line saying
"type error: __init__() got an unexpected keyword argument 'weapons'".
What did I do wrong / what do I need to change so that any Craft
(Battleship, Carrier, and so on) can optionally provide a list of
weapons, or any other arguments to which I assign default values in my
Craft class? I hope this makes sense.

You have to repeat the arguments for the base class in the Battleship
initializer. Battleship(...) will only invoke Battleship.__init__(...), not
Craft.__init__(...) -- so you have to do that explicitly inside
Battleship.__init__() while passing along all parameters it is supposed to
process. As simplified example:

class Craft(object):
def __init__(self, name, id=None, weapons=None):
if id is None:
id = helpers.id()
if weapons is None:
weapons = []
self.name = name
self.id = id
self.weapons = weapons

class Battleship(Craft):
def __init__(self, name, id=None, weapons=None, max_hits=None):
Craft.__init__(self, name, id, weapons)
self.max_hits = max_hits

Notice how using None to signal that a value was not provided simplifies
things; without it you'd have to put a meanigful default for weapons into
both the Craft and Battleship class. If you want a unique id for every Craft
providing helpers.id() as a default is just wrong; default arguments are
calculated once and you would end up with the same id for every Craft
instance.
Yes, I found that out the hard way :), but it made perfect sense as
soon as I realized that I was not getting unique IDs; the class()
statement is just a fancy function call in Python, as I understand it,
so of course it would be called only once, not each time I make a new
instance.
 
E

Ethan Furman

Alex said:
I have since updated each ship's
__init__ to accept all the arguments that Craft accepts so that I can
support all optional arguments,

Ick. Now you'll have to change several things if you make one change to
the Craft class. Better to do it this way:

[borrowing Peter's example]

class Craft(object):
def __init__(self, name, id=None, weapons=None):
if id is None:
id = helpers.id()
if weapons is None:
weapons = []
self.name = name
self.id = id
self.weapons = weapons

class Battleship(Craft):
def __init__(self, name, max_hits=None, **kwds):
Craft.__init__(self, name, **kwds)
self.max_hits = max_hits



Notice the **kwds in Battleships's init, both in the parameter line, and
in the call to Craft's init. This way all keyword arguments that
Battleship doesn't directly support will be passed through to Craft.

~Ethan~
 
A

Alex Hall

Alex said:
I have since updated each ship's
__init__ to accept all the arguments that Craft accepts so that I can
support all optional arguments,

Ick. Now you'll have to change several things if you make one change to
the Craft class. Better to do it this way:

[borrowing Peter's example]

class Craft(object):
Curious: I do not pass Craft this "object" keyword and I have no
problems. What is it for? Just a convention, something like self being
called self?
def __init__(self, name, id=None, weapons=None):
if id is None:
id = helpers.id()
if weapons is None:
weapons = []
self.name = name
self.id = id
self.weapons = weapons

class Battleship(Craft):
def __init__(self, name, max_hits=None, **kwds):
Craft.__init__(self, name, **kwds)
self.max_hits = max_hits



Notice the **kwds in Battleships's init, both in the parameter line, and
in the call to Craft's init. This way all keyword arguments that
Battleship doesn't directly support will be passed through to Craft.

Thanks, the **kwords makes sense!! I implemented it much as you
described, and life is much easier; each ship or aircraft now has a
simple constructor, and I am still free to change any attrib I want
later or at creation time. A very powerful concept, and I now have
2-line constructors.

class Battleship(Craft):
def __init__(self, name, **kwords):
Craft.__init__(self, name, maxHits=4, **kwords) #call the
superclass's __init__

class Carrier(Craft):
def __init__(self, name, **kwords):
Craft.__init__(self, name, maxHits=5, **kwords) #call the
superclass's __init__
 
C

Chris Rebert

Alex said:
I have since updated each ship's
__init__ to accept all the arguments that Craft accepts so that I can
support all optional arguments,

Ick.  Now you'll have to change several things if you make one change to
the Craft class.  Better to do it this way:

[borrowing Peter's example]

class Craft(object):
Curious: I do not pass Craft this "object" keyword and I have no
problems. What is it for? Just a convention, something like self being
called self?

It causes the class to be "new-style". See
http://docs.python.org/reference/datamodel.html#new-style-and-classic-classes

Cheers,
Chris
 
A

Alex Hall

Alex Hall wrote:
I have since updated each ship's
__init__ to accept all the arguments that Craft accepts so that I can
support all optional arguments,

Ick. Now you'll have to change several things if you make one change to
the Craft class. Better to do it this way:

[borrowing Peter's example]

class Craft(object):
Curious: I do not pass Craft this "object" keyword and I have no
problems. What is it for? Just a convention, something like self being
called self?

It causes the class to be "new-style". See
http://docs.python.org/reference/datamodel.html#new-style-and-classic-classes Oh, I see. Thanks.

Cheers,
Chris
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,154
Members
46,701
Latest member
XavierQ83

Latest Threads

Top