Can someone clarify inheriting private attributes in Python? - 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?

Related

#property decorator of python not working

I was trying to set some property to a class via decorator but its not working as expected. How can I get the age via property decorator.
class Person:
def __init__(self):
self.name = ""
self.age = ""
self.dob = ""
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
#property
def age(self):
return self._age
#age.setter
def age(self, value):
self._age = value
#property
def dob(self):
return self._dob
#dob.setter
def dob(self, value):
self._dob = value
self._age = 20 #Utility.getAge(value)
if __name__ == '__main__':
p = Person()
p.name = "Andrew"
p.dob = "10-10-1980"
print p.name
print p.dob
print p.age
Output:
John
10-10-1980
#20 <-missing
I am not getting the age. Am I missing something?
Ok, this took me a while to actually find out why the above code was not working in python 2.7.
If you look at the property documentation for python2.7, you would find that the class that has the property decorators used is actually inheriting object class and your code doesn't.
Now, when you don't inherit, the property decorator actually doesn't work and setting or getting properties don't work either
(Put a print statements in getter or setter functions and they wont be printed since they were never invoked while setting p.name or getting p.name).
Question : So how come get/set for p.name and p.dob works?
Since, you are not inheriting object class in your class, the property decorators are useless, they are not being invoked but have created those property on the Person object.
But, when you use below code, you are explicitly setting those value (without the use of setters), hence thy are printed and p.age never got assigned any value.
p.name = "Andrew"
p.dob = "10-10-1980"
Code Fix : Update your class declaration to -->
class Person(object):
and setters/getters would work (check using print statements) and self.age would also work.
Bonus : Python3 onwards, all classes, by default, inherit object class.

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

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.

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
>>>

subclass constructor using super's constructor

Initially this was defined
class Mammal(object):
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
def say(self):
return("What does the " + self.name + " says")
but now we want to create subclasses of Mammals, whose constructor will call the Mammal's constructor with the correct name.
class Dog(Mammal):
def __init__(self):
Dog.self
This is my code. It says type object 'Dog' has no attribute 'self' what's the problem?
when print(Dog().get_name()) I should get Dog.
If you are using Python 2.x, you should write
super(Dog, self).__init__('name')
or, for Python 3:
super().__init__('name')
instead of
Dog.self
See Understanding Python super() with __init__() methods for detail.
If you want Dog().get_name() to return 'Dog', you should call
super(Dog, self).__init__('Dog')
You should write like this:
class Dog(Mammal):
def __init__(self):
super().__init__('dog name')

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