How to call a function within a class in Python3 - python-3.x

When running this code, I am attempting to get the same output in flipper.swim() that I get from Salmon.swim(). The Salmon.swim() properly outputs the string from that method, but when I call flipper.swim(), I get the following error: TypeError: swim() takes 0 positional arguments but 1 was given
class Fish (object):
def __init__(self, name):
self.name = name
def swim():
print("The fish swam.")
class Salmon(Fish):
pass
flipper = Fish("FLIPPER")
Salmon.swim()
Fish.swim()
print(flipper.name)
flipper.swim()

I think this is what you are trying to do
class Fish (object):
def __init__(self, name):
self.name = name
def swim(self):
print(self.name + " swam.")
class Salmon(Fish):
pass
flipper = Fish("FLIPPER")
salmon = Salmon("FOO")
Salmon.swim(flipper)
Fish.swim(flipper)
print(flipper.name)
Fish.swim(flipper)
Fish.swim(salmon)
The above will output:
FLIPPER swam.
FLIPPER swam.
FLIPPER
FLIPPER swam.
FOO swam.

you get the error because python automatically pass the instance of your class (flipper in your sample code) as the first argument to your method swim, that by convention we call self, to turn off this behavior you need to specify that said method doesn't require/use your instance of this class, and that is done with the staticmethod decorator
class Fish (object):
def __init__(self, name):
self.name = name
#staticmethod
def swim():
print("The fish swam.")

Related

OOP - Python - printing instance variable too when I call static method alone

Here in this code I am just calling out my static method, but it prints my instance variable too. Could you please explain the reason for that, and how to avoid them being printed?
Like below:
I am a static Method
None
class Player:
def __init__(self, name = None):
self.name = name # creating instance variables
#staticmethod
def demo():
print("I am a static Method")
p1 = Player()
print(p1.demo())
As Python docs says:
Print objects to the text stream file, separated by sep and followed
by end. sep, end, file, and flush, if present, must be given as
keyword arguments.
So you can return your message in method and then just print it:
class Player:
def __init__(self, name = None):
self.name = name # creating instance variables
#staticmethod
def demo():
return "I am a static Method"
p1 = Player()
print(p1.demo())

Multiple inheritance, super() and their correct use with arguments in Python

I'm trying to understand multiple inheritance in python. I think that "kinda" got it, but I'm missing a few pieces. I know that if I have two clases I can do something like:
class A():
def __init__(self,name):
self.name = name
class B(A):
def __init__(self,name):
A.__init__(self,name)
self.mm = False
self.name = name
b = B("Peter")
My problem is when I have more classes and each class has their own init arguments. At first glance, it makes like no sense to have something like this:
class A():
def __init__(self,name,arg_a1,arg_a2):
self.name = name
class B(A):
def __init__(self,name,arg_b1,arg_b2,arg_a1,arg_a2...):
A.__init__(self,name,arg_a1,arg_a2...)
self.mm = False
self.name = name
class C(B):
def __init__(self,name,arg_c1,arg_c2,arg_b1,arg_b2,arg_a1,arg_a2.........):
B.__init__(self,name,arg_b1,arg_b2,arg_a1,arg_a2...)
self.name = name
So I started to look how to do it in an efficient way and not just hardcode it. Thats when I came across with multiple inheritance and thats when my doubts started to arraise.
If I have 3 classes:
class A():
def __init__(self,name):
self.name = name
class B(A):
def __init__(self,name,*args,**kwargs):
super().__init__(*args,**kwargs)
self.mm = False
self.name = name
class C(B):
def __init__(self,a,j,*args,**kwargs):
super().__init__(*args,**kwargs)
self.a = a
self.j = j
c = C("p",1,5,name="p")
Why this give an error but adding name as an init argument does not?
In this other example, if I add another argument to A init's function the I get TypeError: __init__() got multiple values for argument 'name'.
class A():
def __init__(self,name,lastname):
self.name = name
self.lastname = lastname
class B(A):
def __init__(self,name,*args,**kwargs):
super().__init__(name,*args,**kwargs)
self.mm = False
self.name = name
class C(B):
def __init__(self,a,j,*args,**kwargs):
super().__init__(*args,**kwargs)
self.a = a
self.j = j
c = C("p",1,5,name="p")
So, after all this, several questions comes to my mind.
Why this TypeError is generated?
How can I make inheritance "smart"?
Do I always need to use *args and **kwargs with multiple inheritance?
And all this gets me to the point to the libraries I use daily. Probably some of them use this concetps (I don't know, I'm assuming). What happes when the user puts a kwarg that is not present in any class? How do python "knows" that name goes in class A and not class B or viceversa?

Decorators unexpectedly change constructor behavior in Python

Below, I show a simplified example of a more complicated code, but nonetheless, it fully represents the issue that I have encountered.
Part 1: this works fine, no issues:
class Animal():
def __init__(self, animal_name = "no name given"):
self.set_name(animal_name)
def get_name(self):
return self._animal_name
def set_name(self, animal_name):
self._animal_name = animal_name
class Dog(Animal):
def __init__(self, dog_breed = "no breed", dog_name = "no name given"):
self._dog_breed = dog_breed
super().__init__(dog_name)
def get_breed(self):
print(self._dog_breed)
x = Dog('Greyhound', 'Rich')
Part 2: after introducing getter & setter decorators, the code stops working:
class Animal():
def __init__(self, animal_name = "no name given"):
#THE LINE BELOW SEEMS TO CAUSE AN ISSUE
self.name(animal_name)
#property
def name(self):
return self._animal_name
#name.setter
def name(self, animal_name):
self._animal_name = animal_name
class Dog(Animal):
def __init__(self, dog_breed = "no breed", dog_name = "no name given"):
self._dog_breed = dog_breed
super().__init__(dog_name)
def get_breed(self):
print(self._dog_breed)
x = Dog('Greyhound', 'Rich')
Output: AttributeError: 'Dog' object has no attribute '_animal_name'
When I keep the decorators in Part 2 but change the constructor in the Animal class to:
class Animal():
def __init__(self, animal_name = "no name given"):
self._animal_name=animal_name
It works.
I am just curious why it doesn't work in the example above in Part 2?
Short answer:
The line
self.name(animal_name)
can be split in two parts:
tmp = self.name
tmp(animal_name)
First, self.name calls the getter and the result is treated as a function. The getter uses return self._animal_name and since the setter has never been called, the respective error occurs.
Long answer:
Let's take the following class:
class Animal:
def __init__(self, animal_name):
self.name(animal_name)
#property
def name(self):
return self._animal_name
#name.setter
def name(self, animal_name):
self._animal_name = animal_name
To understand what the line
self.name(animal_name)
actually does, you first need to understand decorators.
The code
#dec
def func(a, b, ...):
[...]
is equivalent to
def func_impl(a, b, ...):
[...]
func = dec(func_impl)
(except that you can not call func_impl directly). See, for example, PEP 318 for more information.
This means that you can write the Animal class from above without using decorators:
class Animal:
def __init__(self, animal_name):
self.name(animal_name)
def get_name(self):
return self._animal_name
name = property(get_name)
def set_name(self, animal_name):
self._animal_name = animal_name
name = name.setter(set_name)
In order to understand this code, you need to understand the builtin property, which is a class. See the python docs for detailed information.
The line name = property(get_name) creates an object of type property. When retrieving the value of the property, get_name is called.
The line name = name.setter(set_name) first calls name.setter(set_name), which creates a copy of the property, and then overwrites name with this copy. When assigning a value to the copy, set_name is called.
All in all, name is an object of type property that uses get_name as getter and set_name as setter.
How does this help?
You need to understand this: name is not a function. It is a property. It is not callable.
The problematic line
self.name(animal_name)
is actually equivalent to
self.get_name()(animal_name)
which this explains the error message: The constructor calls the getter, which tries to use return self._animal_name. But since the setter has not been called, yet, self._animal_name has not been set.

How to modify/override inherited class function in python?

I have the exact function name say_hello in both parent and inherited class. I want to specify the parameter name in the Kitten class but allows for user to specify the parameter in Cat class.
Is there a way to avoid the needs to repeat the line return ('Hello '+name) in the say_hello function in Kitten class?
Currently:
class Cat:
def __init__(self):
pass
def say_hello(name):
return ('Hello '+name)
class Kitten(Cat):
def __init__(self):
super().__init__()
def say_hello(name='Thomas'):
return ('Hello '+name)
x = Cat
print (x.say_hello("Sally"))
y = Kitten
print (y.say_hello())
Ideally:
class Cat:
def __init__(self):
pass
def say_hello(name):
return ('Hello '+name)
class Kitten(Cat):
def __init__(self):
super().__init__()
def say_hello():
return super().say_hello(name='Thomas') # Something like this, so this portion of the code doesn't need to repeat completely
The say_hello method should include self as the first parameter so that it can use the super() function to access the parent's say_hello method. You should also instantiate a class by calling it with parentheses:
class Cat:
def say_hello(self, name):
return 'Hello ' + name
class Kitten(Cat):
def say_hello(self, name='Thomas'):
return super().say_hello(name)
x = Cat()
print(x.say_hello("Sally"))
y = Kitten()
print(y.say_hello())
This outputs:
Hello Sally
Hello Thomas
i think you might miss a point about classes here which is crucial to what you want to achieve.
Let's first define a class Cat but take a close look at the differences to yours.
class Cat:
def __init__(self, name):
self.name = name
def say_hello(self):
return self.name
And now see the magic and i explain it later :
cat = Cat("Sally")
print(cat.say_hello())
>>> "Sally"
What happened in comparison to your code ?
First, your __init__ method does not take an argument. My version does and it is name. Furthermore i asign it to an instance variable self.name .
With this asignment i can access the name provided at instantiation in EVERY method which has self as it's first argument. What is instantiation ?
Well in your code you called x = Cat. With this x is an object of the class but not an Instance. You need to instantiate your class.
By doing so in my code cat = Cat("Sally") what python does is call the __init__ method of the class and return an Instance of it with everythin set/done as specified in the __init__method.
In my case i set self.name = name which makes , in the example, the value of self.name to "Sally".
So whenever i call self.name in a method , which includes self in it's parameters it becomes, after instantiation, "Sally".
Now the class Cat should be clear.
What to do now ?
Simply inherit from this class !
See:
class Cat:
def __init__(self, name):
self.name = name
def say_hello(self):
return self.name
class Kitten(Cat):
def __init__(self, name):
# This is done for convenience
super().__init__(name)
With this a Kitten-, same as a Cat-instance has a name and inherits the say_hello() mehtod to say it's name .
I hope this helps.

Call the value of variable of method in another method of same class in python

I want to access father's name in the last function(enquiry()), and the name is present in the father() function. How to access value of the name variable in the father() function?
class family(object):
def __init__(self,members,surname):
self.members=members
self.surname=surname
def father(self,name,occupation):
self.name=name
self.occupation=occupation
def mother(self,name,occupation):
self.name=name
self.occupation=occupation
def children(self,numbers):
self.numbers=numbers
def enquiry(self):
print("The name of the father is "self.father(name))
family(4,'vora')
family.father('john','business')
family.enquiry()
This code corrects your syntactic errors but you should definitely take member P i's advice because this is ugly
class family(object):
def __init__(self,members,surname):
self.members=members
self.surname=surname
def father(self,name,occupation):
self.fathers_name=name
self.occupation=occupation
def mother(self,name,occupation):
self.mothers_name=name
self.occupation=occupation
def children(self,numbers):
self.numbers=numbers
def enquiry(self):
print("The name of the father is " + self.fathers_name)
f = family(4,'vora')
f.father('john','business')
f.enquiry()

Resources