super and base class should have different instances of same attribute name - python

I'm new to python, I'm trying to learn basic inheritance and I want the super and base class to have attributes with same name, but they should have different instances. Below is the code I've written to experiment with this Idea.
class master(object):
def __init__(self, name):
self.name = name
def print_name(self):
print (self.name)
class slave(master):
def __init__(self, master_name, slave_name):
self.name = slave_name
super(slave, self).__init__(master_name)
def print_name(self):
super(slave, self).print_name()
print (self.name)
def main():
obj = slave('hello', 'world')
obj.print_name()
if __name__ == '__main__':
main()
The out put I'm getting is :
hello
hello
But I want want the output to be
world
hello

Super class and base class are different names for the same thing, in this case the master class. That is the base class for slave as well as its super class.
You could have an attribute with the same name on the classes, but not on the instance obj, which is an instance of master and slave at the same time. So self in both methods master.print_name() and slave.print_name() refers to the very same object.
An attribute on an object can only be assigned one value at a given time, so you need different names. But you can get the appearance of the same name with the name mangling that is done behind the scenes for attributes with two leading underscores:
class Master(object):
def __init__(self, name):
self.__name = name
def print_name(self):
print self.__name
class Slave(Master):
def __init__(self, master_name, slave_name):
self.__name = slave_name
Master.__init__(self, master_name)
def print_name(self):
Master.print_name(self)
print self.__name
def main():
obj = Slave('hello', 'world')
obj.print_name()
# The *actual* names of the attributes:
print obj._Master__name, obj._Slave__name
if __name__ == '__main__':
main()

The __init__ from master is overriding the value of self.name that you set in slave. You need to use different variable names.

Related

Can someone clarify inheriting private attributes in Python?

[This is in Python 3.6.8] So my impression was that a subclass inheriting from a superclass would be able to access the superclass' attributes freely, even if the attribute was "private". For example:
class A():
def __init__(self, name):
self.__name = name
def __str__(self):
return f'my name is {self.__name}'
class B(A):
def __init__(self, name, number):
A.__init__(self, name)
self.__number = number
def __str__(self):
return f'my name is {self.__name}\nmy number is {self.__number}'
def main():
name = input('name: ')
number = input('number: ')
obj = B(name, number)
print(obj)
main()
Apparently, when I try to use self.__name in the __str__ function of the subclass B, it throws an AttributeError exception, stating: AttributeError: 'B' object has no attribute '_B__name'. However, the program works as intended when I use a accessor (getter) method for getting the name instead of self.__name. So was my initial impression wrong, or am I making a misunderstanding somewhere?

Change a inherited class to another inherited class keeping the attributes

I need to change a inherited class to another inherited class where only one of the attributes has changed
i need to "Promote" a Cashier to a Manager, the only thing that is suppose to change is the salary
both Cashier and Manager are inherited classes of Employee (where I'm not sure if I'm using the "hasattr" function the right way)
class Employee:
def __init__(self,name):
self.name=name
if(hasattr(self,'shifts')==False):
self.shifts=[]
class Manager(Employee):
def __init__(self,name,salary):
Employee.__init__(self,name)
self.salary=salary
class Cashier(Employee):
def __init__(self,name,salarey_per_hours):
Employee.__init__(self,name)
self.salery_per_hours=salarey_per_hours
def promote(self,salary):
return Manager(self.name,salary)
P.s It's my first time uploading a question
What you could do is create the addition method of your class and add self to the manager class you are returning like so:
class Employee(object):
def __init__(self, name):
self.name=name
if not hasattr(self, 'shifts'):
self.shifts = []
def __add__(self, other):
if isinstance(other, Employee):
for key, value in other.__dict__.items():
if key == 'salary':
continue
self.__setattr__(key, value)
return self
class Manager(Employee):
def __init__(self, name, salary):
super().__init__(name)
self.salary = salary
class Cashier(Employee):
def __init__(self,name,salary):
super().__init__(name)
self.salary = salary
def promote(self, salary):
manager = Manager(self.name, salary)
manager += self
return manager
cashier = Cashier('hank', 22)
cashier.shifts = [1, 2, 3, 4]
print(cashier.shifts)
promoted_cashier = cashier.promote(30)
print(promoted_cashier.shifts)
Here you make sure that everything except the "salary" is transferred to the promoted class. And since both the Manager and the Cashier are an Employee this should work nicely. I changed your code a bit to what I'm used to since there was some unusual coding with you Calling Employee in the init which I assumed you did not explicitly needed. Sorry if that was not the case.
You can change the object's class by obj.__class__ to the another class by
doing obj.__class__ = SomeClass
Beware that is can lead to strange behaviours if it is handled incorrectly.
by modifying your code
class Employee:
def __init__(self,name):
self.name=name
if(hasattr(self,'shifts')==False):
self.shifts=[]
class Manager(Employee):
def __init__(self,name,salary):
Employee.__init__(self,name)
self.salary=salary
class Cashier(Employee):
def __init__(self,name,salarey_per_hours):
Employee.__init__(self,name)
self.salery_per_hours=salarey_per_hours
def promote(self,salary):
self.__class__ = Manager
# return Manager(self.name,salary)
You can also take a look at this post changing the class of a python object (casting)

Accessing a method of a parent class in the sublass

i am new to OOP.
i have a parent class with a method that i wanna access in my subclass. But i cannot figure out the right syntax for that. I cannot find a clear cut example anywhere
Members in the base class are simply available to the subclass as well (unless they are overwritten):
class Base:
def example (self):
print('This is in the base class')
class Subclass (Base):
def test (self):
self.example()
An object of type Subclass can now access example directly or indirectly:
>>> x = Subclass()
>>> x.test()
This is in the base class
>>> x.example()
This is in the base class
class Parent(object):
def __init__(self, name):
self.name = name
def output(self):
print self.name
class Child(Parent):
def __init__(self, name, age):
Parent.__init__(self, name)
self.age = age
def output(self):
super(Child, self).output()
print self.age
if __name__ == '__main__':
a = Parent("wy")
b = Child("zhang", 10)
a.output()
b.output()
You can try this code.

Not able to understand how iterator works on class

from __future__ import print_function
class Employee:
def __init__(self, name, salary=0):
self.name = name
self.salary = salary
def giveRaise(self, percent):
self.salary = self.salary + (self.salary * percent)
def work(self):
print(self.name, "does stuff")
def __repr__(self):
return "<Employee: name=%s, salary=%s>" % (self.name, self.salary)
class Chef(Employee):
def __init__(self, name) :
Employee.__init__(self, name, 50000)
def work(self) :
print(self.name, "makes food")
class Server(Employee):
def __init__(self, name):
Employee.__init__(self, name, 40000)
def work(self):
print(self.name, "interfaces with customer")
class PizzaRobot(Chef):
def __init__(self, name):
Chef.__init__(self, name)
def work(self):
print(self.name, "makes pizza")
if __name__ == "__main__":
for klass in Employee, Chef, Server, PizzaRobot:
obj = klass(klass.__name__)
obj.work()
In above code I am not able to understand how last 3 lines behaves whether klass would be a class or it would be an instance of class.
if its an class then what below line means and why it is required?
obj = klass(klass.__name__)
As mentioned there's no iterator here, but a tuple of classes. The loop below will loop over each class and instantiate a new object for each of the classes.
for klass in Employee, Chef, Server, PizzaRobot:
obj = klass(klass.__name__)
obj.work()
If you look at your class definitions and specifically the __init__-function for each class, you'll see that there's a positional argument name:
class Chef(Employee):
def __init__(self, name): # <-- look here.
Which means that each of your classes must be instantiated with a parameter name:
>>> john = Chef('John')
>>> print(john)
<Employee: name=John, salary=50000>
Each class has a __name__ attribute, which is the defined name for that class. For example Chef.__name__ is Chef:
>>> Chef.__name__
'Chef'
This is different from the name parameter you've defined for your classes, as this is an internal attribute and should not be changed.
Thus the line obj = klass(klass.__name__) will create an object for each the classes you're looping over and give the class __name__ attribute as the positional argument name for each object.
You will end up with four objects of types Employee, Chef, Server and PizzaRobot. Instead of human names like John as per the example I've given, you're just naming them after the class.
>>> for klass in Employee, Chef, Server, PizzaRobot:
... print(klass.__name__)
...
Employee
Chef
Server
PizzaRobot
>>>

Python: how to print instance variable of type string

I am trying to print a string variable returned by name() function, which in this case should print "Jim, but Python is printing
`<bound method Human.name of <__main__.Human object at 0x7f9a18e2aed0>>`
Below is the code.
class Human:
def __init__(self):
name = None
def setName(self, _name):
name = _name
def name(self):
return self.name
jim = Human()
jim.setName("Jim")
print(jim.name())
UPDATE:
After reading the answers, i updated the code as shown below, but, now i am getting a new error TypeError: 'str' object is not callable
class Human:
def __init__(self):
self.name = None
def setName(self, _name):
self.name = _name
def name(self):
return self.name
jim = Human()
jim.setName("Jim")
print(jim.name())
self.name is the method itself. You have no attributes storing the name. Nowhere do you actually set the name as an attribute. The following works:
class Human:
def __init__(self):
self.name = None
def setName(self, _name):
self.name = _name
# NOTE: There is no more name method here!
Now you have an actual attribute, and you don't need to call the method here:
jim = Human()
jim.setName("Jim")
print(jim.name) # directly using the attribute
You could even just set the attribute directly:
jim = Human()
jim.name = "Jim"
print(jim.name)
Alternatively, use self._name to store the name on the instance:
class Human:
_name = None
def setName(self, _name):
self._name = _name
def name(self):
return self._name
Here we used a class attribute Human._name as a default, and only set self._name on the instance in the Human.setName() method.
The problem is that name is the name of the internal variable in your object and also the name of the method.
The namespace for variables and methods is the same. Change the name of your method to something other than name. This will fix your getter. On first glance I thought that that would be all you have to do, but the recommendation in Martijn's answer also applies -- you need to assign to self.name and not just name in order to get your setter to work as well.
As an aside, this getter/setter pattern is not usually appropriate for Python. You should ask yourself why you want to use a getter/setter pattern over simply accessing the object's variable directly. See the section on getters and setters in this article for more detail.
You can use setter and getter properties instead of your custom defined methods.
class Human():
def __init__(self):
self._name = None
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
And then, use them:
jim = Human()
jim.name = "Jim"
print(jim.name)

Categories