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.
Related
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')
This has been previously asked on Stack Overflow, but none of the answers seem to address exactly what I need to do. In my case, I want these dynamically-added properties to be a shortcut to store and read values from a database, so unfortunately it's not as easy as in this answer (where a lambda function was used) or this one (where values where stored in a dictionary): I must call other methods of the class.
This is my attempt:
import operator
class Foo(object):
def get_value(self, name):
# read and return value from database
return -1
def set_value(self, name, value):
# store value in database
pass
def add_metadata_property(name):
getter = operator.methodcaller('get_value', name)
setter = operator.methodcaller('set_value', name) # gets value at runtime
setattr(Foo, name, property(getter, setter))
add_metadata_property('spam')
f = Foo()
f.spam # works!
f.spam = 2
The last line, however, raises:
Traceback (most recent call last):
File "<stdin>", line 27, in <module>
TypeError: methodcaller expected 1 arguments, got 2
Any ideas on how to achieve this?
I don't know why you use operator.methodcaller here.
When you call f.spam=2, it will invoke setter.
setter = operator.methodcaller('set_value', name) means setter(r) = r.set_value(name). Make no sense in your case.
I suggest you write this way, using #classmethod:
class Foo(object):
#classmethod
def get_value(self, name):
# read and return value from database
return -1
#classmethod
def set_value(self, name, value):
# store value in database
pass
def add_metadata_property(name):
setattr(Foo, name, property(Foo.get_value, Foo.set_value))
add_metadata_property('spam')
f = Foo()
f.spam # works!
f.spam = 2
If this helped you, please confirm it as the answer. Thanks!
Modifying class template looks a bit odd for me. I would suggest to overload __getattr__() and __setattr__() methods in your case.
class Foo:
def __getattr__(self, name):
print('read and return value from database for ', name)
return 123
def __setattr__(self, name, value):
print('store value', value, 'for', name, 'in database')
What would be the most convenient way to create a class which instances' attributes can't be changed from outside the class (you could still get the value), so it'd be possible to call self.var = v inside the class' methods, but not ClassObject().var = v outside of the class?
I've tried messing with __setattr__() but if I override it, the name attribute cannot be initiated in the __init__() method. Only way would be to override __setattr__() and use object.__setattr__(), which I am doing at the moment:
class MyClass(object):
def __init__(self, name):
object.__setattr__(self, "name", name)
def my_method(self):
object.__setattr__(self, "name", self.name + "+")
def __setattr__(self, attr, value):
raise Exception("Usage restricted")
Now this solution works, and it's enough, but I was wondering if there's even a better solution. The problem with this one is: I can still call object.__setattr__(MyClass("foo"), "name", "foo_name") from anywhere outside the class.
Is there any way to totally prevent setting the variable to anything from outside of the class?
EDIT: Stupid me not mentioning I'm not looking for property here, some of you already answered it, however it's not enough for me since it will leave self._name changeable.
No, you cannot do this in pure python.
You can use properties to mark your attributes as read-only though; using underscore-prefixed 'private' attributes instead:
class Foo(object):
def __init__(self, value):
self._spam = value
#property
def spam(self):
return self._spam
The above code only specifies a getter for the property; Python will not let you set a value for Foo().spam now:
>>> class Foo(object):
... def __init__(self, value):
... self._spam = value
... #property
... def spam(self):
... return self._spam
...
>>> f = Foo('eggs')
>>> f.spam
'eggs'
>>> f.spam = 'ham'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Of course, you can still access the 'private' _spam attribute from outside:
>>> f._spam
'eggs'
>>> f._spam = 'ham'
>>> f.spam
'ham'
You could use the double underscore convention, where attribute names with __ at the start (but not at the end!) are renamed on compilation. This is not meant for making a attribute inaccessible from the outside, it's intent is to protect an attribute from being overwritten by a subclass instead.
class Foo(object):
def __init__(self, value):
self.__spam = value
#property
def spam(self):
return self.__spam
You can still access those attributes:
>>> f = Foo('eggs')
>>> f.spam
'eggs'
>>> f.__spam
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__spam'
>>> f._Foo__spam
'eggs'
>>> f._Foo__spam = 'ham'
>>> f.spam
'ham'
There is no strict way of doing encapsulation on Python. The best you can do is prepend 2 underscores __ to the intended to be private attributes. This will cause them to be mangled with the class name (_ClassName_AttribName), so if you try to use them on an inherited class, the base member won't be referenced. The names are not mangled if you use getattrib() or setattrib() though.
You can also override __dir()__ in order to hide them.
You can use properties to simulate such a behavior but like Martijn said, it'll be possible to access the variable directly.
Doing this is a signal of you not understanding Python philosophy, check this out.
Why Python is not full object-oriented?
The properties way:
http://docs.python.org/2/library/functions.html#property
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
raise Exception("Usage restricted")
x = property(getx, setx)
I am trying to find a way to set dict values encapsulated into a class, for example using __getattr__ i can return the internal dict value, however the __setattr__ is called even when attributes exists, making my implementation ugly. The example below is simplified my actual class inherits from a Subject class (the subject part of the observer pattern)
i am trying to achieve something like this:
obj = Example()
obj.username = 'spidername' # all OK username is a key in the internal dict
# but company is not a key in the internal dict so
obj.company = 'ABC' # will raise AttributeError
and i am asking if there is a better way than the way i am doing below:
class Example(object):
def __init__(self, table=None):
self._fields = {}
self._table = table
def _set_fields(self):
"""
this method will be implemented by
subclasses and used to set fields names and values
i.e.
self._field['username'] = Field(default='unknown', is_primary=False)
"""
raise NotImplementedError
def __getattr__(self, name):
"""
great this method is only called when "name"
is not an attribute of this class
"""
if name in self._fields:
return self._fields[name].value
return None
def __setattr__(self, name, value):
"""
not so great, this method is called even for
attributes that exists in this class
is there a better way to do the following?
this can be in __init__, but its still ugly
"""
attribs = ['_fields', '_table']
if name in attribs:
super(Example, self).__setattr__(name, value)
else:
if name in self._fields:
self._fields[name].value = value
else:
raise AttributeError
EDIT: adjusted comment in code, added missin quotes
The problem is that the attributes don't exist when they are first assigned. In __init__, when you first assign a dict to _fields, _fields is not an attribute. It only becomes an existing attribute after its been assigned. You could use __slots__ if you know in advance what the attributes are, but my guess is that you don't. So my suggestion would be to insert these into the instance dict manually:
class Example(object):
def __init__(self, table=None):
self.__dict__['_fields'] = {}
self.__dict__['_table'] = table
...
def __setattr__(self, name, value):
if name in self._fields:
self._fields[name].value = value
else:
raise AttributeError
However, with this implementation, the only way you can add or change instance attributes later would be through __dict__. But I assume this is not likely.
FWIW, your overall goal can be achieved directly just by using __slots__:
>>> class Example(object):
__slots__ = ['username']
>>> obj = Example()
>>> obj.username = 'spiderman'
>>> obj.company = 'ABC'
Traceback (most recent call last):
File "<pyshell#18>", line 1, in <module>
obj.company = 'ABC'
AttributeError: 'Example' object has no attribute 'company'
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