Python Inheritence from constructor - python

person.py
class Person:
"""---A class representing a person---"""
# Person constructor
def __init__(self,n,a):
self.full_name = n
self.age = a
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
Person.__init__(self,n,a)
self.school = s
driver.py
from person import *
a = Student("Alice", 19, "Univ")
It throws TypeError: __init__() takes 3 positional arguments but 4 were given
I tried to change Student class to the following:
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
super().__init__(n,a)
self.school = s
The error still exists.
Why does this happen? Is super() keyword required to add new attributes?
EDIT: The problem is solved. There was an indentation issue in the source code rendering this strange behavior, hence the question should be closed.

This line:
Person.__init__(self,n,a)
Is the problem. Recall that methods are automatically passed a reference to themselves, so you just passed a second one.
There's also a well-established pattern for this:
class Person
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, school, *args):
super().__init__(*args)
self.school = school
student = Student('Washington Elementary', "Johnny Go'gettem", 10)
although note that simply removing your reference to self in the Person.__init__ call inside Student.__init__ would be sufficient.
Note that you can override the default method behavior with a couple of decorators that become quite useful in certain situations. Neither apply here, but just a bit of knowledge to tease your brain a bit:
def SomeClass:
attr = "class-scoped"
def __init__(self):
self.attr = "instance-scoped"
def some_method(self):
return self.attr == "instance-scoped"
#classmethod
def some_classmethod(cls):
return cls.attr == "class-scoped"
#staticmethod
def some_staticmethod():
return "I'm not given a \"self\" parameter at all!"
classmethods are particularly useful as alternate constructors
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
#classmethod
def from_tuple(cls, tup) -> "Person":
"""Expects a tuple of (name, age) and constructs a Person"""
name, age = tup
return cls(name, age)
#classmethod
def from_dict(cls, dct) -> "Person":
"""Expects a dictionary with keys "name" and "age" and constructs a Person"""
try:
name = dct['name']
age = dct['age']
except KeyError:
raise ValueError(f"Dictionary {dct} does not have required keys 'name' and 'age'")
else:
return cls(name, age)

Related

composition over inheritance, even is it a Is-a relationship

I try to use composition even the relationship is: is-a.
So I have a Animal class and I have a Zebra class:
class Name:
pass
class Age:
pass
class Zebra():
pass
class Animal:
def __init__(self, name_animal, age_animal) -> None:
self.name_animal = name_animal
self.age_animal = age_animal
self.name = Name()
self.age = Age()
self.zebra = Zebra()
def __repr__(self):
return "My name is {} and I am {} years old".format((self.name_animal), (self.age_animal))
zebra1 = Zebra('Zebra', 37)
print(zebra1)
but then of course it fails because Zebra has no arguments.
So is it possible to use the repr method also for Zebra without inheritcance but with compostion?
Because I get now this error:
TypeError: Zebra() takes no arguments
I don't recommend to use composition in this case. This is a use-case for inheritance. But academic questions also deserve an answer.
Add a constructor to Zebra that initializes and stores an Animal instance and delegate __repr__:
class Animal:
def __init__(self, name_animal, age_animal) -> None:
self.name_animal = name_animal
self.age_animal = age_animal
def __repr__(self):
return "My name is {} and I am {} years old".format((self.name_animal), (self.age_animal))
class Zebra():
def __init__(self, name_animal, age_animal) -> None:
self.animal = Animal(name_animal, age_animal)
def __repr__(self):
return self.animal.__repr__()
zebra1 = Zebra('Zebra', 37)
print(zebra1)

self not defined when trying to access

I have the following code:
class Person:
def __init__(self,name):
self.name = name
self.balance = 0
def setBalance(self, value):
self.balance = vale
def setName(self, name):
self.name = name
class Main:
def __init__(self):
self.people = []
def addPerson(self,name):
self.people.append(Person(name))
def updateBalance(self,balance,index):
self.people[index].setBalance(50)
print self.people[0]
main = Main()
main.addPerson("Jack")
main.updateBalance(30,0)
I made the following code just to see how objects works with array. But, when I try to run it I get NameError: name 'self' is not defined. I'm not sure what I'm doing wrong ?
If something is not clear or needs editing then please let me know in the comments before down voting.
Many thanks
There a several issues with your code:
Class methods, need to refer to the class def ...(self, ...)
print(...) is a function in Python3 and has to be called from within a method.
The following adjustments make your code work:
class Person:
def __init__(self, name):
self.name = name
self.balance = 0
def setBalance(self, value):
self.balance = value
def setName(self, name):
self.name = name
class Main:
def __init__(self):
self.people = []
def addPerson(self, name):
self.people.append(Person(name))
def updateBalance(self, balance, index):
self.people[index].setBalance(50)
print("Updating people at index %d" % index)
main = Main()
main.addPerson("Jack")
main.updateBalance(30, 0)
print (main.people[0])
Prints:
Updating people at index 0
<__main__.Person instance at 0x100d065f0>
You should pass self as a parameter as well:
class Main:
def __init__(self):
self.people = []
def addPerson(self, name):
self.people.append(Person(name))
def updateBalance(self, balance,index):
self.people[index].setBalance(50)
print people[0] #no need for self if you are calling local variable of class but this will print an empty array
Also you have type error
class Person:
def __init__(self,name):
self.name = name
self.balance = 0
def setBalance(self, value):
self.balance = vale --> not vale but value
def setName(self, name):
self.name = name
Whereas languages like Java or C++ make the "self"/"this" argument implicit in their method definitions, Python makes it explicit. So, the method definitions in your Main class should look like this:
class Main:
def __init__(self):
self.people = []
def addPerson(self, name):
self.people.append(Person(name))
def updateBalance(self, balance, index):
self.people[index].setBalance(50)
In effect, what you had before was an addPerson() method that accepted zero arguments from the caller... and it had renamed self to name, quite unintuitively. Calling the first parameter to a method "self" in Python is only by convention... you could just as easily define addPerson() like this:
class Main:
def addPerson(something_other_t_self, name):
something_other_than_self.people.append(Person(name))
...and it would work exactly the same.

Python class objectivity convention

Is it a good style to create classes like that ? I read the PEP8 document but I didn't saw any good example. If not how is it a proper way ? Thanks for any answers.
class Zone:
def __init__(self, index=None, name=None):
self._index = index
self._name = name
#property
def index(self):
return self._index
#property
def name(self):
return self._name
#index.setter
def index(self, index):
self._index = index
#name.setter
def name(self, name):
self._name = name
Your setters and getters don't do anything. With your implementation, the user of this class does this:
z = Zone()
z.name = 'foo'
print(z.name)
Compare to this implementation:
class Zone:
def __init__(self, index=None, name=None):
self.index = index
self.name = name
z = Zone()
z.name = 'foo'
print(z.name)
It works exactly the same with a lot less code.
Unless you do anything in your setters and/or getters, you don't need them.
If what you intend doing is encapsulating your data and setting it with setters and getting it with getters, then what you did will not be helpful. you declared the _name and _index as protected, it does not mean it cannot be accessed by extenal functions, so functions outside the class can easily access and change them, making your getter and setter to be useless.
However, you can declare them as private by using one additional underscore in front, so that your property class will be removed and then the setters class will be useful, it will no longer be accessed by external functions.
class Zone:
def __init__(self,index=None,name=None):
self.__index = index
self.__name = name
def index(self, index):
self.__index = index
def name(self, name):
self.__name = name
def get_name(self):
return self.__name
zone=Zone()
zone.name('ben')
print(zone.get_name())
>>>ben
print(zone.__name)
>>> AttributeError: 'Zone' object has no attribute '__name'

Mutually Reference-able Instances in Python

Say I have a pair of instances that reference one another mutually. Is there a preferable manner to structure this relationship than the following.
class Human():
def __init__(self, name):
self.name = name
self.pet = Dog('Sparky', self)
def pet(self, animal):
self.pet.receive_petting()
class Dog(Pet):
def __init__(self, name, owner):
self.name = name
self.owner = owner
def receive_petting(self):
pass
def bark_at(self, person):
"do something"
The thing I don't like is that the relationship needs to be specified in two places. Any ideas on how to make this dryer?
I would break this into three classes:
class Human():
def __init__(self, name):
self.name = name
class Dog(Pet):
def __init__(self, name):
self.name = name
def bark_at(self, person):
"do something"
class OwnerPetRelation():
def __init__(self, dog, human):
self.owner=human
self.pet=dog
Now, one owner can also have many dogs, we just need to define as many OwnerPetRelations.
Similarly, a dog can also belong to multiple owners now.
I would create a method on Human that allows you to add pets (since a human might have many pets):
class Human():
def __init__(self, name):
self.name = name
self.pets = []
def add_pet(self, pet):
pet.owner = self
self.pets.append(pet)
def pet(self, animal):
for pet in self.pets:
pet.receive_petting()
class Dog(Pet):
def __init__(self, name):
self.name = name
self.owner = None
def receive_petting(self):
pass
def bark_at(self, person):
"do something"
This can be used as follows
human = Human('Jim')
human.add_pet(Dog('Woof'))
This approach can of course also be used for just a single pet and one could also extend it to allow pets to be owned by many humans.
There's nothing really Python-specific here; this is just a limitation of constructor-based dependency injection. It's hard to inject a reference to another object that cannot have been created yet. Instead, you can create an object that has a reference to something that will have a reference to the other object. For instance, you can pass a function to the constructor that will be able to return the value:
class Human():
def __init__(self,name,dog):
self.name = name
self._dog = dog
#property
def dog(self):
return self._dog()
class Dog():
def __init__(self,name,human):
self.name = name
self._human = human
#property
def human(self):
return self._human()
Then you can use it like this:
human = None
dog = Dog('fido',lambda: human)
human = Human('john',lambda: dog)
print(dog.human.name)
print(human.dog.name)
john
fido
It is not hard to update this so that the property function caches the value, of course. E.g.:
class Dog():
def __init__(self,name,human):
self.name = name
self._human = human
#property
def human(self):
try:
return self._human_
except AttributeError:
self._human_ = self._human()
return self._human_

how a class works

I am trying to figure out a really simple problem but still I can't quite get how a class works. For example, in case I wanted to create a class called "Friend" with an attribute called "name", does this mean I will have to give a variable called "name"before anything else ? Then how can i define the constructor to allow the specification of "name"? Is this code nonsense? Thanks in advance for any response
class Friend:
def __init__(self,name):
self.set_name(name)
def set_name(self,name):
self.name=name
def get_name(self):
return self.name
That code is not nonsense as in it accomplishes what you want to accomplish. It is not very pythonic, though. There are no reason you should use getter or setters. Just access the attributes directly. Like
class Friend:
def __init__(self,name):
self.name = name
you can instantiate your class by
friend = Friend('jeremy')
now just access name directly
print friend.name # jeremy
There is a good amount to learn about python classes luckily python provides excellent documentation for whatever version you are on.
in this example, to create a new friend you need to instantiate it with a name.
What you are referring to is default keyword arguments. The way you have specified it in your example means that name is required in the constructor. The way to make it default (and be able to be set after the constructor) would look like this:
class Friend(object):
def __init__(self,name=''):
self.name = name
def set_name(self,name):
self.name=name
def get_name(self):
return self.name
Now your class can be instantiated without a name:
aFriend = Friend()
As suggested in comments, it is not "considered pythonic" to have setters and getters for a basic attribute. But, if that attribute requires computation, then you can make it a property:
class Friend(object):
def __init__(self, firstname='', lastname=''):
self.firstname = firstname
self.lastname = lastname
#property
def firstname(self):
return self._first
#firstname.setter
def firstname(self, n):
self._first = n.capitalize()
#property
def lastname(self):
return self._last
#lastname.setter
def lastname(self, n):
self._last = n.capitalize()
#property
def fullname(self):
return "{0} {1}".format(self.firstname, self.lastname)
f = Friend('frank')
f.lastname = 'smith'
f.firstname
# 'Frank'
f.lastname
#'Smith'
f.fullname
#'Frank Smith'

Categories

Resources