I'm trying to use Python's #property decorator on a dict in a class. The idea is that I want a certain value (call it 'message') to be cleared after it is accessed. But I also want another value (call it 'last_message') to contain the last set message, and keep it until another message is set. In my mind, this code would work:
>>> class A(object):
... def __init__(self):
... self._b = {"message": "",
... "last_message": ""}
... #property
... def b(self):
... b = self._b
... self._b["message"] = ""
... return b
... #b.setter
... def b(self, value):
... self._b = value
... self._b["last_message"] = value["message"]
...
>>>
However, it doesn't seem to:
>>> a = A()
>>> a.b["message"] = "hello"
>>> a.b["message"]
''
>>> a.b["last_message"]
''
>>>
I'm not sure what I have done wrong? It seems to me like #property doesn't work like I would expect it to on dicts, but maybe I'm doing something else fundamentally wrong?
Also, I know that I could just use individual values in the class. But this is implemented as a session in a web application and I need it to be a dict. I could either make this work, or make the whole session object to pretend it's a dict, or use individual variables and hack it into workingness throughout the rest of the code base. I would much rather just get this to work.
class MyDict(dict):
def __setitem__(self, key, value):
if key == 'message':
super().__setitem__('message', '')
super().__setitem__('last_message', value)
else:
super().__setitem__(key, value)
class A(object):
def __init__(self):
self._b = MyDict({"message": "",
"last_message": ""})
#property
def b(self):
return self._b
a = A()
a.b['message'] = 'hello'
print(a.b['message'])
# ''
print(a.b['last_message'])
# hello
As I think you've discovered, the reason why your setter wasn't working is because
a.b['message']='hello'
first accesses a.b, which calls the b property's getter, not its setter. The getter returns the dict self._b. Then self._b['message']='hello' causes the dict's __setitem__ is called .
So to fix the problem, you need a special dict (like MyDict).
I may be missing what you are trying to do here, but does this solve your problem?
class A(object):
def __init__(self):
self._b = {'message':'',
'last_message': ''}
#property
def b(self):
b = self._b.copy()
self._b['message'] = ''
return b
#b.setter
def b(self, value):
self._b['message'] = value
self._b['last_message'] = value
if __name__ == "__main__":
a = A()
a.b = "hello"
print a.b
print a.b
print a.b["last_message"]
$ python dictPropTest.py
{'last_message': 'hello', 'message': 'hello'}
{'last_message': 'hello', 'message': ''}
hello
Related
I have known the use of setter and getter for several properties, how could I trigger a same function when any property changes?
For example, the following codes add a setter to property a.
class AAA(object):
def __init__(self):
...
#property
def a(self):
...
#a.setter
def a(self, value):
...
If the class has a lot of properties like a, b, ... , z, and I want to print something like property xxx is modified when any property changes.
It is stupid to add the similar getter and setter one by one.
I have read some related questions and answers, but I do not find the solution for many properties.
How to trigger function on value change?
Using #property versus getters and setters
Metaprogramming, using __setattr__ to intercept modification:
class AAA(object):
def __setattr__(self, attr, value):
print("set %s to %s" % (attr, value))
super().__setattr__(attr, value)
aaa = AAA()
aaa.x = 17
# => set x to 17
print(aaa.x)
# => 17
You can do similarly with __getattr__ for reading access.
You can use descriptors. Descriptors are, in layman's terms, reusable properties. The advantage over the __getattr__ and __setattr__ hooks is that you have more fine-grained control over what attributes are managed by descriptors.
class MyDescriptor:
def __init__(self, default='default'):
self.default = default
def __set_name__(self, owner, name): # new in Python3.6
self.name = name
def __get__(self, instance, owner):
print('getting {} on {}'.format(self.name, instance))
# your getter logic here
# dummy implementation:
if instance is not None:
try:
return vars(instance)[self.name]
except KeyError:
return self.default
return self
def __set__(self, instance, value):
print('setting {} on {}'.format(self.name, instance))
# your getter logic here
# dummy implementation:
vars(instance)[self.name] = value
class MyClass:
a = MyDescriptor()
b = MyDescriptor()
_id = 1
# some logic for demo __repr__
def __init__(self):
self.c = 'non-descriptor-handled'
self.id = MyClass._id
MyClass._id += 1
def __repr__(self):
return 'MyClass #{}'.format(self.id)
Demo:
>>> m1 = MyClass()
>>> m2 = MyClass()
>>> m1.c
'non-descriptor-handled'
>>> m1.a
getting a on MyClass #1
'default'
>>> m1.b
getting b on MyClass #1
'default'
>>> m1.b = 15
setting b on MyClass #1
>>> m1.b
getting b on MyClass #1
15
>>> m2.b
getting b on MyClass #2
'default'
One year after asking this question, I find a more elgant way to add getter and setter to multiple similar properties.
Just make a more 'abstract' function which returns decorated property. And pass each of these properties to this function with a for loop. Then the getter and setter of all these properties are added.
def propABC(arg):
# arg: 'a', 'b', 'c'
#property
def prop(self):
_arg = '_' + arg
return getattr(self, _arg)
#prop.setter
def prop(self, val):
_arg = '_' + arg
setattr(self, _arg, val)
print(f"Set prop {_arg}")
return prop
for key in ['a', 'b', 'c']:
exec(f"{key} = propABC('{key}')")
Ok, lets say I have a really simple class i.e.:
class Test(object):
pass
What I would like to do is to define some default setter and getter methods
which are automatically applied to a new object member at creation time. In the example below a.x should always be uppercase, i.e.:
a = Test()
a.x = "foo"
print a.x
>>> FOO
If I create x within the class I would get this behavior like this:
class Test(object):
def __init__(self):
self._x = ""
#property
def x(self):
return self._x
#x.setter(self, string):
self._x = string.upper()
So is there any possibility to do this without defining setter and getter methods for each member ?? Thank a lot.
EDIT: With creation time I meant the creation time of a.x not of the class instance.
The simplest way is probably to override __setattr__, and change any string values to uppercase:
>>> class Test(object):
def __setattr__(self, attr, val):
if isinstance(val, basestring):
val = val.upper()
super(Test, self).__setattr__(attr, val)
>>> t = Test()
>>> t.x = 'foo'
>>> t.x
'FOO'
Subclass a dict;
In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class Struct(dict):
: """A dict subclass where you can simply use a dot to access attributes."""
:
: def __getattr__(self, name):
: return self[name]
:
: def __setattr__(self, name, value):
: self[name] = value
:--
In [2]: a = Struct()
In [3]: a.x = "foo"
In [4]: a.x
Out[4]: 'foo'
In [5]: a.length = 14
In [6]: a
Out[6]: {'length': 14, 'x': 'foo'}
That sounds like a use-case for pythons Descriptor Proctocol.
class WithDescriptors:
x = UpperCaseDescriptor()
y = UpperCaseDescriptor()
z = UpperCaseDescriptor()
class UperCaseDescriptor(object):
def __init__(self):
self.val = ''
def __get__(self, obj, objtype):
return self.val.upper()
def __set__(self, obj, val):
self.val = val
Thats just an outline and i didnt test the code to work!
If you want to extend such behaviour to every attribute of an instance,
even which are not existent, you should consider metaclasses.
I have a class like this:
class MyClass(object):
def f_1(self,x):
return foo(x, self.property_1)
def f_2(self,x):
return foo(x, self.property_2)
The idea is that multiple functions f_n have a common structure, but depend on different properties property_n of the class.
I look for a more compact way to define those f_n in the __init__? I think of something like
class MyClass(object):
def __init__(self):
self.f_1 = self.construct_function(self.property_1)
self.f_2 = self.construct_function(self.property_2)
def construct_function(self, property):
# ???
That is what I have in mind, but I dont know how to define this construct_function. It is important that 'property' is of a point-by-value type.
Edit:
I simplified Martijn's very good answer to this solution, which works fine:
def construct_function(property_name):
def f_n(self, x):
return foo(x, getattr(self, property_name))
return f_n
class MyClass2(object):
f_1 = construct_function('property_1')
f_2 = construct_function('property_2')
Just wanted to mention it here, as multiline comments are not allowed...
If you want to generate these methods per class, use a class decorator:
def property_functions(**properties):
def construct_method(prop):
def f_n(self):
return foo(getattr(self, prop))
return f_n
def class_decorator(cls):
for name, prop in properties.iteritems():
setattr(cls, name, construct_method(prop))
return cls
return class_decorator
then use it like:
#property_functions(f_1='property_1', f_2='property_2')
class MyClass(object):
property_1 = 'foo'
property_2 = 'bar'
Demonstration:
>>> def foo(value): print value
...
>>> #property_functions(f_1='property_1', f_2='property_2')
... class MyClass(object):
... property_1 = 'foo'
... property_2 = 'bar'
...
>>> mc = MyClass()
>>> mc.f_1()
foo
>>> mc.f_2()
bar
You can have a look at getattr or getattribute . They allow you dynamically create and reference attributes. For ex
It works something like this:
class foo:
def __init__(self):
self.a = "a"
def __getattr__(self, attribute):
return "You asked for %s, but I'm giving you default" % attribute
>>> bar = foo()
>>> bar.a
'a'
>>> bar.b
"You asked for b, but I'm giving you default"
>>> getattr(bar, "a")
'a'
>>> getattr(bar, "b")
"You asked for b, but I'm giving you default"
In python, is there a way to prevent adding new class variables after defining the object?
For example:
class foo:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
bar = foo()
try:
bar.d = 4
except Exception, e:
print "I want this to always print"
Alternatively, is there a way to count the number of variables in an object?
class foo:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
def count(self):
...
bar = foo()
if bar.count() == 3:
print "I want this to always print"
The only way I thought of doing this was using a dictionary or list:
class foo:
def __int__(self):
self.dict = {'foo':1, 'bar':2}
self.len = 2
def chk():
return self.len == len(self.list)
However, doing this feels rather cumbersome for python. (obj.dict['foo']). I'd prefer just obj.foo if possible.
I want to have this so that I never accidentally declare a variable when I mean to change an existing one.
f = foo()
f.somename = 3
...
f.simename = 4 #this is a typo
if f.somename == 3:
solve_everything()
I suggest using __setattr__ to avoid the oddities of __slots__.
You always have to be careful when messing with __setattr__, since it takes care of setting all instance attributes, including those you set in __init__. Therefore it has to have some way of knowing when to allow the setting of an attribute, and when to deny it. In this solution I've designated a special attribute that controls whether new attributes are allowed or not:
class A(object):
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
self.freeze = True
def __setattr__(self, attr, value):
if getattr(self, "freeze", False) and not hasattr(self, attr):
raise AttributeError("You shall not set attributes!")
super(A, self).__setattr__(attr, value)
Testing:
a = A()
try:
a.d = 89
except AttributeError:
print "It works!"
else:
print "It doesn't work."
a.c = 42
print a.a
print a.c
a.freeze = False
a.d = 28
a.freeze = True
print a.d
Result:
It works!
1
42
28
Also see gnibblers answer that wraps this concept neatly up in a class decorator, so it doesn't clutter up the class definition and can be reused in several classes without duplicating code.
EDIT:
Coming back to this answer a year later, I realize a context manager might solve this problem even better. Here's a modified version of gnibbler's class decorator:
from contextlib import contextmanager
#contextmanager
def declare_attributes(self):
self._allow_declarations = True
try:
yield
finally:
self._allow_declarations = False
def restrict_attributes(cls):
cls.declare_attributes = declare_attributes
def _setattr(self, attr, value):
disallow_declarations = not getattr(self, "_allow_declarations", False)
if disallow_declarations and attr != "_allow_declarations":
if not hasattr(self, attr):
raise AttributeError("You shall not set attributes!")
super(cls, self).__setattr__(attr, value)
cls.__setattr__ = _setattr
return cls
And here's how to use it:
#restrict_attributes
class A(object):
def __init__(self):
with self.declare_attributes():
self.a = 1
self.b = 2
self.c = 3
So whenever you want to set new attributes, just use the with statement as above. It can also be done from outside the instance:
a = A()
try:
a.d = 89
except AttributeError:
print "It works!"
else:
print "It doesn't work."
a.c = 42
print a.a
print a.c
with a.declare_attributes():
a.d = 28
print a.d
In python, is there a way to prevent adding new class variables after defining the object?
Yes. __slots__. But do carefully read the notes.
How about a class decorator based on lazyr's answer
def freeze(cls):
_init = cls.__init__
def init(self, *args, **kw):
_init(self, *args, **kw)
self.freeze = True
cls.__init__ = init
def _setattr(self, attr, value):
if getattr(self, "freeze", None) and (attr=="freeze" or not hasattr(self, attr)):
raise AttributeError("You shall not set attributes!")
super(cls, self).__setattr__(attr, value)
cls.__setattr__ = _setattr
return cls
#freeze
class foo(object):
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
bar = foo()
try:
bar.d = 4
except Exception, e:
print "I want this to always print"
Preventing adding new attibutes using __slots__ class attribute:
class foo(object):
__slots__ = ['a', 'b', 'c']
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
bar = foo()
try:
bar.d = 4
except Exception as e:
print(e,"I want this to always print")
Counting attributes:
print(len([attr for attr in dir(bar) if attr[0] != '_' ]))
use this to count no.of attributes of an instance:
>>> class foo:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
>>> bar=foo()
>>> bar.__dict__
{'a': 1, 'c': 3, 'b': 2}
>>> len(bar.__dict__) #returns no. of attributes of bar
3
Do you mean new class variables or new instance variables? The latter looks like what you mean and is much easier to do.
Per Ignacio Vazquez-Abrams's answer, __slots__ is probably what you want. Just do __slots__ = ('a', 'b', 'c') inside of your class and that will prevent any other attributes from being created. Note that this only applies to instances of your class -- class-level attributes can still be set, and subclasses can add whatever attributes they please. And he is right -- there are some oddities, so read the linked documentation before you start sprinkling slots everywhere.
If you aren't using slots, return len(vars(self)) works as a body for your suggested count method.
As an alternative to slots, you could define a __setattr__ that rejects any attribute not on a "known good" list, or to reject any new attributes after a frozen attribute is set to True at the end of __init__, etc. This is harder to get right, but more flexible.
If you actually want your instances to be completely read-only after initialization, and you are using a recent version of Python, consider defining a namedtuple or subclass thereof. Tuple subclasses also have some limitations though; if you need to go this route I can expand on it, but I'd stick with slots unless you have a reason to do otherwise.
Suppose you now want your class to have a fixed set of both mutable and immutable attributes? I've hacked gnibbler's answer to make class attributes immutable after init:
def frozenclass(cls):
""" Modify a class to permit no new attributes after instantiation.
Class attributes are immutable after init.
The passed class must have a superclass (e.g., inherit from 'object').
"""
_init = cls.__init__
def init(self, *args, **kw):
_init(self, *args, **kw)
self.freeze = True
cls.__init__ = init
def _setattr(self, attr, value):
if getattr(self, "freeze", None):
if attr=="freeze" or not hasattr(self, attr):
raise AttributeError("You shall not create attributes!")
if hasattr(type(self), attr):
raise AttributeError("You shall not modify immutable attributes!")
super(cls, self).__setattr__(attr, value)
cls.__setattr__ = _setattr
return cls
And an example:
#frozenclass
class myClass(object):
""" A demo class."""
# The following are immutable after init:
a = None
b = None
c = None
def __init__(self, a, b, c, d=None, e=None, f=None):
# Set the immutable attributes (just this once, only during init)
self.a = a
self.b = b
self.c = c
# Create and set the mutable attributes (modifyable after init)
self.d = d
self.e = e
self.f = f
I can't understand why this code:
class A(object):
def __init__(self):
self.__value = 1
def get_value(self):
return self.__value
class B(A):
def __init__(self):
A.__init__( self )
self.__value = 2
b = B()
print b.get_value()
gives 1, but not 2. Thanks for your help.
Your problem is that double underscores are special in python, and create some modicum of privacy (not enforced, but it mangles the names, which is what is affecting you here). You should recreate this without the variable being named with double underscores. Also, you should use super instead of calling A.__init__ explicitly:
>>> class A(object):
... def __init__(self):
... self.value = 1
... def get_value(self):
... return self.value
...
>>> class B(A):
... def __init__(self):
... super(B, self).__init__()
... self.value = 2
...
>>> b = B()
>>> b.get_value()
2
For more specifics if you don't want to read the referenced documentation:
If you read the link on "special" above, that paragraph describes the internal name mangling that happens when you use __. The short answer is that A.get_value() returns _A__value, and setting self.__value in B actually sets a member variable named _B__value, which means that A.get_value() never sees it.
You can prove this to yourself by indeed doing something truly hinky:
>>> class A(object):
... def get_value(self):
... return self._B__value
...
>>> class B(A):
... def __init__(self):
... self.__value = 2
...
>>> b = B()
>>> b.get_value()
2
self.__value = 1 creates 'private' field, which is invisible for children in a such way.
Use single underscore for 'protected' fields.
E.g. self._value = 1
When you creates field with name started with double underscore, Python makes some modification on it before adding to object's __dict__:
>>> class A(object):
... def __init__(self):
... self.__value = 1
... def get_value(self):
... return self.__value
...
>>> A().__dict__
{'_A__value': 1}
That is why __value field is not visible in child object.