Python "replace" or destroy object reference - python-3.x

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.

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

In Python, how to get typing right when using a class decorator?

This is my first attempt to make a Python decorator, and I humbly admit to drawing inspiration from a number of StackOverflow posts while doing so. When I scavenged the __instancecheck__ method, the last two asserts stopped triggering, but now PyCharm's built-in linter isn't happy with the very last assert.
Expected type 'Union[type, Tuple[Union[type, Tuple[Any, ...]], ...]]', got 'Multiton' instead.
What is the root of the typing problem, and how can it be fixed?
Addition, after making the __init__ change recommended by a commenter another warning surfaces.
Local variable 'instance' might be referenced before assignment.
class Multiton(object):
""" When init parameters match, reuse an old initialized object instead of making a new one. """
def __init__(self, cls):
# self.__dict__.update({'instances': list(), 'cls': cls})
self.instances = list()
self.cls = cls
def __call__(self, *args, **kwargs):
# make a key with the parameters
key = (args, kwargs)
# search for a matching old instance
found = False
for instance in self.instances:
if instance['key'] == key:
found = True
break
# if not found then create a new instance
if not found:
instance = {'key': key, 'object': self.cls(*args, **kwargs)}
self.instances.append(instance)
return instance['object']
def __instancecheck__(self, other):
return isinstance(other, self.cls)
#Multiton
class YourClass:
def __init__(self, number):
self.number = number
yc_1 = YourClass(1)
yc_2 = YourClass(2)
yc_two = YourClass(2)
assert yc_1 != yc_2
assert yc_2 == yc_two
assert not isinstance(yc_1, Multiton)
assert isinstance(yc_1, YourClass)
The false warnings are PyCharm bugs which hopefully the fine folks at JetBrains will remedy soon.
https://youtrack.jetbrains.com/issue/PY-38590
https://youtrack.jetbrains.com/issue/PY-49966

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 - Setting Class Attributes during Object instatiation(construction)

I have a class with an attribute which has to be set only once (let us say via a command line parameter). From there on it is not to be changed.
class Example(object):
_classattribute = None
I am doing this by reading the command line param, and passing them as parameters during object construction. Depending on the classatribute, I return a different object Type.
class Example(object):
_classattribute = None
_instance = None
def __new__(cls, attribute=None):
if not cls._clsattribute:
if not attribute:
raise ValueError('class attribute not set')
cls._clsattribute = attribute
if cls._classatribute == condition1:
cls._instance = Type1(cls._classattribute)
if cls._classatribute == condition2:
cls._instance = Type2(cls._classattribute)
return cls._instance
class Type1:
def __init__(self, property):
self.property = property
class Type2:
def __init__(self, property):
self.property = property
During the object construction, for the very first time:
eg1 = Example("This is type1 attribute")
Subsequent construction of the object:
eg2 = Example()
Is this a good approach I can't say. It feels too explicit to me. Besides it is similar to the Borg, shared state pattern, except for the one time setting of the class attribute _claassattribute.
Any form of critique/feedback/thought/suggestion is welcome.
This is something of a personal design choice, but my take on this is that __new__ should always return an object of the class or None. In your code, there is no reason to use a class if it can never return an instance of its own. What you probably want ressembles a factory that returns either objects of Type1 or Type2.
def get_type_factory(attribute):
if attribution == condition1:
return lambda: Type1(attribute)
elif attribute == condition2:
return lambda: Type2(attribute)
else:
raise ValueError
# Can be used like so
factory = get_type_factory('foo')
type_object1 = factory()
type_object2 = factory()

Resources