How to initialize subclasses of an abstract class? - python

I'm trying to create a dark souls style text-based game. I've created an abstract class which outlines the characteristics of a dark souls class/character type. There are a couple of ways I have initialized it and I'm not sure if one is better than the other.
One important note is that in all of the subclasses, the only parameter I want is the name parameter, while all other variables will be set equal to values specific to that class/character type.
Here is one of the abstract classes I created and its subsequent subclass:
class Character(ABC):
def __init__(self, name, description, health, endurance, strength) -> None:
self.name = name
self.description = description
self.health = health
self.endurance = endurance
self.strength = strength
class Giant(Character):
def __init__(self, name) -> None:
super().__init__(name, description="giant", health=500, endurance=20, strength=100)
Here is the 2nd version:
class Character(ABC):
def __init__(self, name) -> None:
self.name = name
self.description = ''
self.health = 0
self.endurance = 0
self.strength = 0
class Giant(Character):
def __init__(self, name) -> None:
super().__init__(name)
self.description = "giant"
self.health = 500
self.endurance = 20
self.strength = 100
Is one way better than the other? Is there a totally different way which would be better? I'm pretty new to inheritance and abstract classes and I'd appreciate any help you'd be able to provide. Thanks!

I would indeed use neither approach, at least for what you've described here so far in this example.
Is the only way that different subclasses of Character are different that their stats are different? Or would they actually have different behavior?
Because if it's really only about different values, I'd instead just make Character a concrete rather than abstract class and then provide certain methods:
class Character:
def __init__(self, name, description, and so on):
set the values here
#classmethod
def create_giant(cls, name):
return cls(name=name, description="giant", health=500, and so on)
And then you'd make a giant like so:
my_giant = Character.create_giant(name="Olbrzym")
For your versions, they have slightly different semantics. In your version 1, someone calling super().__init__ will be forced to provide concrete values, whereas in version 2, they can just rely on the default values. Given that a character with health=0 probably doesn't make sense, I'd favor version 1.
You see that my version doesn't use inheritance. When would I use inheritance? When I can't easily differentiate the various character types (Giant, Dwarf, Elf?) through their health and endurance and strength values alone but actually need different behavior.
Like, if you imagine keeping with the simple approach and you end up with code that uses a lot of constructs like
if self.description == 'Giant':
do_giant_stuff()
elif self.description == 'Dwarf':
do_dwarf_stuff()
elif AND SO ON
that's a good sign you should be using inheritance instead.
EDIT:
So, to have the classes different behavior, version 1 or 2? Either would work, honestly. But there's a third way. Might be overkill but might come in handy: Hook methods.
Here's how that goes: Write things so that subclasses don't have to call super().__init__. Instead, write the init in the abstract class but have it call abstract methods to fill in the default values.
class Character(ABC):
def __init__(self, name):
self.name = name
self.description = self._get_class_description()
self.health = self._get_class_health()
...
#abstractmethod
def _get_class_description():
pass
... same for the other attributes
class Giant(Character):
def _get_class_description(self):
return "giant"
def _get_class_health(self):
return 500
...
It's called the hook method because the abstract base class describes the methods that a subclass should specify (the hooks) in order to fill in the gaps in behavior.

Related

How to use inner class in python?

class Tesla_car:
def __init__(self,yourname):
self.name = yourname
print("Hey'%s',I am a bot and I will tell you about....." %self.name)
self.cells = self.batteries()
def material(self,model_no):
self.model = model_no
print("your car",self.model," made from aluminium")
def color(self,color):
self.color = color
print("the color of your car is:'%s'" %self.color)
class batteries:
def __init__(self):
pass
def materials(self):
self.battery_name = "Tesla tabless 4680 cells"
self.chemicals = "Tesla uses Lithium-Nickle-cobalt-magnesium(NMC) mixed in 8:1:1 ratio"
EV_car = Tesla_car('Blah')
EV_car()
Hey everyone, I am trying to use nested classes but whenever I try to use the inner class by writing self.cells = self.batteries() It raises an error:"Tesla_car' object has no attribute 'batteries"
How do I fix it
It seems that you're trying to compose objects, but in the wrong way.
Actually your classes reflect a perfect irl scenario for implementing composition: cars are equipped (composed) with a set of different objects, batteries included.
When using composition, you'd typically define TeslaCar and Batteries as separate classes, and then you would assign an instance of Batteries to one of TeslaCar instance variables. E.g.:
class Batteries:
def __init__(self):
...
class TeslaCar:
def __init__(self):
self.batteries = Batteries()
...
The above code is just a simple skeleton of how composition is implemented, but you can adapt it to your case very easily.
Finally FYI, avoid nesting classes at all. It's unpythonic and you'll discover that it's useless as soon as you dive deep into simple oop patterns like composition and inheritance.
Change
self.batteries()
to
Tesla_car.batteries()
your batteries inner class is wrongly indented.
Currently it is inside the color method instead of being at the same level as the method.
class TeslaCar:
def color(...):
...
class Batteries:
...
instead, do:
class TeslaCar:
def color(...):
...
class Batteries:
...

Python: object with a list of objects - create methods based on properties of list members

I have a class which contains a list like so:
class Zoo:
def __init__(self):
self._animals = []
I populate the list of animals with animal objects that have various properties:
class Animal:
def __init__(self, speed, height, length):
self._speed = speed
self._height = height
self._length = length
You can imagine subclasses of Animal that have other properties. I want to be able to write methods that perform the same calculation but on different attributes of the Animal. For example, an average. I could write the following in Zoo:
def get_average(self, propertyname):
return sum(getattr(x, propertyname) for x in self.animals) / len(self.animals)
That string lookup not only messes with my ability to document nicely, but using getattr seems odd (and maybe I'm just nervous passing strings around?). If this is good standard practice, that's fine. Creating get_average_speed(), get_average_height(), and get_average_length() methods, especially as I add more properties, seems unwise, too.
I realize I am trying to encapsulate a one-liner in this example, but is there a better way to go about creating methods like this based on properties of the objects in the Zoo's list? I've looked a little bit at factory functions, so when I understand them better, I think I could write something like this:
all_properties = ['speed', 'height', 'length']
for p in all_properties:
Zoo.make_average_function(p)
And then any instance of Zoo will have methods called get_average_speed(), get_average_height(), and get_average_length(), ideally with nice docstrings. Taking it one step further, I'd really like the Animal objects themselves to tell my Zoo what properties can be turned into get_average() methods. Going to the very end, let's say I subclass Animal and would like it to indicate it creates a new average method: (the following is pseudo-code, I don't know if decorators can be used like this)
class Tiger(Animal):
def __init__(self, tail_length):
self._tail_length = tail_length
#Zoo.make_average_function
#property
def tail_length(self):
return self._tail_length
Then, upon adding a Tiger to a Zoo, my method that adds animals to Zoo object would know to create a get_average_tail_length() method for that instance of the Zoo. Instead of having to keep a list of what average methods I need to make, the Animal-type objects indicate what things can be averaged.
Is there a nice way to get this sort of method generation? Or is there another approach besides getattr() to say "do some computation/work on an a particular property of every member in this list"?
Try this:
import functools
class Zoo:
def __init__(self):
self._animals = []
#classmethod
def make_average_function(cls, func):
setattr(cls, "get_average_{}".format(func.__name__), functools.partialmethod(cls.get_average, propertyname=func.__name__))
return func
def get_average(self, propertyname):
return sum(getattr(x, propertyname) for x in self._animals) / len(self._animals)
class Animal:
def __init__(self, speed, height, length):
self._speed = speed
self._height = height
self._length = length
class Tiger(Animal):
def __init__(self, tail_length):
self._tail_length = tail_length
#property
#Zoo.make_average_function
def tail_length(self):
return self._tail_length
my_zoo = Zoo()
my_zoo._animals.append(Tiger(10))
my_zoo._animals.append(Tiger(1))
my_zoo._animals.append(Tiger(13))
print(my_zoo.get_average_tail_length())
Note: If there are different zoos have different types of animals, it will lead to confusion.
Example
class Bird(Animal):
def __init__(self, speed):
self._speed = speed
#property
#Zoo.make_average_function
def speed(self):
return self._speed
my_zoo2 = Zoo()
my_zoo2._animals.append(Bird(13))
print(my_zoo2.get_average_speed()) # ok
print(my_zoo.get_average_speed()) # wrong
print(my_zoo2.get_average_tail_length()) # wrong

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

Using certain attributes of an object to make other objects in a different class

For a program that creates a timetable for a doctor(specialist) I want to use certain attributes of an object created by a different class to be used in the class that makes the timetable for the doctor.
class makePatient(object):
def __init__(self,name,room):
self.name = name
self.room = room
def getPatient(self):
print(self.name)
print(self.room)
class makeSpecialist(object):
def __init__(self,name,specialization,timetable):
self.name = name
self.specialization = specialization
self.timetable = timetable
def getSpecialist(self):
print(self.name)
print(self.specialization)
print(self.timetable)
class makeAgenda(object):
def addAgenda(self):
self.timetable.append()
#I want to append the name of the patient I have defined here.
print(self.timetable)
patient1 = makePatient("Michael","101")
specialist1 = makeSpecialist("Dr. John","Hematology",[])
What do I do now, to make sure that the name "Michael" gets added to the list [] of specialist Dr. John?
Thanks in advance, I will provide further details if necessary!
I think another approach would be better; you can put the whole makePatient object into the timetable for the specialist:
specialist1 = makeSpecialist("Dr. John", "Hematology", [patient1])
Now you can access the names and other attributes of the patients in a specialist's timetable:
for patient in specialist1.timetable:
print(patient.name)
You can also define a __repr__ method to tell Python how to display an object, rather than the current getPatient:
class makePatient(object):
# ...
def __repr__(self):
return "{0} (room {1})".format(self.name, self.room)
Now when you print the whole timetable:
>>> print(specialist1.timetable)
You get the necessary information:
[Michael (room 101)]
Note also that the classes should probably be called, simply, Patient, Specialist and Agenda; the make is implied.
Finally, you will get errors in makeAgenda.addAgenda as, without an __init__, self.timetable doesn't exist for a makeAgenda object, and an empty append() doesn't do anything anyway.
Classes are often used to represent entities and operations allowed on them, include constructing, or making, new instances of them. Therefore, your classes would be better named simplyPatient, Specialist, andAgenda. The name of the method that constructs a new instance of any class in Python is always__init__().
That said, after creating aPatientand aSpecialistyou could then add patient instances to the specialist's timetable/agenda by passing it to aSpecialistmethod specifically designed for that purpose. In other words, a Specialist "has-a" Agenda instance namedtimetableand to which patients can be added via an appropriately namedadd_to_timetable()method.
Here's what I mean -- note I've modified your code to follow PEP 8 -- Style Guide for Python Code guidelines which I also suggest that you follow:
class Agenda(object):
def __init__(self):
self.events = []
def append(self, event):
self.events.append(event)
class Patient(object):
def __init__(self, name, room):
self.name = name
self.room = room
def get_patient(self):
print(self.name)
print(self.room)
class Specialist(object):
def __init__(self, name, specialization):
self.name = name
self.specialization = specialization
self.timetable = Agenda()
def add_to_timetable(self, patient):
self.timetable.append(patient)
def get_specialist(self):
print(self.name)
print(self.specialization)
print(self.timetable)
specialist1 = Specialist("Dr. John", "Hematology")
patient1 = Patient("Michael", "101")
specialist1.add_to_timetable(patient1)
I'm not too sure what you're trying to accomplish here with method that just print values or with the makeAgenda class, but here's how you can get Michael in Dr. John's list:
specialist1.timetable.append(patient1.name)

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