Why does the usage of this descriptor cause Recursion Error?
class Capitalized:
def __set__(self, instance, value):
instance.name = value.upper()
class Person(object):
name = Capitalized()
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f'{self.name} is {self.age} yr(s) old'
Person('dave', 25) # !Error
I get the following error:
RecursionError: maximum recursion depth exceeded while calling a Python object
However, if I don't use name = Capitalized(), everything is fine (well, except that I can't use the descriptor). I am using python v3.9.1.
You are constructing your classes in a way, so that __set__ is calling itself again and again. instance.name = value.upper() is calling Capitalized.__set__
To avoid this you may use a private attribute like this:
class Capitalized:
def __set__(self, instance, value):
print('setting')
instance._name = value.upper()
def __get__(self, instance, objtype=None):
return instance._name
Change instance._name to instance.name and you will see the infinite recursion.
Related
I am new to Classes and want to know how to return values from one class method to another. Below is the code and I want to return the queues from arrive() function to next_customer() function.
class DonutQueue():
def __init__(self):
self.queue=[]
self.queue2= []
def arrive(self,name,vip):
self.name = name
self.vip = vip
if self.vip==1:
self.queue2.append(self.name)
return self.queue2
else:
self.queue.append(self.name)
return self.queue
def next_customer(self):
while not self.queue2== []:
if not self.queue2==[]:
return self.queue
else:
return self.queue2
def main():
n = int(input("Enter the number of customers you want to add"))
for i in range(0,n):
name = input("Enter their name")
vip= int(input("Are they a VIP"))
DonutQueue().arrive(name,vip)
print(DonutQueue().next_customer())
If I add the below statement in the next_customer function, I get the mentioned error:
a = self.arrive(self.name,self.vip)
Error:
AttributeError: 'DonutQueue' object has no attribute 'name'
The problem is when I call next_customer() it returns None because I am getting values from init instead of arrives()
in python you have to define members in the __init__ function means. in your case you defined the self.name member only in arrive thats why its not available (and throws an exception) in your case.
What is the correct way to implement a data descriptor inside a metaclass? In the following (trivial) example, I wish to always append a question mark to the desired value before setting it:
class AddQDesc:
def __init__ (self, name):
self.name = name
def __get__ (self, instance, owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self, instance, value):
# What should go here ?
#setattr(instance, self.name, "{}?".format(value)) <- This gives me recursion error
#instance.__dict__[self.name] = "{}?".format(value) <- This gives me proxymapping error
pass
class Meta (type):
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
First, it looks like the descriptor was not used when I initialized var to 5. Can I somehow apply descriptor protocol here as well? (Make it "5?")
Second, how should the value be updated in the __set__ method? Updating the __dict__ gives me "TypeError: 'mappingproxy' object does not support item assignment" and using setattr gives me "RecursionError: maximum recursion depth exceeded while calling a Python object".
As I commented in the question, this is tricky - because there is no way from Python code to change a class' __dict__ attribute directly - one have to call setattr and let Python set a class attribute - and, setattr will "see" the descriptor in the metaclass, and call its __set__ instead of modifying the value in the class __dict__ itself. So, you get an infinite recursion loop.
Therefore, if you really require that the attribute proxied by the descriptor will "live" with the same name in the class'dict, you have to resort to: when setting the value, temporarily remove the descriptor from the metaclass, call setattr to set the value, and then restoring it back.
Also, if you want the values set in the class body to be handled
through the descriptor, they have to be set with setattr after the
class is created - type.__new__ won't check for the descriptor
as it builds the initial class __dict__.
from threading import Lock
class AddQDesc:
def __init__ (self, name):
self.name = name
self.lock = Lock()
def __get__ (self, instance, owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self, instance, value):
owner_metaclass = type(instance)
with self.lock:
# Temporarily remove the descriptor to avoid recursion problems
try:
# Note that a metaclass-inheritance hierarchy, where
# the descriptor might be placed in a superclass
# of the instance's metaclass, is not handled here.
delattr(owner_metaclass, self.name)
setattr(instance, self.name, value + 1)
finally:
setattr(owner_metaclass, self.name, self)
class Meta (type):
def __new__(mcls, name, bases, namespace):
post_init = {}
for key, value in list(namespace.items()):
if isinstance(getattr(mcls, key, None), AddQDesc):
post_init[key] = value
del namespace[key]
cls = super().__new__(mcls, name, bases, namespace)
for key, value in post_init.items():
setattr(cls, key, value)
return cls
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
If you don't need the value to live in the class' __dict__, I'd suggest just storing it elsewhere - a dictionary in the descriptor instance for example, having the classes as keys, will suffice - and will be far less weird.
class AddQDesc:
def __init__ (self, name):
self.name = name
self.storage = {}
def __get__ (self, instance, owner):
if not instance: return self
return self.storage[instance]
def __set__ (self, instance, value):
self.storage[instance] = value + 1
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.
I am trying to utilize python deque from collections module where the element inside the deque is a custom class instances. I want to know how I can erase/delete an object? Can I use the builtin methods like deque.remove(element) if yes, how? How would it find my custom object?
class Buffer(object):
""" """
def __init__(self, name, size_total, size_in_cache):
self.name = name
self.size_total = size_total
class Storage(object):
"""
"""
def __init__(self, capacity):
self.CAPACITY = capacity
self.contents = collections.deque()
self.capacity_used = 0
def push_to_contents(self, buffer_name, buffer_size):
buf = Buffer(buffer_name, buffer_size)
self.contents.appendleft(buf)
def delete_from_contents(self, buffer_name)
""" HOW??
How can I use self.contents.remove() here>
"""
The way collections.deque.remove operates is by comparing the argument to each item in the deque. If it finds something which is equal to the argument, it removes it. Otherwise, it raises a ValueError.
Since a Buffer object, as you've implemented it, doesn't know how to compare itself with other objects, Python defaults (using the object parent class) to comparing the id values.
However, if you were to implement the __eq__ method for your class, you could accomplish what you're looking for.
E.g.,
def __eq__(self, other):
if isinstance(other, Buffer):
return self.name == other.name and self.size_total == other.size_total
elif isinstance(other, str):
return self.name == other
else:
return NotImplemented
EDIT:
This is all fine and good if you're using Python 3. In Python 2, you have to implement __ne__ ("not equal") as well. It can be as simple as
def __ne__(self, other):
return not self == other
Python 3 takes care of this for you automatically.
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()