Is there a way in Python to prohibit class members from being set to None outside of __init__?
class Dummy:
def __init__(self, x):
if x is not None:
self.x = x
else:
raise Exception("x cannot be None")
d = Dummy("foo")
d.x = None
In my code I have type hints, but these are not enforced, so saying x can only be str does not really change anything in terms of what's allowed.
You'll need a #property:
class Dummy:
def __init__(self, x):
self.x = x
#property
def x(self):
return self._x
#x.setter
def x(self, value):
if value is None:
raise Exception("x cannot be None")
self._x = value
d = Dummy(8)
d.x = 16
d.x = None # Raises
Note: I have interpreted the question here as meaning preventing all attributes from being set to None outside of __init__, although have added below an option to protect just certain specified attributes.
How about this? Have __setattr__ method as wrapper to object.__setattr__.
class Dummy:
def __init__(self, x):
self._None_forbidden = False
self.z = None # this will work
self._None_forbidden = True
def __setattr__(self, k, v):
if v is None and self._None_forbidden:
raise ValueError("cannot set attribute to None")
object.__setattr__(self, k, v)
d = Dummy("foo")
print(d.z) # None
d.y = 2
print(d.y) # 2
d.x = None # raises ValueError
If you just wanted to protect certain attributes, you could make self._None_forbidden into a set of attribute names that are not allowed to be set to None. For example:
class Dummy:
def __init__(self, x):
self._None_forbidden = set()
self.z = None # this will work
self._None_forbidden.add("z")
def __setattr__(self, k, v):
if v is None and k in self._None_forbidden:
raise ValueError("cannot set attribute to None")
object.__setattr__(self, k, v)
d = Dummy("foo")
print(d.z) # None
d.y = 2
print(d.y) # 2
d.x = None
print(d.x) # None
d.z = 4
print(d.z) # 4
d.z = None # raises ValueError
Obviously if the caller manipulated _None_forbidden, then this can be circumvented, but then they should know that they are doing something unsupported.
Related
I have a class as follows:
class A:
def __init__(self):
pass
def add_attr(self, name):
setattr(self, name, 'something')
How do I define custom setter, getter for self.name? I cannot use __setattr__, __getattribute__ because that will change the behaviour of add_attr too.
EDIT: the users of this class will add arbitrary number of attributes with arbitrary names:
a = A()
a.add_attr('attr1')
a.add_attr('attr2')
I want custom behavior for only these user added attributes.
Building off #Devesh Kumar Singh’s answer, I would implement it in some way like this:
class A:
def __init__(self):
self.attrs = {}
def __setattr__(self, key, value):
if key in self.attrs:
self.set_attr(key, value)
else:
object.__setattr__(self, key, value)
def __getattribute__(self, key):
if key in self.__dict__.get(attrs, {}):
return self.__dict__['get_attr'](self, key)
return object.__getattribute__(self, key)
def get_attr(self, key):
r = self.attrs[key]
# logic
return r
def set_attr(self, key, value):
# logic
self.attrs[key] = value
def add_attr(self, key, value=None):
self.attrs[key] = value
add_attr is only used to initialise the variable the first time. You could also edit __setattr__ to set all new attributes in the self.attrs rather than self.__dict__
Custom getter and setter logic? That's what a property is made for. Usually these are used to magically mask function calls and make them look like attribute access
class MyDoubler(object):
def __init__(self, x):
self._x = x
#property
def x(self):
return x * 2
#x.setter
def x(self, value):
self._x = value
>>> md = MyDoubler(10)
>>> md.x
20
>>> md.x = 20
>>> md.x
40
>>> md._x
20
But there's no rule saying you can't abuse that power to add custom behavior to your getters and setters.
class A(object):
def __init__(self):
pass
#staticmethod
def default_getter_factory(name):
def default_getter(self):
return self.name
return default_getter
#staticmethod
def default_setter_factory(name):
def default_setter(self, value):
setattr(self, name, value)
return default_setter
def add_attr(self, name, getterfactory=None, setterfactory=None):
private_name = f"_{name}"
if getterfactory is None:
getterfactory = self.__class__.default_getter_factory
if setterfactory is None:
setterfactory = self.__class__.default_setter_factory
getter, setter = getterfactory(private_name), setterfactory(private_name)
getter = property(getter)
setattr(self.__class__, name, getter)
setattr(self.__class__, name, getter.setter(setter))
That said this is all a bit silly, and chances are that whatever it is you're trying to do is a thing that shouldn't be done. Dynamic programming is all well and good, but if I were to review code that did this, I would think very long and hard about alternative solutions before approving it. This reeks of technical debt to me.
One possibility I could think of is to have a dictionary of dynamic attributes, and set and get the dynamic attributes using the dictionary
class A:
def __init__(self):
#Dictionary of attributes
self.attrs = {}
#Set attribute
def set_attr(self, name):
self.attrs[name] = 'something'
#Get attribute
def get_attr(self, name):
return self.attrs.get(name)
a = A()
a.set_attr('var')
print(a.get_attr('var'))
The output will be something
Or an alternate is to use property decorator to add arguments explicitly outside the class, as described here
class A:
def __init__(self):
pass
a = A()
#Add attributes via property decorator
a.attr_1 = property(lambda self: self.attr_1)
a.attr_2 = property(lambda self: self.attr_2)
#Assign them values and print them
a.attr_1 = 4
a.attr_2 = 6
print(a.attr_1, a.attr_2)
The output will be 4 6
I am gonna answer my own question just for reference. This is based on others' answers here. The idea is to use default __setattr__ and __getattribute__ on attributes not added through add_attr.
class A:
def __init__(self):
self.attrs = {}
def add_attr(self, name):
self.attrs[name] = 'something'
def __getattribute__(self, name):
try:
object.__getattribute__(self, 'attrs')[name] # valid only if added by user
# custom logic and return
except (KeyError, AttributeError):
return object.__getattribute__(self, name)
def __setattr__(self, name, val):
# similar to __getattribute__
I'm curious on what might be a better pattern for creating a property attribute that initialises its value on 1st use. Below is a class with several variations on a theme.
def some_initializer(s):
return f"Value: {s}"
class Foo(object):
""" Initialize on first use properties """
def __init__(self):
self._prop1 = None
#property
def prop1(self):
""" Existing private member attribute """
if not self._prop1:
self._prop1 = some_initializer("prop1")
return self._prop1
#property
def prop2(self):
""" Create private attribute on demand """
if not hasattr(self, "_prop2"):
self._prop2 = some_initializer("prop2")
return self._prop2
#property
def prop3(self):
""" Create private attribute on demand - shorter """
self._prop3 = getattr(self, "_prop3", some_initializer("prop3"))
return self._prop3
#property
def prop4(self):
""" Stash value in attribute with same name as property """
_prop4 = self.__dict__.get('_prop4')
if _prop4 is not None:
return _prop4
self._prop4 = _prop4 = some_initializer("prop4")
return _prop4
>> f = Foo()
>> print(f.prop1)
>> print(f.prop2)
>> print(f.prop3)
>> print(f.prop4)
Value: prop1
Value: prop2
Value: prop3
Value: prop4
In the past I've used variations prop1, prop2 and prop3. Recently I was introduced to the prop4 variation that I feel is quite confusing although perhaps technically correct. Any cons with these variations or maybe there are better ways?
Edit: Ideally, it would be nice to maintain compatibility with property setter and deleter decorators too.
i would just write a custom descriptor and use that instead:
class cached_property:
def __init__(self, f):
self.f = f
def __get__(self, instance, owner):
if not instance:
return self
res = instance.__dict__[self.f.__name__] = self.f(instance)
return res
usage:
class C:
#cached_property
def prop(self):
print('you will see me once')
return 4
You could use functools.lru_cache to memoize the property value:
from functools import lru_cache
class Foo(object):
#property
#lru_cache()
def prop(self):
print("called once")
return 42
foo = Foo()
print(foo.prop)
print(foo.prop)
I also thought of a descriptor but came up with this approach
from weakref import WeakKeyDictionary
def five():
return 5
def six():
return 6
def seven():
return 7
class FirstInit:
def __init__(self, initializer):
self.initializer = initializer
self.data = WeakKeyDictionary()
def __get__(self, instance, owner):
try:
value = self.data[instance]
except KeyError as e:
value = self.initializer()
self.data[instance] = value
return self.data[instance]
Usage:
class F:
a = FirstInit(five)
b = FirstInit(six)
c = FirstInit(seven)
def __init__(self,name):
self.name = f'{name}:{self.c}'
>>> f = F('foo')
>>> f.name
'foo:7'
>>> f.a, f.b
(5, 6)
>>> f.a = 'sixteen'
>>> f.a, f.b
('sixteen', 6)
>>> f.b += 13
>>> f.a, f.b
('sixteen', 19)
>>>
For an initializer that takes an argument:
d = {'P1':5, 'P2':6, 'P3':7}
def initializer(which):
return d[which]
class FirstInit:
def __init__(self, initializer, prop):
self.initializer = initializer
self.prop = prop
self.data = WeakKeyDictionary()
def __get__(self, instance, owner):
try:
value = self.data[instance]
except KeyError as e:
value = self.initializer(self.prop)
self.data[instance] = value
return self.data[instance]
class G:
a = FirstInit(initializer, 'P1')
b = FirstInit(initializer, 'P2')
c = FirstInit(initializer, 'P3')
def __init__(self,name):
self.name = f'{name}:{self.c}'
...
>>> g = G('foo')
>>> g.name
'foo:7'
>>> g.b += 16
>>> g.a,g.b
(5, 22)
>>> g.a = 'four'
>>> g.a,g.b
('four', 22)
>>>
Is there a way to apply the same property logic to a set of attributes in a class? For example, I want to apply the same #attr1.setter decorator to attr2, attr3, and attr4 without having to define the property for each attribute.
class Sample:
def __init__(self):
self.attr1 = None
self.attr2 = None
self.attr3 = None
self.attr4 = None
#property
def attr1(self):
return self.__attr1
#attr1.setter
def attr1(self, val):
if val < 0:
self.__attr1 = 0
else:
self.__attr1 = val
Just create your own descriptor for this:
class MyDescriptor:
def __set_name__(self, owner, name):
self.name = f'_{name}'
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, val):
if val is None:
setattr(instance, self.name, None)
elif val < 0:
setattr(instance, self.name, 0)
else:
setattr(instance, self.name, val)
class Sample:
attr1 = MyDescriptor()
attr2 = MyDescriptor()
attr3 = MyDescriptor()
attr4 = MyDescriptor()
def __init__(self):
self.attr1 = None
self.attr2 = None
self.attr3 = None
self.attr4 = None
Now, in action:
In [3]: s = Sample()
In [4]: s.attr1 = -99
In [5]: s.attr1
Out[5]: 0
In [6]: s.attr2
In [7]: s.attr2 = 10
In [8]: s.attr2
Out[8]: 10
In [9]: s.attr2 = -1
In [10]: s.attr2
Out[10]: 0
See the Descriptor HOWTO and some more relevant documentation
Note, I incorporated the possibility of None in your setter logic (your code would have raised a TypeError on initialization of an instance, because the setter checks if None < 0). Also note, you probably don't want to be using double-underscore name-mangling (which doesn't mean private), so I used the conventional single-underscore to denote a variable not part of the public api. Using double-underscore name-mangling complicates things here.
You could override the __getattr__ and __setattr__ to behave the way you want them. This way you don't need to define any private variables nor initialize any of the member variables either.
class Sample:
def __getattr__(self, attr):
return self.__dict__.get(attr)
def __setattr__(self, attr, val):
if val is not None and val < 0:
self.__dict__[attr] = 0
else:
self.__dict__[attr] = val
s = Sample()
print(s.attr1) # None
s.attr1 = 10
print(s.attr1) # 10
s.attr1 = -10
print(s.attr1) # 0
s.attr1 = None
print(s.attr1) # None
Read the Python Cookbook and saw descriptors, particularly the example for enforcing types when using class attributes. I am writing a few classes where that would be useful, but I would also like to enforce immutability. How to do it? Type checking descriptor adapted from the book:
class Descriptor(object):
def __init__(self, name=None, **kwargs):
self.name = name
for key, value in kwargs.items():
setattr(self, key, value)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
# by default allows None
class Typed(Descriptor):
def __init__(self, expected_types=None, **kwargs):
self.expected_types = expected_types
super().__init__(**kwargs)
def __set__(self, instance, value):
if value is not None and not isinstance(value, self.expected_types):
raise TypeError('Expected: {}'.format(str(self.expected_types)))
super(Typed, self).__set__(instance, value)
class T(object):
v = Typed(int)
def __init__(self, v):
self.v = v
Attempt #1: add a self.is_set attribute to Typed
# by default allows None
class ImmutableTyped(Descriptor):
def __init__(self, expected_types=None, **kwargs):
self.expected_types = expected_types
self.is_set = False
super().__init__(**kwargs)
def __set__(self, instance, value):
if self.is_set:
raise ImmutableException(...)
if value is not None and not isinstance(value, self.expected_types):
raise TypeError('Expected: {}'.format(str(self.expected_types)))
self.is_set = True
super(Typed, self).__set__(instance, value)
Wrong, because when doing the following, ImmutableTyped is 'global' in the sense that it's a singleton throughout all instances of the class. When t2 is instantiated, is_set is already True from the previous object.
class T(object):
v = ImmutableTyped(int)
def __init__(self, v):
self.v = v
t1 = T()
t2 = T() # fail when instantiating
Attempt #2: Thought instance in __set__ refers to the class containing the attribute so tried to check if instance.__dict__[self.name] is still a Typed. That is also wrong.
Idea #3: Make Typed be used more similar to #property by accepting a 'fget' method returning the __dict__ of T instances. This would require the definition of a function in T similar to:
#Typed
def v(self):
return self.__dict__
which seems wrong.
How to implement immutability AND type checking as a descriptor?
Now this is my approach to the problem:
class ImmutableTyped:
def __set_name__(self, owner, name):
self.name = name
def __init__(self, *, immutable=False, types=None)
self.immutable == immutable is True
self.types = types if types else []
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if self.immutable is True:
raise TypeError('read-only attribute')
elif not any(isinstance(value, cls)
for cls in self.types):
raise TypeError('invalid argument type')
else:
instance.__dict__[self.name] = value
Side note: __set_name__ can be used to allow you to not specify the attribute name in initialisation. This means you can just do:
class Foo:
bar = ImmutableTyped()
and the instance of ImmutableTyped will automatically have the name attribute bar since I typed for that to occur in the __set_name__ method.
Could not succeed in making such a descriptor. Perhaps it's also unnecessarily complicated. The following method + property use suffices.
# this also allows None to go through
def check_type(data, expected_types):
if data is not None and not isinstance(data, expected_types):
raise TypeError('Expected: {}'.format(str(expected_types)))
return data
class A():
def __init__(self, value=None):
self._value = check_type(value, (str, bytes))
#property
def value(self):
return self._value
foo = A()
print(foo.value) # None
foo.value = 'bla' # AttributeError
bar = A('goosfraba')
print(bar.value) # goosfraba
bar.value = 'bla' # AttributeError
class ImmutableTyped(object):
def __set_name__(self, owner, name):
self.name = name
def __init__(self, *, types=None):
self.types = tuple(types or [])
self.instances = {}
return None
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
is_set = self.instances.setdefault(id(instance), False)
if is_set:
raise AttributeError("read-only attribute '%s'" % (self.name))
if self.types:
if not isinstance(value, self.types):
raise TypeError("invalid argument type '%s' for '%s'" % (type(value), self.name))
self.instances[id(instance)] = True
instance.__dict__[self.name] = value
return None
Examples:
class Something(object):
prop1 = ImmutableTyped(types=[int])
something = Something()
something.prop1 = "1"
Will give:
TypeError: invalid argument type '<class 'str'>' for 'prop1'
And:
something = Something()
something.prop1 = 1
something.prop1 = 2
Will give:
TypeError: read-only attribute 'prop1'
A read-only data descriptor is a descriptor that defines both __get__ and __set__, but __set__ raises AttributeError when called.
An example is a simple read-only property:
class Test():
_i = 1
#property
def i(self):
return self._i
assert hasattr(Test.i, '__get__')
assert hasattr(Test.i, '__set__')
t = Test()
t.i # 1
t.i = 2 # ERROR
If I have an instance of a class, I can determine if the instance attribute is a read-only data descriptor this way (although I don't like this at all):
def is_ro_data_descriptor_from_instance(instance, attr):
temp = getattr(instance, attr)
try:
setattr(instance, attr, None)
except AttributeError:
return True
else:
setattr(instance, attr, temp)
return False
If I know the class doesn't require any arguments to be instantiated, I can determine if its class attribute is a read-only data descriptor similar to the above:
def is_ro_data_descriptor_from_klass(klass, attr):
try:
setattr(klass(), attr, None)
except AttributeError:
return True
else:
return False
However, if I don't know the signature of the class ahead of time, and I try to instantiate a temporary object in this way, I could get an error:
class MyClass():
i = 1
def __init__(self, a, b, c):
'''a, b, and c are required!'''
pass
def is_ro_data_descriptor_from_klass(MyClass, 'i') # Error
What can be done to determine if a class attribute is a read-only data descriptor?
EDIT: Adding more information.
Below is the code I am trying to get working:
class StaticVarsMeta(type):
'''A metaclass that will emulate the "static variable" behavior of
other languages. For example:
class Test(metaclass = StaticVarsMeta):
_i = 1
#property
def i(self):
return self._i
t = Test()
assert t.i == Test.i'''
statics = {}
def __new__(meta, name, bases, dct):
klass = super().__new__(meta, name, bases, dct)
meta.statics[klass] = {}
for key, value in dct.items():
if "_" + key in dct:
meta.statics[klass][key] = set()
if hasattr(value, '__get__'):
meta.statics[klass][key].add('__get__')
if hasattr(value, '__set__'):
try:
value.__set__(None, None)
except AttributeError:
continue
else:
meta.statics[klass][key].add('__set__')
return klass
def __getattribute__(klass, attr):
if attr not in StaticVarsMeta.statics[klass]:
return super().__getattribute__(attr)
elif '__get__' not in StaticVarsMeta.statics[klass][attr]:
return super().__getattribute__(attr)
else:
return getattr(klass, '_' + attr)
def __setattr__(klass, attr, value):
if attr not in StaticVarsMeta.statics[klass]:
super().__setattr__(attr, value)
elif '__set__' not in StaticVarsMeta.statics[klass][attr]:
super().__setattr__(attr, value)
else:
setattr(klass, '_' + attr, value)
class Test(metaclass = StaticVarsMeta):
_i = 1
def get_i(self):
return self._i
i = property(get_i)
Note the following:
type(Test.i) # int
type(Test.__dict__['i']) # property
Test().i = 2 # ERROR, as expected
Test.i = 2 # NO ERROR - should produce an error
It seems super-awkward, but here's how you could implement it based on my comment:
class StaticVarsMeta(type):
statics = {}
def __new__(meta, name, bases, dct):
cls = super().__new__(meta, name, bases, dct)
meta.statics[cls] = {}
for key, val in dct.items():
if hasattr(val, '__get__') and hasattr(val, '__set__'):
meta.statics[cls][key] = {'__get__'}
try:
val.__set__(None, None)
except AttributeError as err:
if "can't set attribute" in err.args:
continue
meta.statics[cls][key].add('__set__')
return cls
In use:
>>> class ReadOnly(metaclass=StaticVarsMeta):
#property
def foo(self):
return None
>>> class ReadWrite(metaclass=StaticVarsMeta):
#property
def bar(self):
return None
#bar.setter
def bar(self, val):
pass
>>> StaticVarsMeta.statics
{<class '__main__.ReadOnly'>: {'foo': {'__get__'}},
<class '__main__.ReadWrite'>: {'bar': {'__get__', '__set__'}}}
This is more of a "starter for 10", there must be a better way to do it...
Your first solution can be made simpler and slightly more robust, by attempting to assign the value it already has. This way, no undoing is required (Still, this isn't thread-safe).
def is_ro_data_descriptor_from_instance(instance, attr):
temp = getattr(instance, attr)
try:
setattr(instance, attr, temp)
except AttributeError:
return True
else:
return False