Decorators unexpectedly change constructor behavior in Python - python-3.x

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.

Related

Python (+Django) : use #property in success_url KO . while using it with get_success_url is OK (class based view)

I found a workaround for my issue but I need to know why the first above case doesn't work.
I need to pass a parameter (reman_pk) to my view but when I try :
class RepairCreateView(LoginRequiredMixin, CreateView):
#property
def reman_pk(self):
return int(self.kwargs['reman_pk'])
[...]
success_url = reverse_lazy(
'reman:update-reman', kwargs={'pk': reman_pk})
[...]
... I got an error
django.urls.exceptions.NoReverseMatch: Reverse for 'update-reman' with keyword arguments '{'pk': <property object at 0x10c20bbd0>}' not found. 1 pattern(s) tried: ['reman/update/(?P[0-9]+)/$']
But when in the same class based view I use :
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('reman:update-reman', kwargs={'pk': self.reman_pk})
... it's OK : an int is well passed in my URL.
I tried to pass int(reman_pk) in the first method ... not better.
I've already use #property in the past and always got a value (int/str) and not property object.
EDIT (FULL views.py)
success_url = reverse_lazy...is commented. I must use def get_success_url( ... instead. Otherwise I get the above mentioned error.
class RepairCreateView(LoginRequiredMixin, CreateView):
#property
def reman_pk(self):
return int(self.kwargs['reman_pk'])
# success_url = reverse_lazy(
# 'reman:repairs-list', kwargs={'pk': reman_pk})
success_message = "Nouvelle réparation créée"
form_class = RepairCreateForm
template_name = 'reman/repair_create_form.html'
def get_context_data(self, *args, **kwargs):
context = super(RepairCreateView, self).get_context_data(
*args, **kwargs)
context['title'] = 'Nouveau Repair'
context['pk'] = self.reman_pk
return context
def get_initial(self):
reman = Reman.objects.get(pk=self.reman_pk)
return {'reman': reman}
def get_success_url(self, **kwargs):
return reverse_lazy('reman:repairs-list', kwargs={'pk': self.reman_pk})
This is not related to django, this is related to python in general. When you want to access a class property within the class you always have to call self before!
class Tree:
fruits = 5
#property
def leafes(self):
return self.fruits * 5
def show_tree(self):
print(self.fruits)
print(self.leafes)
print(leafes) # THIS LINE WOULD ERROR
Edit after comment of OP
I don't know how to phrase this properly. Anyhow this keeps being a problem related to python and not to django. The reason is how classes work.
You probably know the def __init__(self): function. That is called when the class gets instanciated. After that function got called your class can use all the self attributes (class attributes). But class attributes like my fruits = 5 get assigned even before that def __init__(self) method is called. So all your assignments directly inside the body of the class do not have self yet.
class Tree:
fruits = 5
def __init__(self):
self.twigs = 10
self.weight = self.twigs + self.fruits # THIS WORKS
class Tree:
fruits = 5
weight = self.twigs + fruits # THIS DOES NOT WORK
def __init__(self):
self.twigs = 10
Last example does not work because at the moment you want to assign weight = self.twigs + fruits your class's __init__ function was not called yet. So you can not use self at that place.

How to use the second parent class methods which is inheriting from an interface in python?

I am trying to implement an interface and this interface is taken by two concrete classes say class First and class Second, I have another class that takes these two classes as a parent class CO. The class CO makes a decision based on a flag to return which of the two inherited classes, so as to use their implementation.
from abc import ABC, abstractmethod
class common(ABC):
#abstractmethod
def firstfunction(self):
pass
#abstractmethod
def secondfunction(self):
pass
class First(common):
def __init__(self)-> boto3:
self.ert = "danish"
# self.username = kwargs["username"]
# self.password = kwargs["password"]
print("Inside First function")
def firstfunction(self):
print("My implementation of the first function in FIRST CLASS")
def secondfunction(self):
print("My implementation of the second function in FIRST CLASS")
class Second(common):
def __init__(self):
self.rty = "pop"
# self.session_id = kwargs["session_id"]
print("Inside Second function")
def firstfunction(self):
print("My implementation of the first function in SECOND CLASS")
def secondfunction(self):
print("My implementation of the second function in SECOND CLASS")
class CO(First, Second):
def __init__(self):
self.inst = self.jo()
def jo(self):
a = True
if a:
main_object = First()
return main_object
else:
main_object = Second()
I am instantiating and calling the methods
mymainclass = co()
objt = mymainclass
objt.firstfunction()
objt.secondfunction()
So my condition is if the flag a = True in the CO class then the Concrete class First implementation of the methods should be used and we should get the output like this:
Inside the First function
My implementation of the first function in FIRST CLASS
My implementation of the second function in FIRST CLASS
If the flag a = False in the CO class then concrete class Second should be used and we should get the output like this:
Inside the Second function
My implementation of the first function in FIRST CLASS
My implementation of the second function in FIRST CLASS
From the given code I am getting the following output for the flag a = False :
Inside the Second function
My implementation of the first function in FIRST CLASS
My implementation of the second function in FIRST CLASS
Can someone make this code work? What am I doing wrong? I know I can make a function that takes these two classes as variables and then return what I want based on a condition. But I want to use classes for the same
I would not inherit class CO from either First or Second because you want to end up with an object that is either an instance of class First or class Second. The most straightforward way of doing this is to define method __new__ which will give you more control over instance creation.
Keep all of your code the same (after fixing the obvious errors, such as boto3 not being defined) and changing class CO as follows:
class CO:
def __new__(cls):
a = True
if a:
main_object = First()
else:
main_object = Second()
return main_object
o = CO()
print(type(o))
Prints:
Inside First function
<class '__main__.First'>
If you want to better parameterize the instance creation (and simplify the code within __new__), then have variable a be an argument to CO:
class CO:
def __new__(cls, a):
return First() if a else Second()
o = CO(False)
print(type(o))
Prints:
Inside Second function
<class '__main__.Second'>
You could optionally have class CO have common as its base class to document that instantiating this class results in an instance that implements the common interface. But doing so will not result in enforcing the actual type that is returned by __new__:
class CO(common):
def __new__(cls, a) -> common:
# The Python interpreter is not type-checking what is returned:
#return First() if a else Second()
return []
o = CO(False)
print(type(o))
Prints:
<class 'list'>
You can also substitute a factory function for class CO:
def common_factory(a):
return First() if a else Second()
o = common_factory(False)
Note
If you want to ensure that classes that inherit from base class common must override both firstfunction and secondfunction to more closely emulate a pure interface as implemented in other object-oriented languages, then you should define these functions in common so that they raise an NotImplementedError exception:
class common(ABC):
#abstractmethod
def firstfunction(self):
raise NotImplementedError
#abstractmethod
def secondfunction(self):
raise NotImplementedError
Unfortunately, the enforcement is done at run time with its attendant surprises instead of at compile time as is done in other object-oriented languages.
Okay I think in this situation, CO class must come from common class and implement the first and second function. In the implementation, it will use the First or Second classes functions depends on the result of jo function. Here is the correct Co class code:
class CO(common):
def __init__(self):
self.inst = self.jo()
def jo(self):
a = False
if a:
return First()
return Second()
def firstfunction(self):
self.inst.firstfunction()
def secondfunction(self):
self.inst.secondfunction()
a = CO()
a.firstfunction()
a.secondfunction()
You can directly call the method you want by using the class name.
Unless you have some pressing reason to do this that you didn't include in your question, I would avoid this in favor of a factory function that returns either a First or Second instance depending on the inputs.
class common(ABC):
#abstractmethod
def firstfunction(self):
pass
#abstractmethod
def secondfunction(self):
pass
class First(common):
def __init__(self):
self.ert = "danish"
super().__init__(self)
def firstfunction(self):
print("My implementation of the first function in FIRST CLASS")
def secondfunction(self):
print("My implementation of the second function in FIRST CLASS")
class Second(common):
def __init__(self):
self.rty = "pop"
super().__init__(self)
def firstfunction(self):
print("My implementation of the first function in SECOND CLASS")
def secondfunction(self):
print("My implementation of the second function in SECOND CLASS")
class CO(First, Second):
def __init__(self):
super().__init__(self)
def use_first(self):
return True or False # whatever logic you have for determining this
def firstfunction(self):
if self.use_first():
return First.firstfunction(self)
else:
return First.firstfunction(self)

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

How to call a function within a class in Python3

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.")

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?

Resources