I am working on a python class that has declared properties, and in which I want to add extra attributes at object instanciation (passed in the init method).
I want them to be read and written.
Finally, I don't want the user to be able to declare custom attributes; it should raise an Error.
class Person:
__slots__ = ["_name", "__dict__"]
def __init__(self, name, extra_arg):
self.__dict__[extra_arg] = None
self._name = name
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
def __getattr__(self, item):
if item in self.__dict__:
return self.__dict__[item]
raise AttributeError(item)
person = Person("gribouille", "hello")
person.custom_attribute = value # I want to prevent this
In this example, I can't manage to prevent new attributes to be declared.
When I override setattr method, it seems to collide with my property and I can't manage to retrieve my "name" attribute.
How about checking for existing attributes via hasattr and __slots__?
class Person:
__slots__ = ["_name", "__dict__"]
def __init__(self, name, extra_arg):
self.__dict__[extra_arg] = None
self._name = name
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
def __getattr__(self, item):
if item in self.__dict__:
return self.__dict__[item]
raise AttributeError(item)
def __setattr__(self, attr_name, attr_value):
if not (hasattr(self, attr_name) or attr_name in self.__slots__):
raise AttributeError(attr_name)
super().__setattr__(attr_name, attr_value)
person = Person("gribouille", "hello")
person.name = "test"
person.custom_attribute = None # Now: AttributeError: custom_attribute
person.custom_attribute = value # I want to prevent this
To achieve this your class should do NOT have __dict__ attribute, that is __slots__ must not contain __dict__. Consider following simple example
class C1:
__slots__ = ["__dict__"]
class C2:
__slots__ = ["x","y"]
c1 = C1()
c1.custom = "hello"
print(c1.custom) # hello
c2 = C2()
c2.x = 10
c2.y = 30
print(c2.x,c2.y) # 10 30
c2.z = 100 # cause AttributeError: 'C2' object has no attribute 'z'
Related
class C():
#property
def x(self):
return 0
delattr(C(), 'x')
>>> AttributeError: can't delete attribute
I'm aware del C.x works, but this deletes the class's property; can a class instance's property be deleted?
Refer to this answer; TL;DR, it's not about properties, but bound attributes, and x is bound to the class, not the instance, so it cannot be deleted from an instance when an instance doesn't have it in the first place. Demo:
class C():
pass
#property
def y(self):
return 1
c = C()
c.y = y
del c.y # works
c.y
>>> AttributeError: 'C' object has no attribute 'y'
I'm aware del C.x works, but this deletes the class's property; can a class instance's property be deleted?
There's no such thing. Properties are defined on the class, there is nothing on the instance in the example you provide. It's like a method, a method is an attribute of the class which Python execute in the context of the instance.
I got the same error below:
AttributeError: can't delete attribute
When trying to delete the instance variable name with del as shwon below:
class Person:
def __init__(self, name):
self._name = name
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
obj = Person("John")
print(hasattr(obj, "name"))
del obj.name # Here
print(hasattr(obj, "name"))
So, I added #name.deleter method as shown below:
class Person:
def __init__(self, name):
self._name = name
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
#name.deleter # Here
def name(self):
del self._name
obj = Person("John")
print(hasattr(obj, "name"))
del obj.name # Here
print(hasattr(obj, "name"))
Then, I could delete the instance variable name with del as shown below:
True
False
You can do something like this to delete attr from instance.
https://stackoverflow.com/a/36931502/12789671
class C:
def __init__(self):
self._x: int = 0
#property
def x(self):
return self._x
#x.deleter
def x(self):
delattr(self, "_x")
obj = C()
delattr(obj, "x")
try:
print(obj.x)
except AttributeError:
print("failed to print x")
print(C().x)
failed to print x
0
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__
How can I set a property attribution from child class to a property attribution of parent class? for attribution, I know I can do something like
setattr(self.name, 'nickname', object). However, if I have one class like Animal that is inherited by Bird and include one property called name. Is it possible for me to create another property
under name for class Bird?
class Animal:
def __init__(self):
self._name = None
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
class Bird(Animal):
def __init__(self):
super().__init__()
# I need to create the other property under name attribution from Animal class as nickname
#so I can access as cat.name.nickname = 'i am nickname'
#print(cat.name.nickname) # 'i am nickname
##property
#def nickname(self):
# return self._name
#
##name.setter
#def name(self, value):
# self._name = value
cat = Animal()
cat.name = 'i am cat'
print(cat.name) # i am cat
Properties getters and setters can call the property methods on the superclass, with super -
This mean you can recreate the name property in the subclass, retrieve the super-class value, for compatibility, and wrap it on another class, which has the attributes you want.
However, the key _name would be taken in the instance dictionary to keep the value Animal.name property knows about - so we need another shadow name in the instance to keep the values for exclusive of the subclass.
That said, it is still needed to build a clever class that can wrap the original value of the property on the superclass, and know how to handle attribute setting and retrieval on the subclass - the Wrapper code bellow can do that:
class Wrapper(str):
def __new__(cls, original_str, *args):
return super().__new__(cls, original_str)
def __init__(self, original_str, name_in_parent, parent):
self._name = name_in_parent
self._parent = parent
# original_str is taken care of in `__new__`
def __setattr__(self, attrname, value):
if attrname.startswith("_"):
return super().__setattr__(attrname, value)
ns = getattr(self._parent, self._name, None)
if ns is None:
ns = {}
setattr(self._parent, self._name, ns)
ns[attrname] = value
def __getattr__(self, attrname):
return getattr(self._parent, self._name)[attrname]
And this will work with a simple property on the superclass like:
class Animal:
#property
def name(self):
return self._name
#name.setter
def name(self, value):
# just so that the property is not 100% meaningless
self._name = value.lower()
class Bird(Animal):
#property
def name(self):
return Wrapper(super().name, "_bird_name", self)
#name.setter
def name(self, value):
# this turned out to be the trickiest part - to retrieve
# the original property on the superclass so that we can
# call it's setter. `super()` did not work for this.
# We set just the core value - the specialized class
# with more attributes is only used upon reading the property back
super_property = [getattr(val, "name") for val in a.__class__.__mro__[1:] if hasattr(val, "name")][0]
super_property.__set__(self, value)
And this working:
In [511]: b = Bird()
In [512]: b.name = "Woodpecker"
In [513]: b.name
Out[513]: 'woodpecker'
In [514]: b.name.nickname = "Woody"
In [515]: b.__dict__
Out[515]: {'_name': 'woodpecker', '_bird_name': {'nickname': 'Woody'}}
In [516]: b.name.nickname
Out[516]: 'Woody'
If you want to restrict the accepted sub-attributes, just use plain if statements in Wrapper.__setattr__.
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'
Is there a simple method for finding all of the descriptors in a class type? If I have this code:
class Descriptor(object):
def __init__(self):
self._name = ''
def __get__(self, instance, owner):
print "Getting: %s" % self._name
return self._name
def __set__(self, instance, name):
print "Setting: %s" % name
self._name = name.title()
def __delete__(self, instance):
print "Deleting: %s" %self._name
del self._name
class ContrivedExample(object):
Name = Descriptor()
Date = Descriptor()
How do I find out that ContrivedExample.Name and ContrivedExample.Date exist from outside the class?
You can iterate through the class' __dict__ property, which stores all of the defined names within the class, and check the type of the value for each name.
descriptors = [m for m,v in ContrivedExample.__dict__.iteritems()
if isinstance(v, Descriptor)]
# descriptors is set to ['Name', 'Date']