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

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.

Related

How to initialize subclasses of an abstract class?

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.

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

Python Classes: Variable subclass creation in the base class's methods

Here's the coding problem I am trying to solve... I have a base class, let's say Animal, and it has two subclasses, say Dog and Cat. My class Animal has a method, make_baby(), that both Dog and Cat will inherit. The trick I'm having trouble pulling off is that I want the return value to be a new instance of the subclass that called the function but with different attribute values, i.e. Dog.make_baby() should return a new Dog and Cat.make_baby() will return a new Cat.
I previously tried returning "type(self)()", but this is no good because type() return a type object, not a class.
Here is the full example code:
Class Animal():
def __init__(self, color):
self.color = color
def make_baby():
new_color = rand_color # a randomly chosen color
return #??? new class of the same type that called the method
Class Dog(Animal):
def pet():
print '*pant*'
Class Cat(Animal):
def pet():
print 'purrr'
So I'd like to avoid writing a make_baby() method for Dogs and Cats because the idea is that the method is exactly the same except for the returned class. I'd also like to avoid a bunch of if statements because I'd like to make and arbitrarily large number of subclasses to Animal.
You wrote:
this is no good because type() return a type object, not a class.
A type is a class, if you're using new-style classes. If you're using Python 3, you're set; all Python 3 classes are "new-style." If you're using Python 2.x, derive your class from object (or from something else that derives from object, like any built-in Python type).
But what you really want here is a class method, where you get a reference to the class passed in automatically.
class Animal(object):
def __init__(self, color):
self.color = color
#classmethod
def make_baby(cls):
return cls(rand_color) # randomly-chosen color
You can call it on the class (e.g. Animal.make_baby() or Dog.make_baby()) or on an instance; either way the method still receives the class as the first argument.
type() can be used to construct entirely new classes. What you want is:
class Animal():
def __init__(self, color):
self.color = color
def make_baby(self):
new_color = rand_color # a randomly chosen color
return self.__class__(new_color)
You approach will totally work! Just use new style classes.
Class Animal(object):
def __init__(self, color):
self.color = color
def make_baby(self):
new_color = rand_color # a randomly chosen color
return type(self)(new_color)
Class Dog(Animal):
def pet():
print '*pant*'
Class Cat(Animal):
def pet():
print 'purrr'
However, if make_baby(self) is not relying on details of self, what you want is a class-wide factory method, like in #Kindall's answer.

Categories