Arguments to __init__ when using a subclass and super - python

I'm spending a nice Saturday looking a bit deeper into Python objects, and had a simple question that isn't mindblowing but kinda curious.
Say I have a base class and a subclass as follows:
class Person:
def __init__(self, first, last):
self.firstname = first
self.lastname = last
def __str__(self):
return self.firstname + " " + self.lastname
class Employee(Person):
def __init__(self, first, last, staffnum):
super().__init__(first, last)
self.staffnumber = staffnum
x = Person("Marge", "Simpson")
y = Employee("Homer", "Simpson", "1007")
I'm looking at the Employee class.
Given:
we are using super() and it has arguments in it,
why in python does the __init__still require us to type first, last?
Shouldn't this be inferred from our use of super? Seems like extra repetitive typing. What's the reasoning for the author having to do this?

The reason is so you can further customize subclasses. In your example, when you still want to input first and last it is more of a hassle because you have to type it twice, like this:
class Employee(Person):
def __init__(self, first, last):
super().__init__(first, last)
emp1 = Employee("Bob", "Jones")
However, you might want to autofill some of those values. In this example maybe a FamilyMember class where the last name is common.
class FamilyMember(Person):
def __init__(self, first):
super().__init__(first, last="Erikson")
fm1 = FamilyMember("Paul")
In this case you would only need to input the first variable for a FamilyMember and the last variable would be automatically filled in.

Related

How can I pass **kwargs in __init__() constructor in python?

I'm trying to write a program where I am trying to pass **kwargs in init() method. After that
when I m trying to make a instance variable inside the constructor(init() method ) , I cant able to make . How can I do this ?
Here is my code :
class Student:
def __init__(self,**kwargs):
self.name = name
self.age = age
self.salary = salary
def show_name(self):
print("Name is : " + self.name)
def show_age(self):
print("Age is : " + str(self.age))
def show_salary(self):
print(f"Salary of {self.name} is : " + str(self.salary))
st = Student('John',25,15000)
st2 = Student('Doe',25,1500000)
st.show_salary()
st2.show_salary()
**kwargs expects arguments to be passed by keyword, not by position. Once you do that, you can access the individual kwargs like you would in any other dictionary:
class Student:
def __init__(self, **kwargs):
self.name = kwargs.get('name')
self.age = kwargs.get('age')
self.salary = kwargs.get('salary')
def show_name(self):
print("Name is : " + self.name)
def show_age(self):
print("Age is : " + str(self.age))
def show_salary(self):
print(f"Salary of {self.name} is : " + str(self.salary))
st = Student(name='John', age=25, salary=15000)
st2 = Student(name='Doe', age=25, salary=1500000)
st.show_salary()
st2.show_salary()
If you want to pass these arguments by position, you should use *args instead.
kwargs is created as a dictionary inside the scope of the function. You need to pass a keyword which uses them as keys in the dictionary. (Try running the print statement below)
class Student:
def __init__(self, **kwargs):
#print(kwargs)
self.name = kwargs["name"]
self.age = kwargs["age"]
self.salary = kwargs["salary"]
def show_name(self):
print("Name is : " + self.name)
def show_age(self):
print("Age is : " + str(self.age))
def show_salary(self):
print(f"Salary of {self.name} is : " + str(self.salary))
st = Student(name = 'John',age = 25, salary = 15000)
st2 = Student(name = 'Doe',age = 25,salary = 1500000)
st.show_salary()
st2.show_salary()
Though you can do this as some of the answers here have shown, this is not really a great idea (at least not for the code you are showing here). So I am not going to answer the subject line question you have asked, but show you what the code you seem to be trying to write should be doing (and that is not using kwargs). There are plenty of places where using kwargs is the best solution to a coding problem, but the constructor of a class is usually not one of those. This is attempting to be teaching, not preaching. I just do not want others coming along later, seeing this question and thinking this is a good idea for a constructor.
The constructor for your class, the __init__(), generally should be defining the parameters that it needs and expects to set up the class. It is unlikely that you really want it to take an arbitrary dictionary to use as its parameter list. It would be relatively rare that this is actually what you want in your constructor, especially when there is no inheritance involved that might suggest you do not know what the parameters are for some reason.
In your __init__() itself you clearly want the parameters name, age and salary, yet without them in the parameter list it is not clear to the caller that you do. Also, your usage of it does not seem to imply that is how you expect to use it. You call it like this:
st = Student('John',25,15000)
and so you do not even seem to want named parameters.
To handle the call structure you have shown the __init__() would look like this:
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
If you want to be be able to call it without some parameters such that it uses defaults for the ones left out, then it should be like this:
def __init__(self, name=None, age=None, salary=None):
self.name = name
self.age = age
self.salary = salary
It seems very unlikely that the kwargs approach is really what you want here, though obviously you can code it that way as other answers have shown.
Perhaps you are just trying to figure out how to use kwargs, and that is fine, but a different example would be better if that is the case.

Python3 Class method inputs; clean solution

I'am using more class based programs, however in some cases it's not handy to provide all self.paramets into a class.
In those cases I want to use a regular input into a function in a class. I figured out a way to achieve both inputs, let me show this in following script:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def myfunc(a):
if (type(a) == str):
name = a
else:
name = a.name
print("Hello my name is " + name)
p1 = Person("John", 36)
p1.myfunc()
print("---------------------")
Person.myfunc("Harry")
Output:
Hello my name is John
---------------------
Hello my name is Harry
First, the name is initialized by the classes self.params.
Second, the name is provided in the method within the class as a string.
So a type check is necessary.
However I don't think this is a clean approach, because when I have >30 methods I need to implement these type checks again, including upcoming type-error results.
Does anyone know a better approach?
The simplest solution is to implement a __str__ method for your class. This method will be called whenever something tries to convert an instance of the class to a string.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name
p = Person('Jane', 25)
print('Hello', p)
'Hello Jane'

"Cannot create a consistent method resolution order" inheriting from another child class

I am revising OOP in Python and tried inheriting attributes from another child class but I couldn't figure out how or if it is possible. Here is what I have so far:
class Employee:
def __init__(self, first, last, pay):
self.first = first
self.last = last
self.pay = pay
def increase_pay(self, multiplier):
self.pay = int(self.pay * multiplier)
class Developer(Employee):
def __init__(self, first, last, pay, prog_lang):
Employee.__init__(self, first, last, pay)
self.prog_lang = prog_lang
self.email = first.lower() + '.' + last.lower() + '#outlook.com'
class BetaTester(Employee, Developer):
def __init__(self, first, last, pay, prog_lang, platform):
self.platform = platform
The error I receive is:
Traceback (most recent call last):
File "main.py", line 33, in <module>
class BetaTester(Employee, Developer):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Employee, Developer
The method resolution order (MRO) is defined by the C3 linearization algorithm, which sounds complicated but it really boils down to this: the class, its parents, their parents, etc need to be placed in a list subject to two conditions:
Each class appears before its parent(s)
If a class inherits from more than one class, its parents appear in the same
order as they do in class statement. That is, given class A(B, C, D), the MRO for A will have B before C, which will be before D. (A, of course, appears before all 3)
You should be able to see the problem: by this algorithm, the MRO for BetaTester has to include Developer before Employer according to the first rule, but Employer has to come before Developer according to the second rule. In this case, you can simply swap the two to fix the problem, but there's never any reason to inherit from a class A and another class the inherits from A. Just drop A altogether.
# Developer is already a descendent of Employee, so BetaTester will be, too
class BetaTester(Developer):
...
To make sure each class's __init__ method is called, use super to make sure each __init__ calls the next one in the chain. The most important rule here is to make sure that if a class adds arguments to __init__, it has to make sure not to pass them on to the next __init__. At the same time, it has to accept arbitrary keywords arguments and be sure to pass them on. Keyword arguments make it simpler to focus on the arguments you need to deal with, and just pass on the ones you don't.
class Employee:
def __init__(self, first, last, pay, **kwargs):
super().__init__(**kwargs)
self.first = first
self.last = last
self.pay = pay
def increase_pay(self, multiplier):
self.pay = int(self.pay * multiplier)
class Developer(Employee):
def __init__(self, prog_lang, **kwargs):
super().__init__(**kwargs)
self.prog_lang = prog_lang
self.email = "{}.{}#outlook.com".format(self.first.lower(), self.last.lower())
class BetaTester(Developer):
def __init__(self, platform, **kwargs):
super().__init__(**kwargs)
self.platform = platform
b = BetaTester(first="Bob", last="Jones", pay=90000, prog_lang="Python", platform="Unix")
#MehrdadEP has answered very well
I think i should make it easy
suppose there are 2 classes A and B
B inherits from A
Now you are creating a new class C
if you inherit from B you are already inheriting from A. no need to write that in class C(A,B)
observe C(B,A) will not give you error
But C(A,B) will
Another thing is you should use Super().__init__() instead of Employee.__init__()
This may not be handy when doing Hybrid Inheritance due to more than 1 different super classes where you would have to call classname1.__init__() , classname2.__init__() and so on
also make sure if you want the attributes defined in SuperClass then call the classname.__init__() so that they are defined in new class's scope otherwise
you will get an error
for example
print(b1_1.first) will give an error
to resolve that use Developer.__init__(self, first, last, pay, prog_lang)
and don't forget the self in this Developer.__init__ calling from subclass __init__
use super() in __init__ method of sub classes
Your BetaTester is inheriting from Employee and Developer. Because Developer already inherits from Employee Python now cannot determine what class to look methods up on first. You don't need to name all base classes of Developer here; just inherit from that one class.
here is your fixed code:
class Employee:
def __init__(self, first, last, pay):
self.first = first
self.last = last
self.pay = pay
def increase_pay(self, multiplier):
self.pay = int(self.pay * multiplier)
emp_1 = Employee('David', 'Jackson', 35000)
print (emp_1.pay)
emp_1.increase_pay(1.2)
print (emp_1.pay)
class Developer(Employee):
def __init__(self, first, last, pay, prog_lang):
super().__init__(first, last, pay)
self.prog_lang = prog_lang
self.email = first.lower() + '.' + last.lower() + '#outlook.com'
dev_1 = Developer('James', 'McCarthy', 70000, 'C++',)
print(dev_1.first)
print(dev_1.email)
class BetaTester(Developer):
def __init__(self,first, last, pay, prog_lang, platform):
self.platform = platform
bt_1 = BetaTester('Jonas', 'Andersen', 45000, 'C#', 'Mobile')
print(bt_1.platform)

How to create a method for a class that takes multiple objects of a class as arguments?

I'd like to define a special method within a class that takes two instances of the class as arguments. I'd also like to be able to call this function with method(object_a, object_b) rather than object_a.method(object_b). Let me illustrate with an example:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def inside_class_age_diff(self, other):
return abs(self.age - other.age)
def outside_class_age_diff(person_a, person_b):
return abs(person_a.age - person_b.age)
Romeo = Person("Romeo", 20)
Juliet = Person("Juliet", 18)
print(Romeo.inside_class_age_diff(Juliet))
print(outside_class_age_diff(Romeo, Juliet))
So, in the above example outside_class_age_diff() takes two objects of the class Person as arguments, but it's defined outside of the class, which to me feels wrong because it's clearly a method that belongs to the class Person. On the other hand, even though inside_class_age_diff() is defined inside of the class, it needs to be called using the dot notation with an object of the class, which isn't very neat.
So, how can I get inside_class_age_diff(Romeo, Juliet) to work? Is it possible even?
Seems like you're playing around with design patterns. What you're looking for is a static method.
You'd define it like so:
class Person:
def __init__(self, name, age):
...
def inside_class_age_diff(self, other):
...
#staticmethod
def outside_class_age_diff(person_a, person_b):
return abs(person_a.age - person_b.age)
You can then use it like so:
Person.inside_class_age_diff(Romeo, Juliet)
It's still a method of the class, and thus needs to be called as such.
You can use a static method:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
#staticmethod
def inside_class_age_diff(person_a, person_b):
return abs(person_a.age - person_b.age)
Romeo = Person("Romeo", 20)
Juliet = Person("Juliet", 18)
print(Romeo.inside_class_age_diff(Romeo, Juliet))
A static method acts just like normal function, i.e. it is not bound to an instance. Hence the first argument, is not treated special in any way.

How to pass method into function outside the Class?

I might be missing something stupid, or I am simply trying to walk through a steel wall instead of going around. Basically I have created turtle class and I use it in different script for drawing L-systems. I thought I could create a function outside the turtle class, which will accept:
turtle - the instance of from my turtle class
a string from L-system
dictionary of rules/instructions of how to interpret different symbols in the string above, that is for which symbol call which method from the turtle class
But it all crashes on trying to pass the method to the function - I think it does not see the method, since it is defined inside the class. To simplify the matters I have created a basic example, which fails at the same place:
class Person():
def __init__(self, age):
self.age = age
def birthday(self):
self.age += 1
def foo(person, method):
person.method()
jane = Person(20)
foo(jane, birthday)
#Traceback (most recent call last):
# File "passmethod.py", line 14, in <module>
# foo(jane, birthday)
#NameError: name 'birthday' is not defined
That is:
the only variable in Person class instance is age, the only method birthday raises the age by 1.
jane is Person class instance, initialized at 20 years old
the by function foo I am trying to call birthday method on her
script does not see birthday method and crashes
So, my question(s) is(are):
Is there a way, how to do it? If so, how?
If not or if it would not be advisable to do so, what should I use?
Update
Thanks for those quick and nice answers! The additional question which naturally follows - is any of those ways preferred? I would guess, that
__getattribute__ and getattr are pretty much the same, although for the first one, the inheritance is probably necessary.
I don't see any great difference in this case between using jane.birthday or Person.birthday, although in general it could be useful to be able to call the method for different Person instances, e.g. created in the foo function.
Here the working code:
class Person():
def __init__(self, age):
self.age = age
def birthday(self):
self.age += 1
def foo(person, method):
getattr(person, method)()
Test:
>>>
>>> jane = Person(20)
>>> foo(jane, 'birthday')
>>> jane.age
21
>>>
Well, there are ways to do that.
First way: just pass method bound to a particular object:
def foo(person, method):
method() # calls jane.birthday()
jane = Person(20)
foo(jane, jane.birthday)
Second way: pass a class method and apply it to a particular object:
def foo(person, method):
method(person) # calls Person.birthday(jane), which is the same thing
jane = Person(20)
foo(jane, Person.birthday)
You can use the __getattribute__ method (inherited from object):
class Person(object):
def __init__(self, age):
self.age = age
def birthday(self):
self.age += 1
def foo(person, method):
person.__getattribute__(method)()
jane = Person(20)
foo(jane, "birthday")
As I suggested in the comments, you could sub-class your Turtle to add a rule-following method. To demonstrate a trivial example:
class InstructableTurtle(Turtle):
def follow_instructions(self, instructions):
for instruction in instructions:
if instruction == "MOVE_LEFT":
self.move_left()
...
But you could also have the rules provided as an additional argument to the new instance:
def __init__(self, ..., rules): # '...' represents args to base Turtle
super().__init__(...) # or 'super(InstructableTurtle, self)' on Python 2.x
self.rules = rules
As an example:
>>> class Turtle():
def __init__(self, name):
self.name = name
def move_left(self):
print("{0.name} is moving left...".format(self))
>>> class InstructableTurtle(Turtle):
def __init__(self, name, rules):
super().__init__(name)
self.rules = rules
def follow_instruction(self, instruction):
self.rules[instruction](self)
>>> tommy = InstructableTurtle("Tommy", {"LEFT": Turtle.move_left})
>>> tommy.follow_instruction("LEFT")
Tommy is moving left...

Categories

Resources