Python inheritance questions - python

I'm a self-taught programmer and currently I'm learning elementary python; read material with inheritance in python yet still don't understand how it works.
Here, players is a child class of Football; however, it can't inherit the function/ objects (.getCoach) from Football. (error: p.getCoach())- the final line.
May I know which part goes wrong?
class Football:
def __init__(self, name, ranking, coach):
self.name= name
self.rank = ranking
self.coach= coach
def getName(self):
print(self.name)
def getRank(self):
print(self.name+ "was in" + self.rank)
def getCoach(self):
if self.rank<5:
print(self.coach+ "is a bad coach")
else:
print(self.coach+ "is a good coach")
class Players(Football):
def __init__(self, Appearances, Score):
self.Appearances= Appearances
self.Score= Score
def getapp(self):
print(self.Appearances)
def getscore(self):
print(self.Score)
p = Players(5,5, "Vincent")
p.getCoach()

You called Players with the arguments to Football's __init__ method, yet your Players.__init__ doesn't call Football.__init__. And it appears you want to pass two additional values when initializing a Player.
The following uses super to call the parent's __init__ function. Note that Player.__init__ needs to take all of the parameters needed for Football.__init__, unless you have some other way of coming up with them. It lets the Football class initialize the fields it cares about (via super()), then Player initializes its own fields. Note that this code assumes Python 3 because it calls super() with no arguments; the syntax for Python 2 is a bit different.
class Football:
def __init__(self, name, ranking, coach):
self.name= name
self.rank = ranking
self.coach= coach
def getName(self):
print(self.name)
def getRank(self):
print(self.name+ "was in" + self.rank)
def getCoach(self):
if self.rank<5:
print(self.coach+ "is a bad coach")
else:
print(self.coach+ "is a good coach")
class Players(Football):
def __init__(self, name, ranking, coach, Appearances, Score):
super().__init__(name, ranking, coach) # calls Football.__init__()
self.Appearances= Appearances
self.Score= Score
def getapp(self):
print(self.Appearances)
def getscore(self):
print(self.Score)
p = Players(5,5, "Vincent", 2, 3)
p.getCoach()

There are lot of issues here. First.
relationship does not make sense.
parent class is not derived from object class so inheritance will not work. (alteast in 2.7)
it seems like your player class contains information about Coach and not actual player. and so it doesn't make any sense. if you want to store information about Coach. Create a Has-A relationship. in simpler terms create a separate class named Coach and instantiate it inside the class Footballer. or player.
I would suggest to go through PEP8 standards.
However not addressing other issue, just to see what's wrong with the code.
This might work.
class Player(object):
def __init__(self, name, ranking, coach):
self.name= name
self.rank = ranking
self.coach= coach
def get_name(self):
print(self.name)
def get_rank(self):
print(self.name + "was in" + self.rank)
def get_coach(self):
if self.rank<5:
print(self.coach + "is a bad coach")
else:
print(self.coach + "is a good coach")
class Footballer(Player):
def __init__(self, name, ranking, coach, appearances, score):
self.appearances= appearances
self.score= score
super(Footballer, self).__init__(name, ranking, coach)
def get_appearances(self):
print(self.Appearances)
def get_score(self):
print(self.Score)
p = Footballer("name", 20.2, "coach name", "appearances", 200)
p.get_coach()
Here you can say, a Footballer is a player.

Related

How does python3 diamond inheritance works for data fields? How to initialize the inherited fields with super()._init__?

I was checking this problem to understand multiple inheritance and I got stuck.
How can I set the fields of the inherited objects from the last class?
class Vehicle():
def __init__(self, name:str, seats:int):
self.name = name
self.seats = seats
def print_vehicle(self):
print(f'Vehicle {self.name} has {self.seats} seats')
class Boat(Vehicle):
def __init__(self, name:str, seats:int, engine_type:str):
super().__init__(name, seats)
self.engine_type = engine_type
def print_vehicle(self):
print(f'Boat {self.name} has {self.seats} seats and engine {self.engine_type}')
class Car(Vehicle):
def __init__(self, name:str, seats:int, fuel:str):
super().__init__(name, seats)
self.fuel = fuel
def print_vehicle(self):
print(f'Car {self.name} has fuel {self.fuel}')
class AnphibiousCar(Boat, Car):
def __init__(self, name, seats, engine_type, fuel):
super(AnphibiousCar, self).__init__(name, seats, engine_type) # ???
def print_vehicle(self):
print(f'Anphibious car {self.name} has {self.seats} seats and {self.engine_type} - {self.fuel} engine')
ac = AnphibiousCar('name', 4, 'piston', 'gas')
ac.print_vehicle()
The point is that each class should focus only on the stuff which is its direct responsibility; the rest should be delegated to superclasses (and note that, when you deal with such a cooperative inheritance with super(), your methods that call super() should not need to know what exactly are the actual superclasses, in particular the nearest one - as this can change, depending on the actual class of self).
So let's reimplement your classes (with a bunch of explanations in the comments):
class Vehicle:
# Added the `*,` marker to make `name` and `seats` *keyword-only*
# arguments (i.e., arguments that are identified only by their
# *names*, never by their positions in a call's arguments list).
def __init__(self, *, name: str, seats: int):
self.name = name
self.seats = seats
# We abstract out class-specific features into separate methods,
# keeping in the `print_vehicle()` method only the common stuff,
# so that in subclasses we'll need to customize only those methods
# (`list_features()`, `get_type_label()`), *not* `print_vehicle()`.
def print_vehicle(self):
vehicle_type_label = self.get_type_label()
features = ', '.join(self.list_features())
print(f'{vehicle_type_label} {self.name}: {features}.')
# Side note: the `list[str]` type annotation requires Python 3.9
# or newer (for compatibility with older versions you need to
# replace it with `List[str]`, using `from typing import List`).
def list_features(self) -> list[str]:
return [f'has {self.seats} seats']
# This implementation is, in fact, quite generic (so that
# in most subclasses we will *not* need to customize it).
def get_type_label(self) -> str:
return self.__class__.__name__
class Boat(Vehicle):
# Only `Boat`-specific arguments (as keyword-only ones, as above...)
# are declared here explicitly. Any other are treated as a "black
# box", just being passed into superclasses...
def __init__(self, *, engine_type: str, **kwargs):
super().__init__(**kwargs)
self.engine_type = engine_type
# Also here we focus only on this-class-specific stuff, handling
# other stuff as "agnostically" as possible...
def list_features(self) -> list[str]:
return super().list_features() + [f'has {self.engine_type} engine']
class Car(Vehicle):
# And analogously...
def __init__(self, *, fuel: str, **kwargs):
super().__init__(**kwargs)
self.fuel = fuel
def list_features(self) -> list[str]:
return super().list_features() + [f'needs {self.fuel} fuel']
class AmphibiousCar(Boat, Car):
# Note: here we get our `__init__()` and `list_features()`
# for free (!), as the superclasses provide all we need
# when it comes to those two methods.
# The only thing we may want to customize is:
def get_type_label(self) -> str:
return 'Amphibious car'
ac = AmphibiousCar(
name='Julia-III',
seats=4,
engine_type='piston',
fuel='gas')
# "Amphibious car Julia-III: has 4 seats, needs gas fuel, has piston engine."
ac.print_vehicle()
As a further reading, I'd recommend: https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
You have some errors:
super(AnphibiousCar, self).__init__(name, seats, engine_type) could become
Boat.__init__(self, name, seats, engine_type) so calling the class you could
give information about how to initialize it.
there is a missing parameter in Boat where you should give a fuel argument
to the superclass Vehicle, like super().__init__(name, seats, "oil")
As you can note if you use super you don't need to pass self, if you use
the class name you are using it.
My point of view is that, yes, is good to understand, but don't loose to much
time as this kind of multiple inheritance is only theoretical and practically
not used in real coding. This in fact can cause a lot of confusion and add
boilerplate... "new" languages like, for example, Rust do not even provide
inheritance.
Just to say: "Yes, study it, but keep it simple" ^_^

Python3: Class Composition and derived methods

Good morning,
I'm trying something like this:
class Fish:
def __init__(self, name):
self.name = name
def swim(self):
print(self.name,"is swimming!")
My ourpose is to extend the class to Aquarium, which cointains a dictionary of fishes:
class Aquarium(Fish):
def __init__(self, **kwargs):
self.fishes ={}
for _, name in kwargs.items():
self.fishes[name] = Fish.__init__(self,name)
def how_many(self):
print("In acquarium there are",len(self.fishes),"fishes")
def all_swimming(self):
#???
Is it possible to implement something like Aquarium.swim() to use the method of all classes inserted? I tried it, but as result it prints out only of the last fish inserted. Any suggestion?
How can I collect many Fish() inside Aquarium()? Are there better methods?
It looks like you are confusing the idea of "is a kind of" and "contains". Writing class Aquarium(Fish) suggests that Aquarium is a kind of Fish, which it is not. An Aquarium contains fish. So, the Aquarium should not be derived from Fish.
I think this is more like your intentions:
class Fish:
def __init__(self, name):
self.name = name
def swim(self):
print(self.name, "is swimming!")
class Aquarium: # An aquarium is not a kind of fish, rather it contains fish
def __init__(self, **kwargs):
self.fishes = [] # list of all fishes in the aquarium
fishes = kwargs["fishes"]
for fish_name in fishes:
new_fish = Fish(fish_name)
self.fishes.append(new_fish) # add to your list
def how_many(self):
print("In aquarium there are " + str(len(self.fishes)) + " fishes")
def all_swimming(self):
print("The list of all fishes in the aquarium:")
for fish in self.fishes:
print(" " + fish.name)
a = Aquarium(fishes=["Nemo", "Dory"])
print(a.how_many())
a.all_swimming()
Yes, it is possible. But I think this is a better way.
class Fish:
def __init__(self, name:str):
self.name = name
def swim(self):
print(self.name,"is swimming!")
class Aquarium():
def __init__(self, fishes:Fish):
self.fishes = []
for fish in fishes:
self.fishes.append(fish)
def how_many(self):
print("In acquarium there are",len(self.fishes),"fishes")
def all_swimming(self):
for fish in self.fishes:
fish.swim()
Here is a list of suggestions that you may correct:
An aquarium is not a fish. Do not inherit from it! If you need an aspect of the Fish class then split that class and make an composite.
A dictionary is used to store a key and a value. But fish already knows that key. So why don't you use a list? Do you need the dictionary? If not use a list, it is easier to use (this is just my personal opinion).
You used **kwargs. While this is usable nobody can clearly understand what exactly you want these parameters to be. Usually it is better to use a clearly defined set of parameters.
Use typing. At least for the parameters. This is really helpful to understand your code better. Also you IDE might become more helpful if you do.

Messing around with OOP in Python

I'm playing around with OOP in Python and I am trying to figure out some stuff related to inheritance. . I have some code here that has a few classes. A class called Blacksmith which behaves as expected and a class called Hero which I am trying to call a function from but I recieve an unexpected output.
class Character(object):
def __init__(self,name):
self.health=100
self.name = name
# self.player = player
def printName(self):
print self.name
#def printPlayerName(self):
# print self.player
class Blacksmith(Character):
def __init__(self,name, forgeName):
super(Blacksmith, self).__init__(name)
#self.name = "Billy"
self.forge = Forge(forgeName)
class Hero(Character):
playerName = "Player"
def __init__(self,name):
super(Hero, self).__init__(name)
def setplayername(self,inputplayername):
playerName = inputplayername
class Forge:
def __init__(self,forgeName):
self.name = forgeName
bs = Blacksmith("Billy", "Billy's Forge")
print bs.health
bs.printName()
print bs.forge.name
player1 = Hero("Methos")
print player1.name
player1.setplayername("Chris")
#print playher1.playerName
print player1.playerName
Output is:
raina#DESKTOP-291MTC0 ~/python
$ python learningoopclasses01.py
100
Billy
Billy's Forge
Methos
Player
Can anyone explain why this output says "Player" and not "Chris". Another question I have is I am not entirely sure how the init methods work. What does super do in these cases? What does calling init with a name value do exactly? Thanks.
__init__ is called when an object of that Class is created. With this method, we will also use the self variable to represent the instance of the object itself. It has to be explicitly declared in Python to be defined on an object.
For example,
class Student():
def __init__(self, score1, score2, score3):
self.scores = [score1, score2, score3]
If you want to assign scores to Student 1, you would only need to use because stu_1 already has score as an attribute in it:
stu_1 = Student(80, 90, 85)
In addition, __init__ also will notify you if any parameters are entered incorrectly according to what it has been set up.
super() is used to first call the parent(super) class of Blacksmith, which is Character, and allows you access Character's property.
The call to super() in Blacksmith's __init__ method is equal to its superclass, which in this case is Character.
You could also replace super(Blacksmith, self).__init__(name) with Character.__init__(self, name). The two are equivalent.
A slight adjustment to
def setplayername(self,inputplayername):
self.playerName = inputplayername
In the Hero class will fix it, if you don't want to change anything else.

Learn Python, exercise 6.7

I'm reading Lutz & Ascher - Learn Python and I found this as a solution to one of the exercises:
class Lunch:
def __init__(self):
self.cust = Customer()
self.empl = Employee()
def order(self, foodName):
# start a Customer order simulation
self.cust.placeOrder(foodName, self.empl)
def result(self):
# ask the Customer what kind of Food it has
self.cust.printFood()
class Customer:
def __init__(self):
# initialize my food to None
self.food = None
def placeOrder(self, foodName, employee):
# place order with an Employee
self.food = employee.takeOrder(foodName)
def printFood(self):
# print the name of my food
print self.food.name
class Employee:
def takeOrder(self, foodName):
# return a Food, with requested name
return Food(foodName)
class Food:
def __init__(self, name):
# store food name
self.name = name
if __name__ == '__main__':
x = Lunch()
x.order('burritos')
x.result()
x.order('pizza')
x.result()`
What I don't understand is how the definition of the method placeOrder inside the customer class works, more specifically, there is no class employee (just Employee) whose method placeOrder could be used.
def placeOrder(self, foodName, employee):
# place order with an Employee
self.food = employee.takeOrder(foodName)
you may need to read a little bit about object oriented programming, and dynamic typing to grasp this. So basically, employee is an argument which will be passed at runtime, its type will be determined after the call to placeOrder. if you call PlaceOrder and put an instance of Employee or any class that has method takeOrder(), it will work. Imho, you should try to code an example from the beginning and test out what you learn, it will help you learn Python faster
`

python __getattr__ help

Reading a Book, i came across this code...
# module person.py
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay *(1 + percent))
def __str__(self):
return "[Person: %s, %s]" % (self.name,self.pay)
class Manager():
def __init__(self, name, pay):
self.person = Person(name, "mgr", pay)
def giveRaise(self, percent, bonus=.10):
self.person.giveRaise(percent + bonus)
def __getattr__(self, attr):
return getattr(self.person, attr)
def __str__(self):
return str(self.person)
It does what I want it to do, but i do not understand the __getattr__ function in the Manager class. I know that it Delegates all other attributes from Person class. but I do not understand the way it works. for example why from Person class? as I do not explicitly tell it to. person(module is different than Person(class)
Any help is highly appreciated :)
In your __init__ you instantiate a Person object which gets assigned to self.person.
You then override attribute lookups on the Manager instance (by implementing __getattr__ for this class) and redirect these attributes to be looked up on the self.person variable instead (which is the Person object from 1 in this particular case).
Like Felix Kling mentioned in the comments, it would make more sense to make Manager inherit from Person. In the current code above, it looks like the manager has a person while it's more logical to think that the manager is a person.
You could do something like this:
class Person(object):
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def give_raise(self, percent):
self.pay = int(self.pay *(1 + percent))
def __str__(self):
return "[Person: %s, %s]" % (self.name, self.pay)
class Manager(Person):
def __init__(self, name, pay):
super(Manager, self).__init__(name, "mgr", pay)
def give_raise(self, percent, bonus=.10):
self.pay = int(self.pay * (1 + (percent + bonus)))
# example usage
John = Person("John", "programmer", 1000)
Dave = Manager("Dave", 2000)
print John, Dave
John.give_raise(.20)
Dave.give_raise(.20)
print John, Dave
Actually, you do tell it explicitly - not by naming the class, but by providing an instance of that class.
In the init method, you bind self.person to an instance of Person. Now, every Manager instance will have this data member.
In __getattr__, you are delegating to the getattr builtin with self.person as the first argument. Regardless of the type of self.person, it will look for a member with the given name.
Beside reading a Book, you might want to consult The Book where you could have found a pretty clear explanation of how __getattr__() methods work.
In a nutshell, it gets called when there are no attributes of the specified name attached to the object it's being applied to, and also not to the object class or any of it's superclasses. In other words, it called when all else fails.
In the code in your example, the implementation of __getattr_() effectively redirects the search for the named attribute onto the self.person object, which is an instance of the Person class.
It also important to understand that __getattr_() is the first step in accessing both the data and the methods associated with any object.

Categories

Resources