Clean way to implement setter and getter for lots of properties? - python

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}')")

Related

How best to implement a property where the setter can only be called once?

I trying to implement a class property where the setter can only be called once and am wondering how best to achieve this ? And how to make it most 'pythonic'?
Options I have considered:
Subclass and extend the builtin property.
Decorate the setter of a property.
Add an attribute which persists how often each setter has been set.
Any other ideas?
And suggestions how to best implement?
If you are using it a lot, along with other property functionality, subclassing property is appropriate.
It is a bit tricky, due to the way property works - when one calls
#prop.setter, a new instance of the property is created. The subclass bellow will work.
class FuseProperty(property):
def setter(self, func):
def fuse(instance, value):
name = f"_fuse_{self.fget.__name__}"
if not getattr(instance, name, False):
func(instance, value)
setattr(instance, name, True)
return super().setter(lambda instance, value: fuse(instance, value))
Here is it in use.
In [24]: class A:
...: #FuseProperty
...: def a(self):
...: return self._a
...: #a.setter
...: def a(self, value):
...: self._a = value
...:
In [25]: a = A()
In [26]: a.a = 23
In [27]: a.a
Out[27]: 23
In [28]: a.a = 5
In [29]: a.a
Out[29]: 23
However, if this "fuse" property is all you need, and no other code is being added to the getters and setters, it can be much simpler: you can create a brand new "Descriptor" class, using the same mechanisms used by property - this can be much better, as your "fuse" properties can be built in a single line, with no need for a setter and getter methods.
All that is needed is a class with __get__ and __set__ methods - we can add __set_name__ to get the new property name automatically (which property itself does not, so we get the name from the fget method above)
class FuseAttribute:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, f"_{self.name}")
def __set__(self, instance, value):
if not getattr(instance, f"_fuse_{self.name}", False):
setattr(instance, f"_{self.name}", value)
# add an else clause for optionally raising an error
setattr(instance, f"_fuse_{self.name}", True)
And using it:
In [36]: class A:
...: a = FuseAttribute()
...:
In [37]: a = A()
In [38]: a.a = 23
In [39]: a.a
Out[39]: 23
In [40]: a.a = 5
In [41]: a.a
Out[41]: 23
Properties in Python are just descriptors, and it's relatively easy to implement your own that does exactly what you want:
class SetOnceProperty:
def __init__(self, name):
self.storage_name = '_' + name
def __get__(self, obj, owner=None):
return getattr(obj, self.storage_name)
def __set__(self, obj, value):
if hasattr(obj, self.storage_name):
raise RuntimeError(f'{self.storage_name[1:]!r} property already set.')
setattr(obj, self.storage_name, value)
def __delete___(self, obj):
delattr(obj, self.storage_name)
class Test:
test_attr = SetOnceProperty('test_attr')
def __init__(self, value):
self.test_attr = value*2 # Sets property.
test = Test(21)
print(test.test_attr) # -> 42
test.test_attr = 13 # -> RuntimeError: 'test_attr' property already set.
I have often preferred this way; "explicit is better than implicit":
class MyError(Exception):
...
NOT_SET = object()
class C:
def set_my_property(self, spam, eggs, cheese):
"""This sets the property.
If it's already set, you'll get an error. Donna do dat.
"""
if getattr(self, "_my_property", NOT_SET) is NOT_SET:
self._my_property = spam, eggs, cheese
return
raise MyError("I said, Donna do dat.")
#property
def my_property(self):
return self._my_property
Testing:
c=C()
c.set_my_property("spam", "eggs", "cheese")
assert c.my_property == ("spam", "eggs", "cheese")
try:
c.set_my_property("bacon", "butter", "coffee")
except MyError:
pass
This is simple enough and more general. A decorator for a function to be called only once, and ignore subsecuent calls.
def onlyonce(func):
#functools.wraps(func)
def decorated(*args):
if not decorated.called:
decorated.called = True
return self.func(*args)
decorated.called = False
return decorated
use like this
class A:
#property
def x(self):
...
#x.setter
#onlyonce
def x(self, val):
...
Or you can define a descriptor:
class Desc:
def __get__(self, inst, own):
return self._value
def __set__(self, inst, value):
if not hasattr(self, _value):
self._value = value
And use like this:
class A:
x = Desc()
It sounds like the attrs project would be helpful for your use case. You can realize "frozen" attributes in the following way:
import attr
#attr.s
class Test:
constant = attr.ib(on_setattr=attr.setters.frozen)
test = Test('foo')
test.constant = 'bar' # raises `attr.exceptions.FrozenAttributeError`
Note that it also supports validators via #constant.validator (see the example at the end of attr.ib docs).

Python setter and getter for class attributes [duplicate]

I have a class with two class methods (using the classmethod() function) for getting and setting what is essentially a static variable. I tried to use the property() function with these, but it results in an error. I was able to reproduce the error with the following in the interpreter:
class Foo(object):
_var = 5
#classmethod
def getvar(cls):
return cls._var
#classmethod
def setvar(cls, value):
cls._var = value
var = property(getvar, setvar)
I can demonstrate the class methods, but they don't work as properties:
>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
Is it possible to use the property() function with #classmethod decorated functions?
3.8 < Python < 3.11
Can use both decorators together. See this answer.
Python < 3.9
A property is created on a class but affects an instance. So if you want a classmethod property, create the property on the metaclass.
>>> class foo(object):
... _var = 5
... class __metaclass__(type): # Python 2 syntax for metaclasses
... pass
... #classmethod
... def getvar(cls):
... return cls._var
... #classmethod
... def setvar(cls, value):
... cls._var = value
...
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3
But since you're using a metaclass anyway, it will read better if you just move the classmethods in there.
>>> class foo(object):
... _var = 5
... class __metaclass__(type): # Python 2 syntax for metaclasses
... #property
... def var(cls):
... return cls._var
... #var.setter
... def var(cls, value):
... cls._var = value
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3
or, using Python 3's metaclass=... syntax, and the metaclass defined outside of the foo class body, and the metaclass responsible for setting the initial value of _var:
>>> class foo_meta(type):
... def __init__(cls, *args, **kwargs):
... cls._var = 5
... #property
... def var(cls):
... return cls._var
... #var.setter
... def var(cls, value):
... cls._var = value
...
>>> class foo(metaclass=foo_meta):
... pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3
In Python 3.9 You could use them together, but (as noted in #xgt's comment) it was deprecated in Python 3.11, so it is not recommended to use it.
Check the version remarks here:
https://docs.python.org/3.11/library/functions.html#classmethod
However, it used to work like so:
class G:
#classmethod
#property
def __doc__(cls):
return f'A doc for {cls.__name__!r}'
Order matters - due to how the descriptors interact, #classmethod has to be on top.
I hope this dead-simple read-only #classproperty decorator would help somebody looking for classproperties.
class classproperty(property):
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class C(object):
#classproperty
def x(cls):
return 1
assert C.x == 1
assert C().x == 1
Reading the Python 2.2 release notes, I find the following.
The get method [of a property] won't be called when
the property is accessed as a class
attribute (C.x) instead of as an
instance attribute (C().x). If you
want to override the __get__ operation
for properties when used as a class
attribute, you can subclass property -
it is a new-style type itself - to
extend its __get__ method, or you can
define a descriptor type from scratch
by creating a new-style class that
defines __get__, __set__ and
__delete__ methods.
NOTE: The below method doesn't actually work for setters, only getters.
Therefore, I believe the prescribed solution is to create a ClassProperty as a subclass of property.
class ClassProperty(property):
def __get__(self, cls, owner):
return self.fget.__get__(None, owner)()
class foo(object):
_var=5
def getvar(cls):
return cls._var
getvar=classmethod(getvar)
def setvar(cls,value):
cls._var=value
setvar=classmethod(setvar)
var=ClassProperty(getvar,setvar)
assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3
However, the setters don't actually work:
foo.var = 4
assert foo.var == foo._var # raises AssertionError
foo._var is unchanged, you've simply overwritten the property with a new value.
You can also use ClassProperty as a decorator:
class foo(object):
_var = 5
#ClassProperty
#classmethod
def var(cls):
return cls._var
#var.setter
#classmethod
def var(cls, value):
cls._var = value
assert foo.var == 5
Is it possible to use the property() function with classmethod decorated functions?
No.
However, a classmethod is simply a bound method (a partial function) on a class accessible from instances of that class.
Since the instance is a function of the class and you can derive the class from the instance, you can can get whatever desired behavior you might want from a class-property with property:
class Example(object):
_class_property = None
#property
def class_property(self):
return self._class_property
#class_property.setter
def class_property(self, value):
type(self)._class_property = value
#class_property.deleter
def class_property(self):
del type(self)._class_property
This code can be used to test - it should pass without raising any errors:
ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')
And note that we didn't need metaclasses at all - and you don't directly access a metaclass through its classes' instances anyways.
writing a #classproperty decorator
You can actually create a classproperty decorator in just a few lines of code by subclassing property (it's implemented in C, but you can see equivalent Python here):
class classproperty(property):
def __get__(self, obj, objtype=None):
return super(classproperty, self).__get__(objtype)
def __set__(self, obj, value):
super(classproperty, self).__set__(type(obj), value)
def __delete__(self, obj):
super(classproperty, self).__delete__(type(obj))
Then treat the decorator as if it were a classmethod combined with property:
class Foo(object):
_bar = 5
#classproperty
def bar(cls):
"""this is the bar attribute - each subclass of Foo gets its own.
Lookups should follow the method resolution order.
"""
return cls._bar
#bar.setter
def bar(cls, value):
cls._bar = value
#bar.deleter
def bar(cls):
del cls._bar
And this code should work without errors:
def main():
f = Foo()
print(f.bar)
f.bar = 4
print(f.bar)
del f.bar
try:
f.bar
except AttributeError:
pass
else:
raise RuntimeError('f.bar must have worked - inconceivable!')
help(f) # includes the Foo.bar help.
f.bar = 5
class Bar(Foo):
"a subclass of Foo, nothing more"
help(Bar) # includes the Foo.bar help!
b = Bar()
b.bar = 'baz'
print(b.bar) # prints baz
del b.bar
print(b.bar) # prints 5 - looked up from Foo!
if __name__ == '__main__':
main()
But I'm not sure how well-advised this would be. An old mailing list article suggests it shouldn't work.
Getting the property to work on the class:
The downside of the above is that the "class property" isn't accessible from the class, because it would simply overwrite the data descriptor from the class __dict__.
However, we can override this with a property defined in the metaclass __dict__. For example:
class MetaWithFooClassProperty(type):
#property
def foo(cls):
"""The foo property is a function of the class -
in this case, the trivial case of the identity function.
"""
return cls
And then a class instance of the metaclass could have a property that accesses the class's property using the principle already demonstrated in the prior sections:
class FooClassProperty(metaclass=MetaWithFooClassProperty):
#property
def foo(self):
"""access the class's property"""
return type(self).foo
And now we see both the instance
>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>
and the class
>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>
have access to the class property.
Python 3!
See #Amit Portnoy's answer for an even cleaner method in python >= 3.9
Old question, lots of views, sorely in need of a one-true Python 3 way.
Luckily, it's easy with the metaclass kwarg:
class FooProperties(type):
#property
def var(cls):
return cls._var
class Foo(object, metaclass=FooProperties):
_var = 'FOO!'
Then, >>> Foo.var
'FOO!'
There is no reasonable way to make this "class property" system to work in Python.
Here is one unreasonable way to make it work. You can certainly make it more seamless with increasing amounts of metaclass magic.
class ClassProperty(object):
def __init__(self, getter, setter):
self.getter = getter
self.setter = setter
def __get__(self, cls, owner):
return getattr(cls, self.getter)()
def __set__(self, cls, value):
getattr(cls, self.setter)(value)
class MetaFoo(type):
var = ClassProperty('getvar', 'setvar')
class Foo(object):
__metaclass__ = MetaFoo
_var = 5
#classmethod
def getvar(cls):
print "Getting var =", cls._var
return cls._var
#classmethod
def setvar(cls, value):
print "Setting var =", value
cls._var = value
x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x
The knot of the issue is that properties are what Python calls "descriptors". There is no short and easy way to explain how this sort of metaprogramming works, so I must point you to the descriptor howto.
You only ever need to understand this sort of things if you are implementing a fairly advanced framework. Like a transparent object persistence or RPC system, or a kind of domain-specific language.
However, in a comment to a previous answer, you say that you
need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.
It seems to me, what you really want is an Observer design pattern.
Setting it only on the meta class doesn't help if you want to access the class property via an instantiated object, in this case you need to install a normal property on the object as well (which dispatches to the class property). I think the following is a bit more clear:
#!/usr/bin/python
class classproperty(property):
def __get__(self, obj, type_):
return self.fget.__get__(None, type_)()
def __set__(self, obj, value):
cls = type(obj)
return self.fset.__get__(None, cls)(value)
class A (object):
_foo = 1
#classproperty
#classmethod
def foo(cls):
return cls._foo
#foo.setter
#classmethod
def foo(cls, value):
cls.foo = value
a = A()
print a.foo
b = A()
print b.foo
b.foo = 5
print a.foo
A.foo = 10
print b.foo
print A.foo
Half a solution, __set__ on the class does not work, still. The solution is a custom property class implementing both a property and a staticmethod
class ClassProperty(object):
def __init__(self, fget, fset):
self.fget = fget
self.fset = fset
def __get__(self, instance, owner):
return self.fget()
def __set__(self, instance, value):
self.fset(value)
class Foo(object):
_bar = 1
def get_bar():
print 'getting'
return Foo._bar
def set_bar(value):
print 'setting'
Foo._bar = value
bar = ClassProperty(get_bar, set_bar)
f = Foo()
#__get__ works
f.bar
Foo.bar
f.bar = 2
Foo.bar = 3 #__set__ does not
Because I need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.
Do you have access to at least one instance of the class? I can think of a way to do it then:
class MyClass (object):
__var = None
def _set_var (self, value):
type (self).__var = value
def _get_var (self):
return self.__var
var = property (_get_var, _set_var)
a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var
Give this a try, it gets the job done without having to change/add a lot of existing code.
>>> class foo(object):
... _var = 5
... def getvar(cls):
... return cls._var
... getvar = classmethod(getvar)
... def setvar(cls, value):
... cls._var = value
... setvar = classmethod(setvar)
... var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3
The property function needs two callable arguments. give them lambda wrappers (which it passes the instance as its first argument) and all is well.
Here's a solution which should work for both access via the class and access via an instance which uses a metaclass.
In [1]: class ClassPropertyMeta(type):
...: #property
...: def prop(cls):
...: return cls._prop
...: def __new__(cls, name, parents, dct):
...: # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
...: dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
...: dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
...: return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
...:
In [2]: class ClassProperty(object):
...: __metaclass__ = ClassPropertyMeta
...: _prop = 42
...: def __getattr__(self, attr):
...: raise Exception('Never gets called')
...:
In [3]: ClassProperty.prop
Out[3]: 42
In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1
AttributeError: can't set attribute
In [5]: cp = ClassProperty()
In [6]: cp.prop
Out[6]: 42
In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1
<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
6 # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
7 dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8 dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
9 return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
AttributeError: can't set attribute
This also works with a setter defined in the metaclass.
I found one clean solution to this problem. It's a package called classutilities (pip install classutilities), see the documentation here on PyPi.
Consider example:
import classutilities
class SomeClass(classutilities.ClassPropertiesMixin):
_some_variable = 8 # Some encapsulated class variable
#classutilities.classproperty
def some_variable(cls): # class property getter
return cls._some_variable
#some_variable.setter
def some_variable(cls, value): # class property setter
cls._some_variable = value
You can use it on both class level and instance level:
# Getter on class level:
value = SomeClass.some_variable
print(value) # >>> 8
# Getter on instance level
inst = SomeClass()
value = inst.some_variable
print(value) # >>> 8
# Setter on class level:
new_value = 9
SomeClass.some_variable = new_value
print(SomeClass.some_variable) # >>> 9
print(SomeClass._some_variable) # >>> 9
# Setter on instance level
inst = SomeClass()
inst.some_variable = new_value
print(SomeClass.some_variable) # >>> 9
print(SomeClass._some_variable) # >>> 9
print(inst.some_variable) # >>> 9
print(inst._some_variable) # >>> 9
As you can see, it works correctly under all circumstances.
Based on https://stackoverflow.com/a/1800999/2290820
class MetaProperty(type):
def __init__(cls, *args, **kwargs):
super()
#property
def praparty(cls):
return cls._var
#praparty.setter
def praparty(cls, val):
cls._var = val
class A(metaclass=MetaProperty):
_var = 5
print(A.praparty)
A.praparty = 6
print(A.praparty)
For a functional approach pre Python 3.9 you can use this:
def classproperty(fget):
return type(
'classproperty',
(),
{'__get__': lambda self, _, cls: fget(cls), '__module__': None}
)()
class Item:
a = 47
#classproperty
def x(cls):
return cls.a
Item.x
After searching different places, I found a method to define a classproperty
valid with Python 2 and 3.
from future.utils import with_metaclass
class BuilderMetaClass(type):
#property
def load_namespaces(self):
return (self.__sourcepath__)
class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
__sourcepath__ = 'sp'
print(BuilderMixin.load_namespaces)
Hope this can help somebody :)
A code completion friendly solution for Python < 3.9
from typing import (
Callable,
Generic,
TypeVar,
)
T = TypeVar('T')
class classproperty(Generic[T]):
"""Converts a method to a class property.
"""
def __init__(self, f: Callable[..., T]):
self.fget = f
def __get__(self, instance, owner) -> T:
return self.fget(owner)
Here is my solution that also caches the class property
class class_property(object):
# this caches the result of the function call for fn with cls input
# use this as a decorator on function methods that you want converted
# into cached properties
def __init__(self, fn):
self._fn_name = fn.__name__
if not isinstance(fn, (classmethod, staticmethod)):
fn = classmethod(fn)
self._fn = fn
def __get__(self, obj, cls=None):
if cls is None:
cls = type(obj)
if (
self._fn_name in vars(cls) and
type(vars(cls)[self._fn_name]).__name__ != "class_property"
):
return vars(cls)[self._fn_name]
else:
value = self._fn.__get__(obj, cls)()
setattr(cls, self._fn_name, value)
return value
Here's my suggestion. Don't use class methods.
Seriously.
What's the reason for using class methods in this case? Why not have an ordinary object of an ordinary class?
If you simply want to change the value, a property isn't really very helpful is it? Just set the attribute value and be done with it.
A property should only be used if there's something to conceal -- something that might change in a future implementation.
Maybe your example is way stripped down, and there is some hellish calculation you've left off. But it doesn't look like the property adds significant value.
The Java-influenced "privacy" techniques (in Python, attribute names that begin with _) aren't really very helpful. Private from whom? The point of private is a little nebulous when you have the source (as you do in Python.)
The Java-influenced EJB-style getters and setters (often done as properties in Python) are there to facilitate Java's primitive introspection as well as to pass muster with the static language compiler. All those getters and setters aren't as helpful in Python.

Automatically apply getter and setter to new object variable

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.

Catching changes to a mutable attribute in python

I am using properties to execute some code every time there is a change to an attribute, like this:
class SomeClass(object):
def __init__(self,attr):
self._attr = attr
#property
def attr(self):
return self._attr
#attr.setter
def attr(self,value):
if self._attr != value:
self._on_change()
self._attr = value
def _on_change(self):
print "Do some code here every time attr changes"
And this works great:
>>> a = SomeClass(5)
>>> a.attr = 10
Do some code here every time attr changes
But if I store a mutable object in attr instead, attr can be modified directly, bypassing the setter and my change-detection code:
class Container(object):
def __init__(self,data):
self.data = data
>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
>>>
Let's assume that attr is only ever going to be used to store an object of type Container. Is there an elegant way to modify SomeClass and/or Container to make SomeClass execute _on_change whenever the Container object stored in attr is modified? In other words, I want my output to be:
>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
Do some code here every time attr changes
Here is another solution. Some kind of proxy class. You dont need to modify any classes to monitor attributes changes in them, only wrap object in ChangeTrigger derived class with ovverriden _on_change function:
class ChangeTrigger(object):
def __getattr__(self, name):
obj = getattr(self.instance, name)
# KEY idea for catching contained class attributes changes:
# recursively create ChangeTrigger derived class and wrap
# object in it if getting attribute is class instance/object
if hasattr(obj, '__dict__'):
return self.__class__(obj)
else:
return obj
def __setattr__(self, name, value):
if getattr(self.instance, name) != value:
self._on_change(name, value)
setattr(self.instance, name, value)
def __init__(self, obj):
object.__setattr__(self, 'instance', obj)
def _on_change(self, name, value):
raise NotImplementedError('Subclasses must implement this method')
Example:
class MyTrigger(ChangeTrigger):
def _on_change(self, name, value):
print "New value for attr %s: %s" % (name, value)
class Container(object):
def __init__(self, data):
self.data = data
class SomeClass(object):
attr_class = 100
def __init__(self, attr):
self.attr = attr
self.attr_instance = 5
>>> a = SomeClass(5)
>>> a = MyTrigger(a)
>>>
>>> a.attr = 10
New value for attr attr: 10
>>>
>>> b = SomeClass(Container(5))
>>> b = MyTrigger(b)
>>>
>>> b.attr.data = 10
New value for attr data: 10
>>> b.attr_class = 100 # old value = new value
>>> b.attr_instance = 100
New value for attr attr_instance: 100
>>> b.attr.data = 10 # old value = new value
>>> b.attr.data = 100
New value for attr data: 100
Here is a version of SomeClass and Container that I think has the behavior you are looking for. The idea here being that modifications to Container will call the _on_change() function of the SomeClass instance that is associated with it:
class Container(object):
def __init__(self, data):
self.data = data
def __setattr__(self, name, value):
if not hasattr(self, name) or getattr(self, name) != value:
self.on_change()
super(Container, self).__setattr__(name, value)
def on_change(self):
pass
class SomeClass(object):
def __init__(self, attr):
self._attr = attr
self._attr.on_change = self._on_change
#property
def attr(self):
return self._attr
#attr.setter
def attr(self,value):
if self._attr != value:
self._on_change()
self._attr = value
def _on_change(self):
print "Do some code here every time attr changes"
Example:
>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
Do some code here every time attr changes
>>> b.attr.data = 10 # on_change() not called if the value isn't changing
>>> b.attr.data2 = 'foo' # new properties being add result in an on_change() call
Do some code here every time attr changes
Note that the only change to SomeClass was the second line in __init__(), I just included the full code for completeness and easy testing.
If you want to track changes and don't want to mess with juggling with on_change() methods in different classes you could use functools.partial in the way shown starting here.
This way you can wrap your data and hide it totally. Get/change will be possible only via some methods melded inside that object.
NB: python has no private properties and on convention that we all are grownups and act against rules. In your case users of your api shouldn't change data on container (after creation) directly.
NB: here for those who may be interested in other ways...

In Python, count the number of variables in a class or prevent adding new class variables

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

Categories