Detecting when a new attribute is added to a object in Python? - python

I would like to have a special obj that does the following:
obj.newly_created_attribute = some_value
Obviously, all objects will allow this. But I would like the previous code to automatically call a method when newly_created_attribute is not yet a attribute of obj. In my particular case, I wish to set up a custom get and set method for obj.newly_created_attribute (a property now).
Is there any way to do this? Some way to specify a callback that will be run whenever a new attribute is added to a object?

You can accomplish this by overriding __setattr__:
class SomeClass(object):
def __setattr__(self, name, value):
if not hasattr(self, name):
print "new attribute", name
# do stuff here
return object.__setattr__(self, name, value)

__setattr__ will help you there:
Called when an attribute assignment is attempted. This is called instead of the normal mechanism (i.e. store the value in the instance dictionary). name is the attribute name, value is the value to be assigned to it.
#!/usr/bin/env python
class Klass(object):
def __setattr__(self, name, value):
if not hasattr(self, name):
self.on_first_setattr()
return object.__setattr__(self, name, value)
def on_first_setattr(self):
print "I am just a callback and my story's seldom told."
obj = Klass()
obj.some_attr = 1 # will call callback
obj.some_attr = 2 # no output

Overload __setattr__. Example:
class Foo(object):
def __setattr__(self, attr, val):
print "setattr"
if attr not in self.__dict__:
print "new attr:", attr
self.__dict__[attr] = val
else:
print "extant attr:", attr
self.__dict__[attr] = val

Related

Immutable attribute in python class and type check

I am looking for a way in python to build classes in python that:
setter check the type of the value before assignment
impossible to add new class attribute
for the time being I found those two decorators:
def getter_setter_gen(name, type_):
def getter(self):
return getattr(self, "__" + name)
def setter(self, value):
print "setter", value
if not isinstance(value, type_):
raise TypeError("%s attribute must be set to an instance of %s" % (name, type_))
setattr(self, "__" + name, value)
return property(getter, setter)
def auto_attr_check(cls):
new_dct = {}
print "auto_attr_check", cls.__dict__.items()
for key, value in cls.__dict__.items():
if isinstance(value, type):
value = getter_setter_gen(key, value)
new_dct[key] = value
# Creates a new class, using the modified dictionary as the class dict:
n = type(cls)(cls.__name__, cls.__bases__, new_dct)
return n
and
def froze_it(cls):
def frozensetattr(self, key, value):
print key
key = ''+key
print(dir(self))
print key
if not hasattr(self, key):
raise TypeError("Class {} is frozen. Cannot set {} = {}"
.format(cls.__name__, key, value))
else:
object.__setattr__(self, key, value)
cls.__setattr__ = frozensetattr
return cls
but I have a lot of trouble to join those two approach.
Can you help me? Do you have ideas?
Thanks a lot
The issue you're encountering is that your property is using setattr to set the value of your attributes, but that you've overridden __setattr__ in such a way that it can't ever succeed. The hasattr check will fail for both the main property name, and for the underscore-prefixed name of the actual attribute that underlies the property (if the setter code could get that far).
I suggest two complementary fixes. First, change the hasattr check to be a little more careful. If it fails, it should also check the class dict for a property object:
def frozensetattr(self, key, value):
if not hasattr(self, key) and type(cls.__dict__.get(key)) is not property:
raise TypeError("Class {} is frozen. Cannot set {} = {}"
.format(cls.__name__, key, value))
else:
object.__setattr__(self, key, value)
The second fix is to the setter function, which should bypass the __setattr__ method for the underlying attribute:
def setter(self, value):
print "setter", value
if not isinstance(value, type_):
raise TypeError("%s attribute must be set to an instance of %s" % (name, type_))
object.__setattr__(self, "__" + name, value)
A final note: The double-underscore prefix on the attribute names created by your property is not going to invoke name mangling, which I suspect it what you intend. Name mangling only happens at compile time. When a method contains an name like __bar, the name gets transformed by the compiler to _NameOfTheClass__bar (where NameOfTheClass is the name of the class the method is defined in).
At runtime, the code behaves exactly as if the mangled name was written directly in the source. This means that __setattr__ and friends don't treat mangled names (or double-underscore prefixed names that would have been mangled by the compiler if they were written as regular variable names instead of strings) in any kind of special way. So there's no easy way to do name mangling on dynamically created variables.
If you just want your names to be informally marked as private (that is, they're not part of the public API), you should probably use a single leading underscore.

How to raise exception for non-existent class member?

I have a method for automatically creating Python classes that wrap database tables, with class members that have the same name as the fields in the table. The class files look like this:
class CpsaUpldBuildChrgResultSet(Recordset):
def __init__(self, connection):
super().__init__(connection)
self.DefaultTableName = 'cpsa_upld_build_chrg_result'
self._keyFields.append('j_trans_seq')
self._keyFields.append('j_index')
#property
def j_trans_seq(self):
return self.GetValue('j_trans_seq')
#j_trans_seq.setter
def j_trans_seq(self, value):
self.SetKeyValue('j_trans_seq', value)
#property
def j_index(self):
return self.GetValue('j_index')
#j_index.setter
def j_index(self, value):
self.SetKeyValue('j_index', value)
I just found that if I try to set a value for a non-existent class member, such as J_TRANS_SEQ, no exception is thrown. Is there something I can add to this class so that an attempt to access a non-existent member would raise an exception?
You can add a __setattr__ method to your class that raises an AttributeError whenever an invalid attribute is assigned to. I'm not sure exactly how you'd want to determine which attributes are valid and which are not, but one approach might be something like this:
def __setattr__(self, name, value):
if hasattr(self, name):
super().__setattr__(name, value)
else:
raise AttributeError("{} object has no attribute {!r}".format(type(self), name))
This assumes that any attribute that can be looked up is also valid to be assigned to. It might break if your property's getters don't work unless the setter is called before the getter. It might also be too permissive, since it would allow setting of instance attributes that override class attributes (such as __init__). Another approach might be to check the name against a white-list of known attributes (but be sure to include the attributes that you need for the inherited class machinery, like DefaultTableName and _keyFields).
I think #Blckknght has the right idea, but left out some important details in his answer—such has how class attributes (class members) are set the first time, when they don't preexist, such as in the typical scenario when the class's __init__() method executes. Here's a more fully fleshed-out answer that works in Python 3 which addresses that deficiency.
It also shows how to minimize the coding of a bunch of repetitive properties.
class Recordset(object):
def __init__(self, connection):
print('Recordset.__init__({!r}) called'.format(connection))
def SetKeyValue(self, name, value):
print('SetKeyValue({!r}, {!r}) called'.format(name, value))
def GetValue(self, name):
print('GetValue({!r}) called'.format(name))
def fieldname_property(name):
storage_name = '_' + name
#property
def prop(self):
return self.GetValue(storage_name)
#prop.setter
def prop(self, value):
self.SetKeyValue(storage_name, value)
return prop
class CpsaUpldBuildChrgResultSet(Recordset):
# define properties for valid fieldnames
j_trans_seq = fieldname_property('j_trans_seq')
j_index = fieldname_property('j_index')
def __init__(self, connection):
super().__init__(connection)
self._setter('DefaultTableName', 'cpsa_upld_build_chrg_result')
def __setattr__(self, name, value):
if hasattr(self, name):
self._setter(name, value)
else:
raise AttributeError("No field named %r" % name)
def _setter(self, name, value):
"""Provides way to intentionally bypass overloaded __setattr__."""
super().__setattr__(name, value)
print('start')
db_table = CpsaUpldBuildChrgResultSet('SomeConnection')
print('assigning attributes...')
db_table.j_trans_seq = 42 # OK
db_table.j_index = 13 # OK
db_table.J_TRANS_SEQ = 99 # -> AttributeError: No field named 'J_TRANS_SEQ'
print('done')

__setattr__ can override __getattribute__ in Python?

I have the following code, it is from Learning Python published by O'Reilly Media. Why line 3 (self._name = name) won't trigger __getattribute__? Is it because __setattr__ overrides it?
class Person: # Portable: 2.X or 3.X
def __init__(self, name): # On [Person()]
self._name = name # Triggers __setattr__!
def __getattribute__(self, attr): # On [obj.any]
print('get: ' + attr)
if attr == 'name': # Intercept all names
attr = '_name' # Map to internal name
return object.__getattribute__(self, attr) # Avoid looping here
def __setattr__(self, attr, value): # On [obj.any = value]
print('set: ' + attr)
if attr == 'name':
attr = '_name' # Set internal name
self.__dict__[attr] = value # Avoid looping here
You are setting an attribute. Assignment to an attribute always uses __setattr__.
__getattr__ and __getattribute__ are only consulted when looking up the value of a specific attribute; when setting you are not retrieving a value.
This is not an override; even if __setattr__ was not defined, the __getattribute__ method would not be consulted.

Accessing an object's attribute inside __setattr__

Python bit me today. I'm trying to access an object's attribute inside its __setattr__ implementation - I can't figure out how. This is what I've tried so far:
class Test1(object):
def __init__(self):
self.blub = 'hi1'
def __setattr__(self, name, value):
print self.blub
class Test2(object):
def __init__(self):
self.blub = 'hi2'
def __setattr__(self, name, value):
print object.__getattr__(self, 'blub')
class Test3(object):
def __init__(self):
self.blub = 'hi3'
def __setattr__(self, name, value):
print object.__getattribute__(self, 'blub')
class Test4(object):
def __init__(self):
self.blub = 'hi4'
def __setattr__(self, name, value):
print self.__getattr__('blub')
class Test5(object):
def __init__(self):
self.blub = 'hi5'
def __setattr__(self, name, value):
print self.__getattribute__('blub')
class Test6(object):
def __init__(self):
self.blub = 'hi6'
def __setattr__(self, name, value):
print self.__dict__['blub']
Testing:
try:
TestX().bip = 'bap'
except Exception as ex:
print ex
with X from 1 to 6
Output:
'Test1' object has no attribute 'blub'
type object 'object' has no attribute '__getattr__'
'Test3' object has no attribute 'blub'
'Test4' object has no attribute '__getattr__'
'Test5' object has no attribute 'blub'
'blub'
Any suggestions?
Because inside the __init__ it is trying to set blub which calls __setattr__; and it does not set anything but tries to access (and print) blub, finds nothing and raises the error. Check this:
>>> class Test2(object):
def __init__(self):
print "__init__ called"
self.blub = 'hi2'
print "blub was set"
def __setattr__(self, name, value):
print "__setattr__ called"
print self.blub
>>> Test2()
__init__ called
__setattr__ called
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
Test2()
File "<pyshell#9>", line 4, in __init__
self.blub = 'hi2'
File "<pyshell#9>", line 9, in __setattr__
print self.blub
AttributeError: 'Test2' object has no attribute 'blub'
>>>
OP, you haven't told us the whole story. You did not just run code like this:
TestX().bip = 'bap'
You ran code like this:
try:
TestX().bip = 'bap'
except Exception as ex:
print ex
There's a big difference. Why, you ask? Well, your output seems on first glance to indicate that Test6 works, and several comments and answers assumed that it did. Why does it appear to work? Reading the code, there's no way it should work. A closer inspection of the source code reveals that if it had worked, it should have printed hi6, not 'blub'.
I put a breakpoint at the print ex line in pdb to examine the exception:
(Pdb) ex
KeyError('blub',)
(Pdb) print ex
'blub'
For some reason print ex does not print KeyError: blub like you'd expect, but just 'blub', which was why Test6 appeared to work.
So we've cleared that up. In the future, please do not leave out code like this because it might be important.
All the other answers correctly point out that you have not set the attribute you're attempting to print, and that this is your problem. The answer you had accepted previously, before you accepted this answer istead, prescribed the following solution:
def __setattr__(self, name, value):
self.__dict__[name] = value
print self.__dict__[name]
While this solution does indeed work, it is not good design. This is because you might want to change the base class at some point, and that base class might might have important side effects when setting and/or getting attributes, or it might not store the attributes in self.__dict__ at all! It is better design to avoid messing around with __dict__.
The correct solution is to invoke the parent __setattr__ method, and this was suggested by at least one other answer, though with the wrong syntax for python 2.x. Here's how I'd do it:
def __setattr__(self, name, value):
super(Test6, self).__setattr__(name, value)
print getattr(self, name)
As you see I'm using getattr instead of __dict__ to look up attributes dynamically. Unlike using __dict__ directly, this will call self.__getattr__ or self.__getattribute__, as appropriate.
__setattr__ works on the class, so when you're over-riding - you need to make it actually set the attribute... otherwise, it'll never exist... eg:
object.__setattr__(self, name, value)
So, in your __init__, when you do self.blub = 'hi' that's in effect a no-op.
You never actually set the attribute in your __setattr__ so of course the object doesn't have it.
def __setattr__(self, name, value):
self.name = value
# this doesn't work, since it calls itself (the __setattr__)
def __setattr__(self, name, value):
super().__setattr__(name, value)
# call the default implementation directly in Py 3.x
def __setattr__(self, name, value):
super(TestX, self).__setattr__(name, value) # for Python 2.x
Of course doing this alone is good for nothing, you usually want to add some functionality oround this like some condition, debug printing, caching or whatever you need.
print self.__dict__['blub']
Prints out blub which is correct. You have to set the new value first, because python won't do that for you, like that:
def __setattr__(self, name, value):
self.__dict__[name] = value
print self.__dict__[name]
Then test6.blub = 'test' should print out test
Edit:
As suggested by #Lauritz you can also use
def __setattr__(self, name, value):
super(Test6, self).__setattr__(name, value)
print self.__dict__[name]
Which invokes the parent function, so if you superclass has already a __setattr__ function it won't get overridden.

python __getattribute__ override and #property decorator

I had to write a class of some sort that overrides __getattribute__.
basically my class is a container, which saves every user-added property to self._meta which is a dictionary.
class Container(object):
def __init__(self, **kwargs):
super(Container, self).__setattr__('_meta', OrderedDict())
#self._meta = OrderedDict()
super(Container, self).__setattr__('_hasattr', lambda key : key in self._meta)
for attr, value in kwargs.iteritems():
self._meta[attr] = value
def __getattribute__(self, key):
try:
return super(Container, self).__getattribute__(key)
except:
if key in self._meta : return self._meta[key]
else:
raise AttributeError, key
def __setattr__(self, key, value):
self._meta[key] = value
#usage:
>>> a = Container()
>>> a
<__main__.Container object at 0x0000000002B2DA58>
>>> a.abc = 1 #set an attribute
>>> a._meta
OrderedDict([('abc', 1)]) #attribute is in ._meta dictionary
I have some classes which inherit Container base class and some of their methods have #property decorator.
class Response(Container):
#property
def rawtext(self):
if self._hasattr("value") and self.value is not None:
_raw = self.__repr__()
_raw += "|%s" %(self.value.encode("utf-8"))
return _raw
problem is that .rawtext isn't accessible. (I get attributeerror.) every key in ._meta is accessible, every attributes added by __setattr__ of object base class is accessible, but method-to-properties by #property decorator isn't. I think it has to do with my way of overriding __getattribute__ in Container base class. What should I do to make properties from #property accessible?
I think you should probably think about looking at __getattr__ instead of __getattribute__ here. The difference is this: __getattribute__ is called inconditionally if it exists -- __getattr__ is only called if python can't find the attribute via other means.
I completely agree with mgilson. If you want a sample code which should be equivalent to your code but work well with properties you can try:
class Container(object):
def __init__(self, **kwargs):
self._meta = OrderedDict()
#self._hasattr = lambda key: key in self._meta #???
for attr, value in kwargs.iteritems():
self._meta[attr] = value
def __getattr__(self, key):
try:
return self._meta[key]
except KeyError:
raise AttributeError(key)
def __setattr__(self, key, value):
if key in ('_meta', '_hasattr'):
super(Container, self).__setattr__(key, value)
else:
self._meta[key] = value
I really do not understand your _hasattr attribute. You put it as an attribute but it's actually a function that has access to self... shouldn't it be a method?
Actually I think you should simple use the built-in function hasattr:
class Response(Container):
#property
def rawtext(self):
if hasattr(self, 'value') and self.value is not None:
_raw = self.__repr__()
_raw += "|%s" %(self.value.encode("utf-8"))
return _raw
Note that hasattr(container, attr) will return True also for _meta.
An other thing that puzzles me is why you use an OrderedDict. I mean, you iterate over kwargs, and the iteration has random order since it's a normal dict, and add the items in the OrderedDict. Now you have _meta which contains the values in random order.
If you aren't sure whether you need to have a specific order or not, simply use dict and eventually swap to OrderedDict later.
By the way: never ever use an try: ... except: without specifying the exception to catch. In your code you actually wanted to catch only AttributeErrors so you should have done:
try:
return super(Container, self).__getattribute__(key)
except AttributeError:
#stuff

Categories

Resources