Problem regarding inheritance in python classes - python

I am trying to inherit methods from one a base class to another class, specifically the __str__ method, where I only need to change it a little by adding a note.
Something like this.
class Person:
def __init__(self, name=None):
self.name = name
def __str__(self):
return ' Name: ' + str(self.name)
And my second class.
class AnnotatedPerson(Person):
def __init__(self, name=None, note=None):
super().__init__(name=None)
self.note = note
def __str__(self):
return ' Name: ' + str(self.name) + 'Note:' + str(self.note)
However this is not working, when printing print(AnnotatedPerson.__str__()) on an object I only get the part note and the name part in the __str__ method is set to None for objects that do have a value.
What is my mistake?

This is the problem:
class AnnotatedPerson(Person):
def __init__(self, name=None, note=None):
super().__init__(name=None)
You are passing None as the value of the name argument, instead of passing through the argument you got. You just need:
super().__init__(name)
This is an important point. When you say
...(name=name,...)
The first one is the name of an argument in the function being called. The second one is the name of a local variable in the function doing the calling. So:
...(name=None,...)
doesn't mention the local variable at all. It just forces a value into the function being called.

You've been pass None to variables, soo you have no results from them, below the code i'm using using the string repr from Person.
class AnnotatedPerson(Person):
def __init__(self, name=None, mother=None, father=None, born=None, died=None, note=None) -> None:
super().__init__(name, mother, father, born, died) # pass the arguments directly
self.note=note
def __str__(self) -> str:
return f'{super().__str__()} Note: {self.note}' # Here you have to pass only string representation from super().__str__() and add the note variable
if __name__ == "__main__":
obj1 = AnnotatedPerson("Gerson", "Maria", "José", "19/12/1975","02/02/2022","Died like a wild horse")
print(obj1)
#Name: Gerson Note: Died like a wild horse
Here a thread with a semelhant question:
python multiple inheritance passing arguments to constructors using super

Related

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.

Python ignoring property setter in favor of property getter

I have created a class that has a property with a setter. There are 2 different ways to use this class, so the values of some of the object components may be set in a different order depending on the scenario (i.e. I don't want to set them during __init__). I have included a non-property with its own set function here to illustrate what is and isn't working.
class Dumbclass(object):
def __init__(self):
self.name = None
self.__priority = None
#property
def priority(self):
return self.__priority
#priority.setter
def priority(self, p):
self.__priority = p
def set_name(self, name):
self.name = "dumb " + name
def all_the_things(self, name, priority=100):
self.set_name(name)
self.priority(priority)
print self.name
print self.priority
When I run the following, it returns TypeError: 'NoneType' object is not callable. I investigated with pdb, and found that it was calling the getter instead of the setter at self.priority(priority).
if __name__ == '__main__':
d1 = Dumbclass()
d1.all_the_things("class 1")
d2 = Dumbclass()
d2.all_the_things("class 2", 4)
What's going on here?
Short Answer: Please change your line self.priority(priority) to self.priority = priority
Explanation: Setter is called only when you assign something to the attribute. In your code, you are not doing an assignment operation.
Here are some nice references if you want to understand more:
Python descriptors
How does the #property decorator work?
Real Life Example
You are facing this issue due to trying to treat priority as a method in your all_the_things method. At this point it is already a property, so you assign to it like a variable:
def all_the_things(self, name, priority=100):
self.set_name(name)
self.priority = priority

TypeError in Python 3.x

I have no idea what is wrong! This is a very simple program and I have done a lot head banging! Please someone enlighten me!
This a lab problem from the CSE 111 - Programming Language II course. They teach Java at the university and the code I wrote in Java works fine.
I just have to create a Student class with some fields to hold the basic information about a student with methods to get and set the attributes. Then create an instance of that class and tryout the methods.
But every time I run this program the following error occurs:
TypeError: set_name() takes exactly 1 positional argument (2 given)
Here is the code I wrote.
class Student:
'''Student class'''
name = None
id = 0
address = None
cgpa = None
def get_name():
return name
def set_name(n):
name = n
def get_id():
return id
def set_id(i):
id = i
def get_address():
return address
def set_address(a):
address = a
def get_cgpa():
return cgpa
def set_cgpa(c):
cgpa = c
#An object of Student class
jack = Student()
jack.set_name('jacky')
print(jack.get_name())
You're not accepting a reference to your instance as the first argument to that method, i.e. your set_name() should be written:
def set_name(self, n):
self.name = n
This is somewhat different from other languages where there is a built-in keyword (such as this) that refers to the current object. Python passes that reference explicitly, as an argument to the method.
All your other methods must be modified similarly.
Note that just setting name = n sets a local variable name which goes away when the method ends; it does not set anything on the instance. You have to explicitly set self.name if you want an instance attribute.
Also, and this is a matter of style, but you do not usually write set and get methods in Python. It is normal practice to set and get attributes directly. If you want to do validation of values, use a property instead. So basically, none of your methods are actually necessary in good style.
However, you don't have an __init__() method. Usually you would pass the desired attributes of the instance when instantiating the class and save these on the instance.
class Student:
def __init__(self, name, id, address, cgpa):
self.name = name
self.id = id
self.address = address
self.cgpa = cgpa
herman = Student("Herman Munster", 12345, "1313 Mockingbird Lane", 4.0)
Try this:
import sys
class Student:
'''Student class'''
self.name = None
self.id = 0
self.address = None
self.cgpa = None
def get_name(self):
return self.name
def set_name(self, n):
self.name = n
def get_id(self):
return self.id
def set_id(self, i):
self.id = i
def get_address(self):
return self.address
def set_address(self, a):
self.address = a
def get_cgpa(self):
return self.cgpa
def set_cgpa(self, c):
self.cgpa = c
You need to pass self as the first argument to each member function of the class. Member variables must then be referred to with self, i.e. self.name. Furthermore, you may wish to include an __init__() function; this serves usually to initialize any member variables, and is called at the instantiation of the class.
Take a look at the Python documentation here for some examples on well-formed classes: http://docs.python.org/tutorial/classes.html#random-remarks
In Python, you need to pass in self for each of your member functions. You also need to reference class variables as self.x, if you want them to take an effect.
Here are a couple examples that you need to apply to the rest of your code.
def set_name(self, n):
self.name = n
def get_cgpa(self):
return self.cgpa
There is some explanation for why this is the case in the documentation.
This is because first argument of methods is self - the class instance.
See What is the purpose of self?
and http://docs.python.org/tutorial/classes.html#class-objects

python metaclass doesn't remember the new value

I wrote a class Person with a metaclass Spell. In the metaclass I change an attribute and it is ok, but if i want to use this new value for another operation, it doesn't work and it use the previous value.
How can i fix this?
class Spell(type):
def __new__(cls,classname,super,classdict):
def pph( hours ): return lambda self : classdict['pay_per_hour'] * hours
classdict['pay_per_hour'] = 12
classdict['day_salary'] = pph(8)
return type.__new__(cls, classname, super, classdict )
class Person(metaclass=Spell):
def __init__(self,name,lastname,bday):
self.name = name
self.lastname = lastname
self.bday = bday
def get_name(self):
return self._name
def get_lastname(self):
return self._lastname
def get_bday(self):
return self._bday
def __repr__(self):
return "name: {0}, lastname: {1}, bday: {2}".format(self.name,self.lastname,self.bday)
if __name__ == "__main__":
persona4 = Person("lugdfgca","djfosd","16 febbraio 85")
print(persona4.pay_per_hour)
print(persona4.day_salary())
persona4.pay_per_hour=15
print(persona4.pay_per_hour)
print(persona4.day_salary())
The output is
12
96
15
96
but 96 is 12*8 not 15*8, why? where is the error?
The lambda you created refers to the dictionary filled during class construction. Later (after class creation) changes to class variables are not reflected in it, but even if that was the case, the line persona4.pay_per_hour = 15 assigns a new instance attribute instead of changing the class attribute. Use self.pay_per_hour in the functions produced by pph to get the value the instance in question uses at the moment.
Or, even better, do away with the metaclass. There's no reason to use them here, and as you saw, it's easy to make things less extensible than needed.
class Spell:
pay_per_hour = 12
hours_per_day = 8
# #property # allows nicer syntax, look it up if you don't know it
def day_salary(self):
return hours_per_day * pay_per_hour
class Person(Spell):
...
This handles changes to pay_per_hour and hours_per_day transparently and at instance level.
The problem is that your function pph only look up the value of pay_per_hour in the class dictionary, while you only override the value of pay_per_hour in the instance. In Python, when you lookup a value of a field of an object, it first check in the instance dictionary, then in the class dictionary (and all the super class in the mro order).
You need to change your metaclass to:
def __new__(cls,classname,super,classdict):
def pph( hours ): return lambda self : self.pay_per_hour * hours
classdict['pay_per_hour'] = 12
classdict['day_salary'] = pph(8)

Is it safe to replace a self object by another object of the same type in a method?

I would like to replace an object instance by another instance inside a method like this:
class A:
def method1(self):
self = func(self)
The object is retrieved from a database.
It is unlikely that replacing the 'self' variable will accomplish whatever you're trying to do, that couldn't just be accomplished by storing the result of func(self) in a different variable. 'self' is effectively a local variable only defined for the duration of the method call, used to pass in the instance of the class which is being operated upon. Replacing self will not actually replace references to the original instance of the class held by other objects, nor will it create a lasting reference to the new instance which was assigned to it.
As far as I understand, If you are trying to replace the current object with another object of same type (assuming func won't change the object type) from an member function. I think this will achieve that:
class A:
def method1(self):
newObj = func(self)
self.__dict__.update(newObj.__dict__)
It is not a direct answer to the question, but in the posts below there's a solution for what amirouche tried to do:
Python object conversion
Can I dynamically convert an instance of one class to another?
And here's working code sample (Python 3.2.5).
class Men:
def __init__(self, name):
self.name = name
def who_are_you(self):
print("I'm a men! My name is " + self.name)
def cast_to(self, sex, name):
self.__class__ = sex
self.name = name
def method_unique_to_men(self):
print('I made The Matrix')
class Women:
def __init__(self, name):
self.name = name
def who_are_you(self):
print("I'm a women! My name is " + self.name)
def cast_to(self, sex, name):
self.__class__ = sex
self.name = name
def method_unique_to_women(self):
print('I made Cloud Atlas')
men = Men('Larry')
men.who_are_you()
#>>> I'm a men! My name is Larry
men.method_unique_to_men()
#>>> I made The Matrix
men.cast_to(Women, 'Lana')
men.who_are_you()
#>>> I'm a women! My name is Lana
men.method_unique_to_women()
#>>> I made Cloud Atlas
Note the self.__class__ and not self.__class__.__name__. I.e. this technique not only replaces class name, but actually converts an instance of a class (at least both of them have same id()). Also, 1) I don't know whether it is "safe to replace a self object by another object of the same type in [an object own] method"; 2) it works with different types of objects, not only with ones that are of the same type; 3) it works not exactly like amirouche wanted: you can't init class like Class(args), only Class() (I'm not a pro and can't answer why it's like this).
Yes, all that will happen is that you won't be able to reference the current instance of your class A (unless you set another variable to self before you change it.) I wouldn't recommend it though, it makes for less readable code.
Note that you're only changing a variable, just like any other. Doing self = 123 is the same as doing abc = 123. self is only a reference to the current instance within the method. You can't change your instance by setting self.
What func(self) should do is to change the variables of your instance:
def func(obj):
obj.var_a = 123
obj.var_b = 'abc'
Then do this:
class A:
def method1(self):
func(self) # No need to assign self here
In many cases, a good way to achieve what you want is to call __init__ again. For example:
class MyList(list):
def trim(self,n):
self.__init__(self[:-n])
x = MyList([1,2,3,4])
x.trim(2)
assert type(x) == MyList
assert x == [1,2]
Note that this comes with a few assumptions such as the all that you want to change about the object being set in __init__. Also beware that this could cause problems with inheriting classes that redefine __init__ in an incompatible manner.
Yes, there is nothing wrong with this. Haters gonna hate. (Looking at you Pycharm with your in most cases imaginable, there's no point in such reassignment and it indicates an error).
A situation where you could do this is:
some_method(self, ...):
...
if(some_condition):
self = self.some_other_method()
...
return ...
Sure, you could start the method body by reassigning self to some other variable, but if you wouldn't normally do that with other parametres, why do it with self?
One can use the self assignment in a method, to change the class of instance to a derived class.
Of course one could assign it to a new object, but then the use of the new object ripples through the rest of code in the method. Reassiging it to self, leaves the rest of the method untouched.
class aclass:
def methodA(self):
...
if condition:
self = replace_by_derived(self)
# self is now referencing to an instance of a derived class
# with probably the same values for its data attributes
# all code here remains untouched
...
self.methodB() # calls the methodB of derivedclass is condition is True
...
def methodB(self):
# methodB of class aclass
...
class derivedclass(aclass):
def methodB(self):
#methodB of class derivedclass
...
But apart from such a special use case, I don't see any advantages to replace self.
You can make the instance a singleton element of the class
and mark the methods with #classmethod.
from enum import IntEnum
from collections import namedtuple
class kind(IntEnum):
circle = 1
square = 2
def attr(y): return [getattr(y, x) for x in 'k l b u r'.split()]
class Shape(namedtuple('Shape', 'k,l,b,u,r')):
self = None
#classmethod
def __repr__(cls):
return "<Shape({},{},{},{},{}) object at {}>".format(
*(attr(cls.self)+[id(cls.self)]))
#classmethod
def transform(cls, func):
cls.self = cls.self._replace(**func(cls.self))
Shape.self = Shape(k=1, l=2, b=3, u=4, r=5)
s = Shape.self
def nextkind(self):
return {'k': self.k+1}
print(repr(s)) # <Shape(1,2,3,4,5) object at 139766656561792>
s.transform(nextkind)
print(repr(s)) # <Shape(2,2,3,4,5) object at 139766656561888>

Categories

Resources