A property's `fdel` method is not invoked when `__delattr__` is overridden - python

I have a class like this, in which I have declared a property x, and overridden __delattr__:
class B(object):
def __init__(self, x):
self._x = x
def _get_x(self):
return self._x
def _set_x(self, x):
self._x = x
def _del_x(self):
print '_del_x'
x = property(_get_x, _set_x, _del_x)
def __delattr__(self, name):
print '__del_attr__'
Now when I run
b = B(1)
del b.x
Only __del_attr__ get invoked, anybody knows why and how to solve this problem?

You have to call the __delattr__ of your ancestor to achieve this correctly.
class B(object):
.....
def __delattr__(self, name):
print '__del_attr__'
super(B, self).__delattr__(name) # explicit call to ancestor (not automatic in python)
And then running :
b = B(1)
del b.x
Will output:
__del_attr__
_del_x

_del_x is called from the default __del_attr__. But since you have overridden __del_attr__, the onus is on you to call _del_x from inside your __del_attr__.

Related

How to differentiate between a property being set with ___setattr__ from inside the class and outside the class?

Is there any way in __setattr__() to differentiate between an attribute set from inside the class or a child/inheriting class, and an attribute set from outside the current or a child class?
I want to change how setting attributes works from the "outside", in my case of making a module, I want the user to have different logic when setting a attribute than when it's set from inside the class.
For example:
i.x = 5 should assign 5 normally when called from within the class and i is a instance of it, but when called from another class it should, say, subtract 5 instead of set to 5.
A bit lowlevel, but you could use inspect module:
import inspect
class A:
def __init__(self):
self.__x = 0
#property
def x(self):
return self.__x
#x.setter
def x(self, value):
f = inspect.currentframe()
if 'self' in f.f_back.f_locals and issubclass(type(f.f_back.f_locals['self']), A):
print('Called from class!')
self.__x = -value
else:
print('Called from outside!')
self.__x = value
def fn(self):
print('Calling A.x from inside:')
self.x = 10
class B(A):
def __init__(self):
super().__init__()
def fn2(self):
print('Calling B.x from inside:')
self.x = 15
a = A()
print("A.x after init:", a.x)
print('Calling A.x from outside')
a.x = 10
print("A.x called from the outside:", a.x)
a.fn()
print("A.x called from the inside:", a.x)
b = B()
print("B.x after init:", b.x)
print('Calling B.x from outside')
b.x = 20
print("B.x called from the outside:", b.x)
b.fn2()
print("B.x called from the inside:", b.x)
Prints:
A.x after init: 0
Calling A.x from outside
Called from outside!
A.x called from the outside: 10
Calling A.x from inside:
Called from class!
A.x called from the inside: -10
B.x after init: 0
Calling B.x from outside
Called from outside!
B.x called from the outside: 20
Calling B.x from inside:
Called from class!
B.x called from the inside: -15
Use a property. Inside the class, you can assign directly to the underlying attribute. Outside, assignments to x decrement it instead.
class Foo:
def __init__(self):
self._x = 0
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x -= value
A solution may consist in always using self.__dict__ inside the class without calling the __setattr__ method.
Example:
class myClass:
def __init__(self, value):
self.__dict__['a'] = value
def __setattr__(self, name, value):
print("called from outside")
if name == 'a':
self.__dict__[name] = value - 5
else:
self.__dict__[name] = value
f = myClass(10)
print(f.a)
# 10
f.a = 20
print(f.a)
# called from outside
# 15

Class property returning property object

Before this is flagged as a duplicate, I know this question has been answered before, but the solutions provided there don't seem to apply to my case. I'm trying to programmatically set class properties. I know I can use property for that, so I thought about doing this:
class Foo:
def __init__(self, x):
self._x = x
def getx(): return self._x
def setx(y): self._x = y
self.x = property(fget=getx, fset=setx)
However, when I run this interactively, I get:
>>> f = Foo(42)
>>> f.x
<property object at 0x0000000>
>>> f._x
42
>>> f.x = 1
>>> f.x
1
Is there any way to solve this?
Edit:
I feel I may have left out too much, so here's what I am actually trying to reach. I have a class with a class variable called config, which contains configuration values to set as properties. The class should be subclassed to implement the config variable:
class _Base:
config = ()
def __init__(self, obj, **kwargs):
self._obj = obj()
for kwarg in kwargs:
# Whatever magic happens here to make these properties
# Sample implementation
class Bar(_Base):
config = (
"x",
"y"
)
def __init__(self, obj, x, y):
super().__init__(obj, x=x, y=y)
Which now allows for manipulation:
>>> b = Bar(x=3, y=4)
>>> b.x
3
>>> # Etc.
I'm trying to keep this as DRY as possible because I have to subclass _Base a lot.
property objects are descriptors, and descriptors are only invoked when defined on the class or metaclass. You can't put them directly on an instance; the __getattribute__ implementation for classes simply don't invoke the binding behaviour needed.
You need to put the property on the class, not on each instance:
class Foo:
def __init__(self, x):
self._x = x
#property
def x(self): return self._x
#x.setter
def x(self, y): self._x = y
If you have to have a property that only works on some instances, you'll have to alter your getter and setter methods to vary behaviour (like raise an AttributeError for when the state of the instance is such that the attribute should 'not exist').
class Bar:
def __init__(self, has_x_attribute=False):
self._has_x_attribute = has_x_attribute
self._x = None
#property
def x(self):
if not self._has_x_attribute:
raise AttributeError('x')
return self._x
#x.setter
def x(self, y):
if not self._has_x_attribute:
raise AttributeError('x')
self._x = y
The property object still exists and is bound, but behaves as if the attribute does not exist when a flag is set to false.

how to modify parent class variable with the child class and use in another child class in python

class A(object):
__A = None
def get_a(self):
return self.__A
def set_a(self, value):
self.__A = value
class B(A):
def method_b(self, value):
self.set_a(value)
class C(A):
def method_c(self)
self.get_a()
Someone can to explain me how can i to catch installed value in method_b inside my 'C' class method?
P.S. In this variant i just getting nothing.
Python isn't Java; you don't need setters & getters here: just access the attributes directly.
There are three problems with your code.
C.method_c() has no return statement, so it returns None.
You are using __ name mangling when that's exactly what you don't want.
In A.set_a() you want to set a class attribute, but your assignment instead creates an instance attribute which shadows the class attribute.
Here's a repaired version.
class A(object):
_A = 'nothing'
def get_a(self):
return self._A
def set_a(self, value):
A._A = value
class B(A):
def method_b(self, value):
self.set_a(value)
class C(A):
def method_c(self):
return self.get_a()
b = B()
c = C()
print(c.method_c())
b.method_b(13)
print(c.method_c())
output
nothing
13
Here's a slightly more Pythonic version:
class A(object):
_A = 'nothing'
class B(A):
def method_b(self, value):
A._A = value
class C(A):
pass
b = B()
c = C()
print(c._A)
b.method_b(13)
print(c._A)

Override a field in parent class with property in child class

Where I am now looks like this:
class A(object):
def __init__(self, val):
self.x=val
self.y=42
# other fields
class B(object):
def __init__(self):
self.a=22
# other fields
class C(A,B):
def __init__(self, val):
super(C,self).__init__(val)
#property
def x(self):
# if A.x is None return a value that I can compute from A.y and B.a
# if A.x is not None return it
#x.setter
def x(self, val):
# set the field value
Sometimes I just want to set an assumed value for x by hand, in which case I would just use an A. In other cases I want to use a more complicated approach that involves computing A.x's value on the basis of information that is organized into a B. The idea in this code is to make a C class that can look like an A (in terms of the x field) but doesn't need that field value to be set by hand, instead it just gets derived.
What I can't figure out is how to have the C.x property shadow the A.x field in a sensible way.
The line self.x = val in the A.__init__ method will simply invoke your C.x setter. You already have everything handled here. You are handling per instance attributes here, not attributes on a class that are inherited by subclasses.
All you need to do is to set a different attribute in the setter to represent the x value. You could name it _x, for example:
class C(A, B):
_x = None
#property
def x(self):
if self._x is not None:
return self._x
return self.a + self.y
#x.setter
def x(self, val):
self._x = val
Note that if all C.__init__ does is call super().__init__, you don't need it at all. However, you do need to make sure at least A.__init__() plays along in the inheritance structure; add in more calls to super().__init__():
class A(object):
def __init__(self, val, *args, **kwargs):
super(A, self).__init__(*args, **kwargs)
self.x = val
self.y = 42
class B(object):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.a = 22
Using *args and **kwargs allows these methods to pass on any extra arguments to other classes in the hierarchy.
Demo, using the above classes:
>>> c = C(None)
>>> c.x
64
>>> c.x = 15
>>> c.x
15

Python overriding setter to an attribute with no setter?

I have a class A made by someone else, that I cannot edit:
class A:
def __init__(self, x):
self.x = x
Now I'm trying to inherit my own class B from A, and have x as a property instead of an attribute.
Is this possible?
I already tried:
class B(A):
def __init__(self, x):
super().__init__(x)
#property
def x(self):
return super().x
#x.setter
def x(self, x):
super().x = x
print(x) # my stuff goes here
But as I expected, it's not possible: AttributeError: 'super' object has no attribute 'x'
Is there any other method, some workaroud maybe?
No, you cannot use super() for anything other than class attributes; x is an instance attribute, and there is no inheritance mechanism for attributes.
Instance attributes live in a single namespace; there is no 'parent instance' attribute namespace.
You can still reach the attribute in the instance __dict__ object:
class B(A):
#property
def x(self):
return self.__dict__['x']
#x.setter
def x(self, x):
self.__dict__['x'] = x
print(x) # my stuff goes here
A property is a data descriptor, meaning that it is looked up before the instance attributes are consulted (see the descriptor howto), but you can still access it directly.

Categories

Resources