Explain the use of __setattr__ - python

Can you please explain the use of the __setattr__ method in the below code :
class Human(object):
def __setattr__(self,name,value):
if name == 'gender':
if value in ('male', 'female'):
self.gender = value
else :
raise AttributeError('Gender can only be Male or Female')
h = Human()
h.name = 'Mary'
h.gender = 'female'
print h.gender

In your code, __setattr__() function is making a validation that while assigning the value of self.gender, it's value should be only between male and female. In case of any other value, it will raise AttributeError exception.
Note: In your __setattr__() function, there is no call to super of __setattr__, and hence this function is not actually updating the properties of class. You should add below line in your __setattr__() in order to make it work:
super(Human, self).__setattr__(name, value)
Overview about .__setattr__(self, name, value):
Whenever you assign any value to the property of the class, __setattr__(self, name, value) function is called where name is the property of class and value is the new value to be assigned. Below is the sample code to demonstrate that:
>>> class MyClass(object):
... def __init__(self, x, y):
... self.x = x
... self.y = y
... def __setattr__(self, name, value):
... print 'In Set Attr for: ', name, ', ', value
... super(MyClass, self).__setattr__(name, value)
...
...
>>> my_object = MyClass(1, 2)
In Set Attr for: x, 1
In Set Attr for: y, 2
>>> my_object.x = 10 # <-- Called while assigning value x
In Set Attr for: x, 10

Related

python 3.8 - dynamically set properties to a class when it's initialized

I'm trying to create a class whose instances can have an arbitrary number of attributes. This can be easily achieved using **kwargs in the init method. Then, for each of its attributes I need to count the access throughout the entire lifecycle of an instance.
If I had a property for each attributes, then I could increment a counter in both getter and setter methods for that attribute accessed at a given time.
The problem is that I need to create those properties dynamically, because I don't know which attributes an instance of my class will have.
I'm sure this can be solved by mean of metaclasses. Although, I'm trying to address that without them.
I've written this code so far:
class CountAttr:
def __init__(self, **kwargs):
# here I init the counter for each keyword argument
self.__count = {attr: 0 for attr in kwargs}
# then I add the attribute to my instance
for kwarg in kwargs:
object.__setattr__(self, '_' + kwarg, kwargs[kwarg])
def getter(self):
self.__count[kwarg] +=1
return object.__getattribute__(self, '_' + kwarg)
def setter(self, value):
self.__count[kwarg] +=1
object.__setattr__(self, '_' + kwarg, kwargs[kwarg], value)
#here comes the issue: the property doesn't work
object.__setattr__(self, kwarg, property(getter, setter))
#property
def count(self):
return self.__count
test = CountAttr(x=5, y=6)
print(test.x, test.y)
Debugging my code, I've assessed that test has attribute _x with value of 5, as well as attribute _y with value of 6. x and y are shown as property objects, so everything seems to work fine. But it doesn't at the end.
Indeed, the final print statement will output this:
<property object at 0x7fbb241d4b30> <property object at 0x7fbb241d4b80>
While the output I'm expecting is: 5 6, that is, the value of _x and _y attributes.
All attribute access in an object in Python, regardless of how the attribute is actually stored in memory (or even if it is computed), goes trough two special methods in the class __setattr__ and __getattribute__.
That is it - just put your counter code in those two methods, use a separate name as instance attribute to store the counter data itself, and go on - no need for fiddling with a metaclass. All you need is a base (or mixin) class with this counter code. If you don't want the initial setup of the arguments to be counted, you can just start the count from "-1" for writting, for example.
class CounterBase:
def __init__(self, **kwargs):
self._counters = {}
for name, value in kwargs.items():
setattr(self, name, value)
super().__init__()
def __getattribute__(self, name):
if name != "_counters":
_counters = self._counters
_counters[name] = _counters.get(name, 0) + 1
return super().__getattribute__(name)
def __setattr__(self, name, value):
if name != "_counters":
_counters = self._counters
_counters[name] = _counters.get(name, 0) + 1
return super().__setattr__(name, value)
If you are willing to use globals, you can do this with a global dict:
_count = {}
class CountAttr:
def __init__(self, **kw):
for i, j in kw.items():
object.__setattr__(self, i, j)
global _count
_count = {i: 0 for i in kw.keys()}
def __getattribute__(self, key):
global _count
if key in _count:
_count[key] += 1
return object.__getattribute__(self, key)
def __setattr__(self, key, val):
global _count
_count[key] += 1
return object.__setattr__(self, key, val)
#property
def count(self):
return _count # expose count dict interface
Usage:
>>> test = CountAttr(x=5, y=6)
>>> test.x
5
>>> test.y
6
>>> test.count['x']
1
>>> test.count['y']
1
Following with #jsbueno's answer, you can set count to the instance as long as you're careful with ordering in __init__:
class CountAttr:
def __init__(self, **kw):
self.count = {i: 0 for i in kw.keys()} # If <- isn't first
for i, j in kw.items(): # you will get an
setattr(self, i, j) # `AttributeError`.
super().__init__()
def __getattribute__(self, key):
if key != 'count':
counter = self.count
counter[key] += 1
return super().__getattribute__(key)
def __setattr__(self, key, val):
if key != 'count':
counter = self.count
counter[key] += 1
return super().__setattr__(key, val
As #Wups noted, setting class attribute instead of instance attribute helps the situation. Here they show an example, how to set encapsulated attribute using property() function. value is a class attribute, value of which is what property() returns. So, in your loop you could create this class attribute passing property() with proper setter and getter as a value. I'd also not declare setters and getter for each kwarg repetitively in a loop, as you can declare them once, but as the name for each kwarg differs, you can encapsulate them in outer functions, which will take this name as a parameter, and then return a function with a proper name of attribute set.
This example seems to be working, as you want it to:
class SomeClass:
def __init__(self, **kwargs):
for item in kwargs:
setattr(SomeClass, item, property(self.getter(item),
self.setter(item, kwargs[item])))
setattr(self, '__'+item, kwargs[item])
def setter(self, name: str, value):
def inner_setter(self, value):
setattr(self, '__'+name, value)
return inner_setter
def getter(self, name: str):
def inner_getter(self):
return getattr(self, '__'+name)
return inner_getter
inst = SomeClass(r=2, s=4)
print(inst.r, inst.s)
Output:
2 4

How do I define setter, getter for dynamically added attributes

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__

Can I create a singleton static class variable in python?

I have the following scenario:
class A:
b = 1
pass
x = A()
y = A()
Can I change this class so that x.b = 2 is equivalent to A.b = 2, I mean, when a change the static variable for one instance it´s changed for all instances?
Edit: I want to be able to work with multiple different instances of this class.
You can, but it's kind of ugly:
class A:
b = 1
def __setattr__(self, name, value):
if name == "b":
setattr(A, "b", value)
else:
super().__setattr__(name, value)
This would work as expected now:
>>> a = A()
>>> a.b = 3
>>> A.b
3
>>> A.b = 5
>>> a.b
5
The real question is: Why would you want that?
If you're going to use this often, it might be nice to write a decorator for it:
def sharedclassvar(variable):
def __setattr__(self, name, value):
if name in self.__class__._sharedclassvars:
setattr(self.__class__, name, value)
elif hasattr(self.__class__, "__oldsetattr__"):
self.__class__.__oldsetattr__(self, name, value)
else:
super().__setattr__(name, value)
def decorator(cls):
if not hasattr(cls, "_sharedclassvars"):
cls._sharedclassvars = []
if hasattr(cls, "__setattr__"):
cls.__oldsetattr__ = getattr(cls, "__setattr__")
cls.__setattr__ = __setattr__
cls._sharedclassvars.append(variable)
return cls
return decorator
You can then define such a class like this:
#sharedclassvar("b")
class A:
b = 1

How to write metaclass which would prevent creating new attributes after __init__()?

Currently I override the class' __setattr__() towards the end of the class' __init__() method to prevent new attribute creation -
class Point(object):
def __init__(self):
self.x = 0
self.y = 0
Point.__setattr__ = self._setattr
def _setattr(self, name, value):
if not hasattr(self, name):
raise AttributeError("'" + name + "' not an attribute of Point object.")
else:
super(Point, self).__setattr__(name, value)
Is there a way to avoid manually overriding __setattr__() and do this automatically with the help of metaclasses?
The closest I came was -
class attr_block_meta(type):
def __new__(meta, cname, bases, dctry):
def _setattr(self, name, value):
if not hasattr(self, name):
raise AttributeError("'" + name + "' not an attribute of " + cname + " object.")
object.__setattr__(self, name, value)
dctry.update({'x': 0, 'y': 0})
cls = type.__new__(meta, cname, bases, dctry)
cls.__setattr__ = _setattr
return cls
class ImPoint(object):
__metaclass__ = attr_block_meta
Is there a more generic way of doing this such that apriori knowledge of the subclass attributes is not required?
Basically, how to avoid the line dctry.update({'x': 0, 'y': 0}) and make this work irrespective of what the names of class attributes are?
P.S. - FWIW I have already evaluated the __slots__ and namedtuple options and found them lacking for my needs. Please don't narrow your focus to the pared down Points() example that I have used to illustrate the question; the actual use case involves a far more complex class.
Don't reinvent the wheel.
Two simple ways to achieve that (without directly using a metaclass) are using:
namedtuples
__slots__
For example, using namedtuple (based on the example in the docs):
Point = namedtuple('Point', ['x', 'y'])
p = Point(11, 22)
p.z = 33 # ERROR
For example, using __slots__:
class Point(object):
__slots__ = ['x', 'y']
def __init__(self, x=0, y=0):
self.x = x
self.y = y
p = Point(11,22)
p.z = 33 # ERROR
Would this make sense for your case?
from functools import wraps
class attr_block_meta(type):
def __new__(meta, cname, bases, dctry):
def _setattr(self, name, value):
if not hasattr(self, name):
raise AttributeError("'" + name + "' not an attibute of " + cname + " object.")
object.__setattr__(self, name, value)
def override_setattr_after(fn):
#wraps(fn)
def _wrapper(*args, **kwargs):
cls.__setattr__ = object.__setattr__
fn(*args, **kwargs)
cls.__setattr__ = _setattr
return _wrapper
cls = type.__new__(meta, cname, bases, dctry)
cls.__init__ = override_setattr_after(cls.__init__)
return cls
class ImPoint(object):
__metaclass__ = attr_block_meta
def __init__(self, q, z):
self.q = q
self.z = z
point = ImPoint(1, 2)
print point.q, point.z
point.w = 3 # Raises AttributeError
See this for more details on 'wraps'.
You probably need to fiddle a little bit more with it to get it more elegant, but the general idea is to override __setattr__ only after init is called.
Having said that, a common approach to this is just to use object.__setattr__(self, field, value) internally to bypass the AttributeError:
class attr_block_meta(type):
def __new__(meta, cname, bases, dctry):
def _setattr(self, name, value):
if not hasattr(self, name):
raise AttributeError("'" + name + "' not an attibute of " + cname + " object.")
object.__setattr__(self, name, value)
cls = type.__new__(meta, cname, bases, dctry)
cls.__setattr__ = _setattr
return cls
class ImPoint(object):
__metaclass__ = attr_block_meta
def __init__(self, q, z):
object.__setattr__(self, 'q', q)
object.__setattr__(self, 'z', z)
point = ImPoint(1, 2)
print point.q, point.z
point.w = 3 # Raises AttributeError
You don't need metaclasses to solve this kind of problem.
If you want to create the data once up front and then have it be immutable, I would definitely use a namedtuple as shx2 suggests.
Otherwise, just define a collection of allowed fields on the class, and have __setattr__ check to see if the name that you're attempting to set is in the allowed fields collection. You don't need to change the implementation of __setattr__ part way through __init__ -- it will work during __init__ the just the same as it will work later. Use a tuple or a frozenset as the data structure for the allowed fields, if you want to discourage mutating/changing them on a given class.
class Point(object):
_allowed_attrs = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
def __setattr__(self, name, value):
if name not in self._allowed_attrs:
raise AttributeError(
"Cannot set attribute {!r} on type {}".format(
name, self.__class__.__name__))
super(Point, self).__setattr__(name, value)
p = Point(5, 10)
p.x = 9
p.y = "some string"
p.z = 11 # raises AttributeError
This can easily be factored out into a base-class for re-use:
class RestrictedAttributesObject(object):
_allowed_attrs = ()
def __setattr__(self, name, value):
if name not in self._allowed_attrs:
raise AttributeError(
"Cannot set attribute {!r} on type {}".format(
name, self.__class__.__name__))
super(RestrictedAttributesObject, self).__setattr__(name, value)
class Point(RestrictedAttributesObject):
_allowed_attrs = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
I don't think it would be considered pythonic to lock down the allowed attributes of an object in this way, and it will cause some complication for subclasses that need additional attributes (a subclass will have to ensure that the _allowed_attrs field has contents appropriate for it).
I have this same need (for a development quick-hack API). I don't use metaclasses for this, just inheritance:
class LockedObject(object):
def __setattr__(self, name, value):
if name == "_locked":
object.__setattr__(self, name, value)
return
if hasattr(self, "_locked"):
if not self._locked or hasattr(self, name):
object.__setattr__(self, name, value)
else:
raise NameError("Not allowed to create new attribute {} in locked object".format(name))
else: # never called _lock(), so go on
object.__setattr__(self, name, value)
def _lock(self):
self._locked = True
def _unlock(self):
self._locked = False
Then:
class Base(LockedObject):
def __init__(self):
self.a = 0
self.b = 1
self._lock()
If I need to subclass Base and add extra attributes I use unlock:
class Child(Base):
def __init__(self):
Base.__init__(self)
self._unlock()
self.c = 2
self._lock()
If Base is abstract you can skip its locking and just lock the childs.
I have then some unittests that check that every public class is locked after init to catch me if I forget the locking.

Python __getattribute__ and __setattr__

I have the following code:
#-*-coding:utf-8-*-
class A(object):
def __init__(self, x):
self.x = x
def __getattr__(self, name): # `__getattr__` will be called undefined attribute
print "get: ", name
return self.__dict__.get(name)
def __setattr__(self, name, value):
print "set:", name, value
self.__dict__[name] = value
def __getattribute__(self, name): # `__getattribute__` will be called all attributes
print "attribute:", name
return object.__getattribute__(self, name)
if __name__ == "__main__":
a = A(10)
print '---------------'
a.x
print '---------------'
a.y = 20
print '---------------'
a.z
And the result is :
set: x 10
attribute: __dict__
---------------
attribute: x
---------------
set: y 20
attribute: __dict__
---------------
attribute: z
get: z
attribute: __dict__
When I called a=A(10), why __getattribute__ is called ? This is my thought: there is self.x = x in __init__ , and __setattr__ catch __init__, self.__dict__[name] = value catch __getattrbute__. So, __getattribute__ is called. Does my thought right ? What's wrong ?
The arrow is pointing to where __setattr__ invokes __getattribute__:
def __setattr__(self, name, value):
print "set:", name, value
self.__dict__[name] = value
# ^ attribute access!
__getattribute__ handles all explicit attribute lookup, including __dict__. I believe this is the conclusion you already came to; I couldn't quite understand what you were trying to say.

Categories

Resources