When does a class warrant a sub-class? - python

For example, I am trying to build an enemy class for a simple game. Each enemy that spawns has a type which affects it stats which are fields in the enemy class.
class Enemy:
#Base Stats for all enemies
name = "foo"
current_health = 4
max_health = 4
attack = 0
defense = 0
armor = 0
initiative = 0
initMod = 0
alive = True
Should each type be a subclass of enemy like so..
class goblin(Enemy):
name = goblin;
current_health = 8
max_health = 8
attack = 3
defense = 2
armor = 0
def attack(){
//goblin-specific attack
}
But this method means that I would have to build a class for each individual type (which would be 80+ classes), or is there a better way to do it? This enemy is going to be randomized, so I was thinking the types could also be put into a dictionary which uses the names of the types as keywords. Although I'm not entirely sure how I could implement that.

If you want to go the dictionary route you could do something like this with a tuple being returned by the key:
enemy_types = {"goblin": ("goblin", 8, 8, 3, 2, 0, goblin_attack)}
def goblin_attack(enemy):
do_stuff()
Though you may want to use a named_tuple
https://stackoverflow.com/a/13700868/2489837
to make sure you don't mix up the fields in the tuple

If the set of attributes and possible actions for each enemy type is mostly common across types, I don't see a reason to do anything more complicated than defining an 80 item enumeration for the type, and using a single class called Enemy.
If you do need a variety of actions and such, perhaps you could group your 80 types into various collections that would then have common attributes (e.g. FlyingEnemy, UndeadEnemy, etc). Those would then become your sub-classes.

The goblin should probably be an instance of Enemy. However, in your example you added a method called attack. I would say the best way to do this is like this:
class Enemy:
#Base Stats for all enemies
def __init__ (self, **kwargs):
self.name = "foo"
self.current_health = 4
self.max_health = 4
self.attack = 0
self.defense = 0
self.armor = 0
self.initiative = 0
self.initMod = 0
self.alive = True
# Use this to specify any other parameters you may need
for key in kwargs:
exec ("self.%s = %s" % (key, kwargs[key])
Now you can create an instance of Enemy called goblin.
To answer your initial question of when to use a subclass, I would say that it is great to use sub-classes but may over complicate things in your case. Since you only have one method in your subclass you can easily just make a module and then have an argument in the __init__ that specifies the attack function. This will simplify your code more.
Note: If you are going to use subclasses make sure that in the __init__ of your subclass you call the __init__ of your baseclass with super().

Related

simple taming calculator project

class tame_dilo:
torpor = 250
def __init__(self, name, effect):
self.name = name
self.effect = effect
def attack(self):
self.torpor = self.torpor - self.effect
dilo = tame_dilo('dilo', 25)
dilo.attack()
print(dilo.torpor)
class tame_sable(tame_dilo):
torpor = 500
sable = tame_sable('sable', 25)
sable.attack()
print(sable.torpor)
I just started learning some oop on python and I decide to do this little project to practice a little.
What I want to know is, if Im using the proper way to relate the name of the creature with its torpor by using inheritance and some polymorphism to define a diferent torpor according to the creatur class.
And also i want to know what would be the proper method so the user can change the effect of the attack method like if you were using better equitment to knock the creature.
A dilo and a sable are a type of tame. They are instances, not classes.
Therefore, you need one class capable of holding different attributes.
Also, assuming torpor is health, or energy, I'm not sure why the attack function is affecting itself. Shouldn't an instance be attacking something else?
class Tame:
def __init__(self, name, effect, torpor):
self.name = name
self.effect = effect
self.torpor = torpor
def attack(self, other):
other.torpor -= self.effect
Now you create named instances
dilo = Tame('dilo', 25, 250)
sable = Tame('sable', 25, 500)
dilo.attack(sable)
print(sable.torpor)
To change the effect of a tame, just update it
dilo.effect += 10

Changing attributes of an object in a python class

So, my question is, if I were to make a Race based on this class, say, an elf. I make the elf like this:
elf = Race('Elf', 100, 50, 60, 90)
If the elf got into combat, how do I make his health go down in that one instance, or any of his other stats go up based on a bonus of some sort, like if he had some kind of equipment giving him +(stat)?
Kind of a specific question, I know.
Here is the class Race I have set up so far. . .
class Race(object):
race = None
health = None
strength = None
speed = None
endurance = None
def __init__(self, race, health, strength, speed, endurance):
self.race = race
self.health = health
self.strength = strength
self.speed = speed
self.endurance = endurance
def set_race(self, race):
self.race = race
def set_health(self, health):
self.health = health
def set_strength(self, strength):
self.strength = strength
def set_speed(self, speed):
self.speed = speed
def set_endurance(self, endurance):
self.endurance = endurance
Criticism is welcome, so long as its constructive!
Well, for one, the code wouldn't compile due to IndentationError but that's besides the point I suppose (so, please indent with 4 SPACES, not tab as per PEP-8 for each function body and class body).
With that said, you don't need any getters or setters because it's not pythonic. If you want to change the attribute of an object, just change the attribute. If you need to control how an attribute is set, then use properties.
Also, in your code below, you are actually not setting instance variables, you are setting member variables (please keep in mind I fixed the indentation):
class Race(object):
race = None
health = None
strength = None
speed = None
endurance = None
If you were to remove the above attributes but kept all the self.* in the __init__() function, you would then have instance attributes. The term self.* is stating "this is an attribute of myself, of my exact instance of me".
Also, please consider renaming race. You want a class to encapsulate all the attributes of an object. Think about it: a character's race doesn't have a health. A character can have a health, but the character also has a race. Both of these things are attributes of the character. I would suggest naming this class to class Character(object):.

Easy way to set multiple class attributes?

For example, if have a 'creature' class:
class Creature:
def __init__(self,name,hp,damage):
self.name = name
self.hp = hp
self.damage = damage
Now if I want to create an orc, I would do orc = Creature(0,0,'orc',20,4). However, with multiple types of creatures (troll, ogre, etc), this would be impractical: I have to remember the stats for each type of creature, and every time I want to create a creature I have to enter all those different stats.
Is there a way to do this in an easier way? I would for example do orc = Creature(orc), and it would automatically assign its name, hp and damage, which I defined in a different file/part of the code, in a dictionary for example.
Is this possible?
EDIT: I know about inheritance and all the stuff that comes with it, but I read somewhere that making child classes which just set the attributes of the parent to a set value is "not good", although I can't recall the exact reason.
You can always use a prototype:
proto = {
"orc" : (200, 300, 0)
"troll" : (500, 300, 0)
"wolf" : (100, 100, 0)
...
}
class Creature:
def __init__(self, name):
self.name = name
self.hp = proto[name][0]
...
The above of course is not complete or safe. But the point is that if you need to create many object of the same "type" just keep the default values aside someplace else.
You definitely want to use inheritance.
You should really read some tutorial on OOP before writing a game in an object-oriented language.

reinitialize an object with self.__init__(...)

Could anybody explain whether it is safe to reinitialize an object by calling "self.init(". as shown in the following simplified example?
The reason i'm asking is that i couldn't find this method neither in several python books nor in internet. There are some who suggest to list all attributes and set them to initial value one by one. Basically i want to set my object to initial state after it has finished some tasks.
class Book(object):
def __init__(self,name,author):
self.name = name
self.author = author
self.copies = 5
def reset(self):
self.__init__(self.name,self.author)
def incrementCopy(self):
self.copies += 1
Kite = Book('kite runner','khaled hosseini')
print 'initial number of copies:', Kite.copies
Kite.incrementCopy()
Kite.incrementCopy()
Kite.incrementCopy()
print '3 copies are added:', Kite.copies
Kite.reset()
print 'number of copies are reinitialized', Kite.copies
initial number of copies: 5
3 copies are added: 8
number of copies are reinitialized 5
The only thing special about __init__ is that it is called automatically when an instance is created. Other than that it is a normal method, and it is safe to use it to set your object back to its initial state.
That being said, just because it is safe doesn't mean it is a good idea. Other people looking at your code might be confused by it, and it isn't difficult to do everything in a reset method (that __init__ can even call) to be more explicit about how the method is being used.
I would consider it a very bad practice - you should not __init__ manually (unless calling __init__ of the parent class). Also, passing object's data back to __init__ is somewhat strange.
Why not something like this:
class Book(object):
def __init__(self,name,author):
self.name = name
self.author = author
self.reset()
def reset(self):
self.copies = 5
I consider is not unsafe, I have used it and nothing strange happens in the memory, but take into account that attributes defined in other methods will not be reset. Consider for example:
class Dummy:
def __init__(self):
self.x = 4
def plus_one(self):
self.x += 1
def define_other_variables(self):
self.y = 3
def reset(self):
self.__init__()
D = Dummy()
print(D.x) # 4
# print(D.y) will raise an AttributeError
D.plus_one()
D.plus_one()
# D.y do not exist
D.define_other_variables()
# D.y exist
print(D.x) # 6
D.reset()
print(D.x) # 4
print(D.y) # 3, still exist!!
Then, just remember to define every object in the init function. you could consider bad practice for this reason but I still think is elegant.

Inheritance : transform a base class instance to a child class instance

I have an instance of a base class, and then I want to make it an instance of a child class of this base class. Maybe I'm taking the problem in a wrong way and there's something important I didn't understand in OOP. Code is only there to illustrate and a very different approach can be suggested. Any help appreciated.
class Car(object):
def __init__(self, color):
self.color = color
def drive(self):
print "Driving at 50 mph"
class FastCar(Car):
def __init__(self, color, max_speed=100):
Car.__init__(self, color)
self.max_speed = max_speed
def drive_fast(self):
print "Driving at %s mph" %self.max_speed
one_car = Car('blue')
# After the instanciation, I discovered that one_car is not just a classic car
# but also a fast one which can drive at 120 mph.
# So I want to make one_car a FastCar instance.
I see a very similar question, but none of the answers suits my problem :
I don't want to make FastCar a wrapper around Car which would know how to drive fast : I really want that FastCar extends Car ;
I don't really want to use the __new__ method in FastCar to make some tests on the arguments and decide if __new__ has to return a new instance of Car or the instance I gave to it (example: def __new__(cls, color, max_speed=100, baseclassinstance=None)).
class FastCar(Car):
def __init__(self, color, max_speed=100):
Car.__init__(self, color)
self.max_speed = max_speed
def drive_fast(self):
print "Driving at %s mph" %self.max_speed
#staticmethod
def fromOtherCar(car):
return FastCar(car.color)
actually_fast = FastCar.fromOtherCar(thought_was_classic)
This is the standard way.
Depending on the real class layout, you may be able to do something like:
classic = Car('blue')
classic.__class__ = FastCar
classic.__dict__.update(FastCar(classic.color).__dict__)
classic.drive_fast()
But I wouldn't recommend it -- it's a hack, it won't always work, and the other way is cleaner.
Edit: Was just about to add basically what #PaulMcGuire's comment says. Follow that advice, he's right.
You can borrow the C++ notion of a "copy constructor" to do something like this.
Allow Car's constructor to take a Car instance, and copy all of its properties. FastCar should then accept either Car instances or FastCar instances.
So then, to convert the car, you would just do one_car = FastCar(one_car). Note that this will not affect references to the original Car object, which will remain pointing to the same Car.
Why not just use one class?
class Car(object):
def __init__(self, color, max_speed = 50):
self.color = color
self.max_speed = max_speed
def drive(self):
print "Driving at %s mph"%self.max_speed
c=Car('blue')
c.max_speed = 100
It is not common in OOP to change type (class) of a living object after instantiation. I know barely two languages that would allow that as a dirty hack. The whole purpose of types (classes) is to know beforehand what operations an object can and can not perform. If you want something like this, you're probably mistaking the idea of OOP.

Categories