Discriminate between callers inside and outside class hierarchy - python

I have two classes A and B, where B inherits from A and overrides a property. A is not under my control so I cannot change it.
The code looks as follows:
class A():
def __init__(self, value):
self.value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
raise AttributeError("can't set attribute")
When I try to call B(1) I obviously get AttributeError: can't set attribute.
I would like to have a different behaviour when value is set from inside class methods
#value.setter
def value(self, value):
if set from inside class hierarchy:
pass
else:
raise AttributeError("can't set attribute")
The module inspect does not seem to give me enough information to do this, except checking against a list of known functions.

You can inspect the stack to determine who called, and whether that it's in the class hierarchy to decide whether or not to allow it:
import inspect
def who_called():
frame = inspect.stack()[2][0]
if 'self' not in frame.f_locals:
return None, None
cls = frame.f_locals['self'].__class__
method = frame.f_code.co_name
return cls, method
class A(object):
def __init__(self, value):
self._value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
# Assuming this existed it would also work
def change_value(self, value):
self.value = value
Class B now checking:
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
cls, method = who_called()
if cls in B.__mro__ and method in A.__dict__:
self._value = value
else:
raise AttributeError("can't set attribute")
Proof:
b = B('does not raise error')
b.change_value('does not raise error')
b.value = 'raises error'

You could use the code that made the call to determine whether the call came from inside the class. Only throw an exception if the call didn't start with self.value =.
import re
import traceback
class A(object):
def __init__(self, value):
self.value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
call = traceback.extract_stack(limit=2)[0][3]
if re.match(r'self.value\s*=', call):
pass
else:
raise AttributeError("can't set attribute")
b = B(1) # OK
b.value = 3 # Exception
Of course, this breaks as soon as you start calling your variables self:
self = B(1) # OK
self.value = 3 # Meh, doesn't fail

Related

How to declare instance variables in abstract class?

class ILinkedListElem:
#property
def value(self):
raise NotImplementedError
#property
def next(self):
raise NotImplementedError
class ListElem(ILinkedListElem):
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
I wanna something like this. This abstract variables definition works for class vars, but not for instance
I want to all instances of ILinkedListElem subclass must has "value" and "next" attributes
If you want to force/require all instances of any subclass of ILinkedListElem to have the attributes "value" and "nxt", the following standard implementation with abstractmethod seems to do what you're after:
from abc import ABC, abstractmethod
class ILinkedListElem (ABC):
#property
#abstractmethod
def value(self):
raise NotImplementedError
#property
#abstractmethod
def nxt(self):
raise NotImplementedError
This is the abstract class, from which we create a compliant subclass:
class ListElem_good (ILinkedListElem):
def __init__(self, value, next_node=None):
self._value = value
self._nxt = next_node
#property
def value(self):
return self._value
#property
def nxt(self):
return self._nxt
We create an instance of this compliant subclass and test it:
x = ListElem_good('foo', 'bar')
print (x.value)
print (x.nxt)
#result:
# foo
# bar
If we create a non-compliant subclass that omits an implementation of nxt, like so:
class ListElem_bad (ILinkedListElem):
def __init__(self, value):
self._value = value
#property
def value(self):
return self._value
when we try to create an instance of this non-compliant subclass:
y = ListElem_bad('foo')
print (y.value)
it fails:
y = ListElem_bad('foo')
TypeError: Can't instantiate abstract class ListElem_bad with abstract methods nxt
This relies on essentially the same solution offered here, which you suggested in a comment-exchange does not meet your requirements. But when applied to your specific use-case above, it appears to precisely address the issue you've raised - or have I misunderstood?

How to use __setitem__ properly?

I want to make a data object:
class GameData:
def __init__(self, data={}):
self.data = data
def __getitem__(self, item):
return self.data[item]
def __setitem__(self, key, value):
self.data[key] = value
def __getattr__(self, item):
return self.data[item]
def __setattr__(self, key, value):
self.data[kay] = value
def __repr__(self):
return str(self.data)
When I create a GameData object, I get RecursionError. How can I avoid setitem recall itself?
In the assignment self.data = data, __setattr__ is called because self has no attribute called data at the moment. __setattr__ then calls __getattr__ to obtain the non-existing attribute data. __getattr__ itself calls __getattr__ again. This is a recursion.
Use object.__setattr__(self, 'data', data) to do the assignment when implementing __setattr__.
class GameData:
def __init__(self, data=None):
object.__setattr__(self, 'data', {} if data is None else data)
def __getitem__(self, item):
return self.data[item]
def __setitem__(self, key, value):
self.data[key] = value
def __getattr__(self, item):
return self.data[item]
def __setattr__(self, key, value):
self.data[key] = value
def __repr__(self):
return str(self.data)
For details, see the __getattr__ manual
Additionally, do not use mutable objects as default parameter because the same object {} in the default argument is shared between GameData instances.

generating function based off class field?

I have a class which has fields that would all be properties with pass through getters and setters that are validated in a certain way, such that it would satisfy the following pattern:
import numpy as np
import typing
def validate_field(value, dtype: typing.Type):
limits = np.iinfo(dtype)
assert limits.min < value < limits.max, \
"value shoule be in range: {} < {} < {}".format(limits.min, value,
limits.max)
return value
class Foo:
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
#property
def a(self):
return self._a
#property
def b(self):
return self._b
#property
def c(self):
return self._c
#a.setter
def a(self, value):
self._a = validate_field(value, self._a.dtype)
#b.setter
def b(self, value):
self._b = validate_field(value, self._b.dtype)
#c.setter
def c(self, value):
self._c = validate_field(value, self._c.dtype)
I want to eliminate having to type a separate property and setter decorator for each method.
I thought about using properties manually via
self._a = a
def set_a(self, value):
self._a = validate_field(value, self._a.dtype)
self.a = property(lambda self: self._a, set_a)
...
However, it seemed I would still have to manually define a function that accessed the required member for both setter and getter, so I wasn't really saving much work.
If there was a way to automatically generate such functions via naming the parameter e.g.:
def generate_function(self, parameter)
def temp(self, value):
self.parameter = validate_field(value, self.parameter.dtype)
return temp
then I wouldn't have any issues, but right now I don't see how to accomplish this.
Is there a way for me to generate these functions with a single decorator per field or automated function based property generation in __init__?
You can use getattr() and setattr(), or direct dictionary access via self.__dict__, to parametrize the attribute name:
def validated_property(name):
def getter(self):
return getattr(self, name)
def setter(self, value):
dtype = getter(self).dtype
setattr(self, name, validate_field(value, dtype))
return property(getter, setter)
then use this as
class Foo:
# ...
a = validated_property('_a')
b = validated_property('_b')
c = validated_property('_c')
etc.
If you are using Python 3.6 or newer, you can avoid having to repeat the attribute name and generate one from the name for the property (by prefixing it with _, for example), by implementing your own descriptor object, which is passed the name under which it is being assigned to a class via the descriptor.__set_name__() method:
class ValidatedProperty:
_name = None
def __set_name__(self, owner, name):
self._name = '_' + name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self._name)
def __set__(self, instance, value):
dtype = self.__get__(instance, type(instance)).dtype
setattr(instance, self._name, validate_field(value, dtype))
then use this like this:
class Foo:
# ...
a = ValidatedProperty()
b = ValidatedProperty()
c = ValidatedProperty()

Property setter not working in Python class

I am using a class (MainClass) over which I have no control. I want to base my class on MainClass but to add extra functionality. I have added an attribute (index) to my class (SuperClass), but when I try convert index to a property, the #.setter seems to be ignored. What is wrong here?
class MainClass(object):
def __init__(self):
self.name = 'abc'
class SuperClass(object):
def __init__(self, main, *args, **kwargs):
super(SuperClass, self).__init__(*args, **kwargs)
self.__main = main
self._index = 0
def __getattr__(self, attr):
return getattr(self.__main, attr)
def __setattr__(self, attr, val):
if attr == '_SuperClass__main':
object.__setattr__(self, attr, val)
return setattr(self.__main, attr, val)
#property
def index(self):
return self._index
#index.setter
def index(self, value):
self._index = value
main_object = MainClass()
super_object = SuperClass(main_object)
print('x', super_object.index, super_object.name)
super_object.index = 3
print('y', super_object.index)
super_object.index += 2
print('z', super_object.index)
__getattr__ is only used when the normal lookup mechanism fails.
__setattr__, however, is called for all attempts to set an attribute. This means your current definition creates an attribute named index on the
MainClass instance, rather than accessing the property's setter.
>>> super_object._SuperClass__main.index
2
Because __setattr__ always calls setattr(self.__main, attr, val), += is effectively treated as =.
__setattr__ has to handle three cases:
The attribute _SuperClass__main itself, for when you assign to self.__main in __init__.
Assignments to attributes that exist on self.__main
Assignments to attributes specific to SuperClass.
With that in mind, try
def __setattr__(self, attr, val):
if attr == '_SuperClass__main':
super().__setattr__(attr, val)
elif hasattr(self.__main, attr):
setattr(self.__main, attr, val)
else:
super().__setattr__(attr, val)
The __setattr__ method you have defined is taking precedence over the #index.setter
Simplify the code and it should work:
class MainClass(object):
def __init__(self):
self.name = 'abc'
class SuperClass(object):
def __init__(self, main, *args, **kwargs):
super(SuperClass, self).__init__(*args, **kwargs)
self.__main = main
self._index = 0
#property
def name(self):
return self.__main.name
#name.setter
def name(self):
return self.__main.name
#property
def index(self):
return self._index
#index.setter
def index(self, value):
self._index = value
main_object = MainClass()
super_object = SuperClass(main_object)
print('x', super_object.index, super_object.name)
super_object.index = 3
print('y', super_object.index)
super_object.index += 2
print('z', super_object.index)
Output:
x 0 abc
y 3
z 5
I would also suggest the simpler option of just inheriting from MainClass instead of using composition and delegation:
class SuperClass(MainClass):
def __init__(self):
super().__init__()
self._index = 0
#property
def index(self):
return self._index
#index.setter
def index(self, value):
self._index = value

how to create multiple property decorators in python

How can I create multiple property decorators with self defined function as getter and setter based on following class structure? I have try to use
setattr(self, 'a', property(_to_get('a'), _to_set('a'))) but it does not work.
class ABC:
def __init__(self):
pass
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
#property
def a(self):
res = self._to_get('a')
return res.split(' ')[0]
#a.setter
def a(self, value)
self._to_set('a', value)
#property
def b(self):
res = self._to_get('b')
return res.split(' ')[1]
#b.setter
def b(self, value)
self._to_set('b', value)
#property
def c(self):
res = self._to_get('c')
return res.split(' ')[2]
#c.setter
def c(self, value)
self._to_set('c', value)
No reason why something like this wouldn't work:
class A(object):
def __init__(self):
self._a = None
#property
def a(self):
return self._a
#a.setter
def a(self, x):
self._a = x
#a.deleter
def a(self):
del self._a
#property
def b(self):
return self._b
#b.setter
def b(self, x):
self._b = x
#b.deleter
def b(self):
del self._b
#property
def c(self):
return self._c
#c.setter
def c(self, x):
self._c = x
#c.deleter
def c(self):
del self._c
Consider your original class written without decorator syntax. (The translation may not be 100% accurate, but should be close enough to illustrate the point I want to make.)
class ABC:
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
a = property(lambda self: ABC._to_get(self, 'a').split(' ')[0],
lambda self, value: ABC._to_set(self, 'a', value))
b = property(lambda self: ABC._to_get(self, 'b').split(' ')[1],
lambda self, value: ABC._to_set(self, 'b', value))
c = property(lambda self: ABC._to_get(self, 'c').split(' ')[2],
lambda self, value: ABC._to_set(self, 'c', value))
a, b and c are all basically the same thing, but parameterized
by the name of the property and an integer.
def make_getter(attr, x):
def getter(self):
return self._to_get(attr).split(' ')[x]
return getter
def make_setter(attr):
def setter(self, value):
self._to_set(attr, value)
return setter
class ABC:
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
a = property(make_getter('a', 0), make_setter('a'))
b = property(make_getter('b', 1), make_setter('b'))
c = property(make_getter('c', 2), make_setter('c'))
Something like the following should also work (not heavily tested), moving the logic into a subclass of property.
class Foo(property):
def __init__(self, x):
super().__init__(self._to_get, self._to_set)
self.x = x
# name is the class attribute the instance of Foo
# will be assigned to
def __set_name__(self, owner, name):
self.attr = name
# In both of the following, obj is the instance that actually
# invokes the parameter. You would probably want to pass it
# to something_function and do_something as well.
def _to_get(self, obj):
return something_function(self.attr).split(' ')[self.x]
def _to_set(self, obj, value):
do_something(self.attr, value)
class ABC:
a = Foo(0) # Will call a.__set_name__(ABC, 'a')
b = Foo(1) # Will call b.__set_name__(ABC, 'b')
c = Foo(2) # Will call c.__set_name__(ABC, 'c')

Categories

Resources