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

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.

Related

Python "replace" or destroy object reference

class A :
CONST = 0
def __init__(self, base) :
self.base = base
return
def dostuff(self) :
if type(self).CONST == self.base.value :
print("this is good")
else :
print("CAUSES FIRE!")
return
pass
class A_1 (A) :
CONST = 1
def __init__(self, *args, **kwargs) :
super().__init__(*args, **kwargs)
return
pass
class A_2 (A) :
CONST = 2
def __init__(self, *args, **kwargs) :
super().__init__(*args, **kwargs)
return
pass
class Base :
def __init__(self, value) :
self.value = value
return
#property
def a(self) :
if self.value == 1 :
return A_1(self)
if self.value == 2 :
return A_2(self)
return None
pass
# Initialize base
my_base = Base(1)
# A.dostuff() is intended to only be used "trough" the property.
my_base.a.dostuff()
#> this is good
# It is not really intended to be stored.
my_a = my_base.a
# As some external factors can cause the Base.value to change.
my_base.value = 2
# Which works fine when getting the property
my_base.a.dostuff()
#> this is good
# But not when the reference was stored.
my_a.dostuff()
#> CAUSES FIRE!
Can we somehow prevent this fire?
I do know it seems questionable to initialize the returned property, but this is intended.
The best case would be to "destroy" the reference/object. Raise an exception when using my_a or it becoming None.
One way would be to put an if self.custom_verify() in every method of the sub-classes, which exits or raises an exception (I rather have the application stop if such a thing happens). - but that seems like a lot of copy-paste for the project, and I hope there is a better way...
One option could be, to "replace" the my_a reference. - meaning the class would change from A_1 to A_2 and back, depending on what my_base.value is. Any attributes inside the A class stay the same. only methods and constants change in the application.

How operator overloading can return a third class in Python?

I have the following classes in different files
class Fruit():
def __init__(self, value=0):
self.value = value
def __add__(self, other):
if type(self) == type(otro):
## PROBLEM AREA ##################################################
return type(self)(self.value + other.value)
else:
raise Exception.SumError(self, other) # Custom exception
def __repr__(self):
return f"{type(self).__name__}({self.value})"
The Fruit () class is the base class, from which the following two child classes inherit
class Pear(Fruit):
"""docs"""
def __init__(self, quantity=0):
super().__init__(quantity)
self.unit = "Pe"
class Apple(Fruit):
"""docs"""
def __init__(self, quantity=0):
super().__init__(quantity)
self.unit = "Ap"
The class required in the result is the following:
class Market_List():
"""docs"""
def __init__(self, value=0):
self.value = value
Currently I can add Pears() with Pears() and Apples() with Apples(), my question is how do I make adding Pears() with Apples() throw me a Market_List() object. I have already tried to use from market_list import Market_List at the beginning of the Fruit() class, but wanting to do the same in the Market_List() class to do the inverse operation then it enters a loop and gives me an error
Put the inports in the init methods, then the code will not run at module load time and you will not get the error

How to access outer class attribute from inner class

I'm having trouble understanding nested classes and have a specific problem
I have a model "Pair" which has an attribute "name." I'd like to have a nested class called "History" that passes variables "start" and "end" as well as its parent (Pair) variable's "name". A #property decorated function would then return the standard deviation of that list.
To call the standard deviation I'd be looking for something like:
pair = Pair(base_currency='USD', quote_currency='GBP')
print(pair.History(granularity='1D', start=start_date, end=end_date).std_dev
>>> 634
I think it should look something like the below, but cant figure out how to inherit the outer class attribute "name"
class Pair:
def __init__(self, base_currency, quote_currency):
self.base_currency = base_currency
self.quote_currency = quote_currency
self.name = '{base}-{quote}'.format(base=base_currency, quote=quote_currency)
self.history = self.History()
class History:
def __init__(self, granularity, start, end):
self.name = HOW DO I GET THE PARENT NAME HERE
self.granularity = granularity
self.start = start
self.end = end
def data(self):
return retrieve_history(product=self.name, granularity=self.granularity, start=self.start, end=self.end)
#property
def std_dev(self):
return stdev(self.data)

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.

Python overriding default attribute assignment

for a specific framework i work with, i need to define object attributes as special classes, for example, instead of writing this:
class A:
def __init__(self):
self.some_int = 2
i would need to write:
class A:
def __init__(self):
self.some_int = SpecialIntWrapper(name = "some_int", value = 2)
I would like to somehow override operators/methods so that typing the first code (self.some_int = 2) will call SpecialIntWrapper behind the scenes, with the attribute name and value.
is this possible?
Basically there are two ways - via a #property decorator (preferable unless you want to affect arbitrary names)
class MyClass:
def __init__(self):
self.some_int = 2
# if you know the name of the property define it as a property - a getter
#property
def some_int(self):
return self._some_int
# and a setter
#some_int.setter
def some_int(self, value):
self._some_int = SpecialIntWrapper("some_int", value)
or overloading the __setattr__ magic method
class MyClass:
def __init__(self):
self.some_int = 2
def __setattr__(self, name, value):
# in general if you dont know the names of the properties
# beforehand you can somehow filter them here
if name == "some_int":
super().__setattr__(name, SpecialIntWrapper(name=name, value=value))
else:
# to use the setattr in a default way, just call it via super(Python 3)
super().__setattr__(name, value)
either way the some_int will be initialized to the SpecialIntWrapper instance
>>>print(MyClass().some_int)
<__main__.SpecialIntWrapper object at 0x03721810>
Something like this
class SpecialIntWrapper:
def __init__(self, name, value):
pass
class MyClass:
def __init__(self):
self.some_int = 3
def __setattr__(self, key, value):
if key == 'some_int':
self.__dict__[key] = SpecialIntWrapper(key, value)
else:
self.__dict__[key] = value
print(MyClass().some_int)
# >>> <__main__.SpecialIntWrapper object at 0x1076f1748>

Resources