Related
I have given up memoization of a class as a bag-of-worms that I didn't want to explore and here is one example of why. The question I ask is "how does one extend or inherit from a memoized class" but it's very possible I have made a mistake. The memoize class below is a cut-down version of the one by brandizzi in How can I memoize a class instantiation in Python? and googling the subject finds more involved such classes.
class memoize(object):
def __init__(self, cls):
self.cls = cls
# I didn't understand why this was needed
self.__dict__.update(cls.__dict__)
# bit about static methods not needed
def __call__(self, *args):
try:
self.cls.instances
except:
self.cls.instances = {}
key = '//'.join(map(str, args))
if key not in self.cls.instances:
self.cls.instances[key] = self.cls(*args)
return self.cls.instances[key]
class Foo():
def __init__(self,val):
self.val = val
def __repr__(self):
return "{}<{},{}>".format(self.__class__.__name__,self.val,id(self))
class Bar(Foo):
def __init__(self,val):
super().__init__(val)
f1,f2,f3 = [Foo(i) for i in (0,0,1)]
print([f1,f2,f3])
b1,b2,b3 = [Bar(i) for i in (0,0,1)]
print([b1,b2,b3])
# produces exactly what I expect
# [Foo<0,3071981964>, Foo<0,3071982092>, Foo<1,3071982316>]
# [Bar<0,3071983340>, Bar<0,3071983404>, Bar<1,3071983436>]
Foo = memoize(Foo)
f1,f2,f3 = [Foo(i) for i in (0,0,1)]
print([f1,f2,f3])
b1,b2,b3 = [Bar(i) for i in (0,0,1)]
print([b1,b2,b3])
# and now Foo has been memoized so Foo(0) always produces the same object
# [Foo<0,3071725804>, Foo<0,3071725804>, Foo<1,3071726060>]
# [Bar<0,3071711916>, Bar<0,3071711660>, Bar<1,3071725644>]
# this produces a compilation error that I don't understand
class Baz(Foo):
def __init__(self,val):
super().__init__(val)
# Traceback (most recent call last):
# File "/tmp/foo.py", line 49, in <module>
# class Baz(Foo):
# TypeError: __init__() takes 2 positional arguments but 4 were given
This "recipe" is indeed a very bad idea - once you rebind Foo to memoize(Foo), Foo is a memoize instance and not class Foo anymore. This breaks all expectations wrt/ python's type and the whole object model. In this case, it about how the class statement works. Actually, this:
class Titi():
x = 42
def toto(self):
print(self.x)
is syntactic sugar for:
def toto(self):
print(self.x)
Titi = type("Titi", (object,), {x:42, toto:toto})
del toto
Note that this happens at runtime (like everything in Python except parsing / bytecode compilation), and that type is a class so calling type creates a new class which is a type instance (this is named a 'metaclass' - the class of a class - and type is the default metaclass).
So with Foo being now a memoize instance instead of a Type instance, and since memoize is not a proper metaclass (it's __init__ methods signature is incompatible), the whole thing just cannot work.
To get this to work, you'd have to make memoize a proper metaclass (this is a simplified example assuming a single arg named param but it can be generalized if you want to):
class FooType(type):
def __new__(meta, name, bases, attrs):
if "_instances" not in attrs:
attrs["_instances"] = dict()
return type.__new__(meta, name, bases, attrs)
def __call__(cls, param):
if param not in cls._instances:
cls._instances[param] = super(FooType, cls).__call__(param)
return cls._instances[param]
class Foo(metaclass=FooType):
def __init__(self, param):
self._param = param
print("%s init(%s)" % (self, param))
def __repr__(self):
return "{}<{},{}>".format(self.__class__.__name__, self._param, id(self))
class Bar(Foo):
pass
f1,f2,f3 = [Foo(i) for i in (0,0,1)]
print([f1,f2,f3])
b1,b2,b3 = [Bar(i) for i in (0,0,1)]
print([b1,b2,b3])
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.
I would like to know if there is an easy way to do some identical edits on several methods of a class. An example :
class Dog():
def __init__(self):
self.name = 'abc'
self.age = 1
def setName(self, newValue):
self.name = newValue
def setAge(self, newValue):
self.age = newValue
class TalkingDog(Dog):
def __init__(self):
super().__init__()
# The end is in pseudo code :
for method in TalkingDog.allMethods :
method = method + 'print('I have been edited !')'
I know that I can also overwrite each method but in a situation with tens of methods, that will be a little boring...
So I tried this :
class TalkingDog(Dog):
def __init__(self):
super().__init__()
for method in self.__dir__():
if method.startswith('set'):
oldMethod = getattr(self, method)
def _newMethod(newValue):
oldMethod(newValue)
print('I have been edited !')
setattr(self, method, _newMethod)
a = TalkingDog()
print(a.setName) >>> <function TalkingDog.__init__.<locals>._newMethod at 0x0000000002C350D0>
That almost works but setName is not anymore a method. It's an attribute which contains a function. I completely understand why but I'm trying to get a cleaner result. With that result, I risk of having problems later. For example I can't use the library pickle with that object (got the error _pickle.PicklingError: Can't pickle <function TalkingDog.__init__.<locals>._newMethod at 0x00000000003DCBF8>: attribute lookup _newMethod on __main__ failed).
The Pythonic way to do this is probably to use the descriptor protocol, which is also what properties use:
class VocalAttribute:
def __init__(self, name, feedback):
"""Called when you first create the descriptor."""
self.name = name # the name of the attribute 'behind' the property
self.feedback = feedback # the feedback to show when the value changes
def __get__(self, obj):
"""Called when you get the descriptor value."""
return getattr(obj, self.name)
def __set__(self, obj, value):
"""Called when you set the descriptor value."""
prev = getattr(obj, self.name, None)
if value != prev:
setattr(obj, self.name, value)
print(self.feedback)
def __delete__(self, obj):
"""Called when you delete the descriptor value."""
delattr(obj, self.name)
class Foo:
bar = VocalAttribute('_bar', 'I have been edited!')
foo = Foo()
print('1.')
foo.bar = 'hello'
print('2.')
foo.bar = 'hello'
print('3.')
foo.bar = 'world'
Output:
1.
I have been edited!
2.
3.
I have been edited!
Note that this only gives feedback when the new value is different to the old one - you can tweak the behaviour as needed in __set__. It also means you can directly read from and assign to foo.bar, rather than needing to call getters and setters (what is this, Java?)
since decorator could explicit called here a way to use it:
def updater(obj, call_back, call_back_args=(), call_back_kw=None, replace=False):
# ability to be called on the fly with different args and kw for the callback
# now it returns the updated obj (instance or class)
# but could a be factory returning a new obj in this case make a copy of obj, update this coy and return it
def update_function(fn, *args, **kw):
def wrapper(*args, **kw):
if replace:
# call only the callback
res = call_back(*call_back_args, **call_back_kw)
else:
res = fn(*args, **kw)
call_back(*call_back_args, **call_back_kw)
return res
return wrapper
# get all methods of the obj
# and apply update_function (a decorator) to all methods
for name, m in inspect.getmembers(
obj, predicate=lambda x: inspect.isfunction(x) or inspect.ismethod(x)):
# make the selection here
# could be made on the name for instance
if not name.startswith('_'):
new_m = update_function(m)
setattr(obj, name, new_m)
return obj
# declare a callback
def call_back(*args, **kw):
# simple callback
print("I have been edited and called with %r args and %r kw " % (args, kw))
a = Dog()
# could be called on instance or class
# apply the callback on all "public" methods
updater(
a,
call_back,
call_back_args=(2, 3, 4),
call_back_kw={"kw1": "v_1"}
)
I'm trying to do something like this:
inst = AnyClass()
remember_last_method(inst)
inst.foo()
inst.bar()
print inst.last_method # print bar
inst.foo
print inst.last_method # print foo
inst.remember_last_method = False
inst.bar()
print inst.last_method # print foo
inst.remember.last_method = True
Any suggestion to write the remember_last_method function?
First edit:
seems that votes are negatives...
Here is the code I started to write, if it can clarify the question:
def remember_last_method_get(cls):
"""
Subclass cls in a way that remeber last method get by instances
>>> #remember_last_method_get
... class Foo(object):
... def bar(self):
... pass
... def baz(self):
... pass
>>> foo = Foo()
>>> foo.bar()
>>> foo.baz()
>>> print foo.last_method_get
baz
>>> m = foo.bar # get a method without calling it
>>> print foo.last_method_get
bar
"""
class clsRememberLastMethodGet(cls):
def __getattribute__(self,name):
attr = cls.__getattribute__(self,name)
if callable(attr):
self.last_method_get = name
return attr
return clsRememberLastMethodGet
if __name__ == '__main__':
import doctest
doctest.testmod()
Works on instances and not on classes as I want, and doesn't have the remember_last_method=True/False attribute
Second edit:
Here is a metaclass that do the job (only for method called, not
get, which is better):
class RememberLastMethod(type):
def __init__(self, name, bases, d):
type.__init__(self, name, bases, d)
for name,attr in d.iteritems():
if not callable(attr) or name.startswith('_'):
continue
def create_new_attr(name,attr):
def new_attr(self,*args,**kwargs):
if self.remember_last_method:
self.last_method = name
return attr(self,*args,**kwargs)
return new_attr
setattr(self,name,create_new_attr(name,attr))
orig__init__ = self.__init__
def new__init__(self,*args,**kwargs):
self.remember_last_method = True
self.last_method = None
orig__init__(self)
self.__init__ = new__init__
class AnyClass(object):
__metaclass__ = RememberLastMethod
def foo(self):
pass
def bar(self):
pass
# Call two method, inst.last_method is the last
inst = AnyClass()
inst.foo()
inst.bar()
assert inst.last_method == "bar"
# Call a new method, changes inst.last_method.
inst.foo()
assert inst.last_method == "foo"
# Stop changing inst.last_method.
inst.remember_last_method = False
inst.bar()
assert inst.last_method == "foo"
# Changing last_method again.
inst.remember_last_method = True
inst.bar()
assert inst.last_method == "bar"
# Work with reference to method as well
method = inst.foo
inst.remember_last_method = False
method()
assert inst.last_method == "bar"
inst.remember_last_method = True
method()
assert inst.last_method == "foo"
Thrid edit:
Here is a function that take an instance as argument and do the same work as the metaclass:
def remember_last_method(inst):
inst.remember_last_method = True
cls = inst.__class__
for name in dir(inst):
if name.startswith('_'):
continue
attr = getattr(inst,name)
if not callable(attr):
continue
def create_new_attr(name,attr):
def new_attr(self,*args,**kwargs):
if self.remember_last_method:
self.last_method = name
return attr(*args,**kwargs)
return new_attr
setattr(cls,name,create_new_attr(name,attr))
class AnyClass(object):
def foo(self):
pass
def bar(self):
pass
inst = AnyClass()
remember_last_method(inst)
# Call two method, inst.last_method is the last
inst.foo()
inst.bar()
assert inst.last_method == "bar"
# Call a new method, changes inst.last_method.
inst.foo()
assert inst.last_method == "foo"
# Stop changing inst.last_method.
inst.remember_last_method = False
inst.bar()
assert inst.last_method == "foo"
# Changing last_method again.
inst.remember_last_method = True
inst.bar()
assert inst.last_method == "bar"
# Work with reference to method as well
method = inst.foo
inst.remember_last_method = False
method()
assert inst.last_method == "bar"
inst.remember_last_method = True
method()
assert inst.last_method == "foo"
You can implement this yourself for each method:
class X(object):
def __init__(self):
self.last_method = None
self.should_store_last_method = True
def set_last_method(self, meth):
if self.should_store_last_method:
self.last_method = meth
def store_last_method(self, should_store):
self.should_store_last_method = should_store
def one(self):
self.set_last_method(self.one)
print("ONE")
def two(self):
self.set_last_method(self.two)
print("TWO")
to use it:
x = X()
x.one()
# ONE
print x.last_method
# <bound method X.one of <__main__.X object at 0x1035f8210>>
x.last_method()
# ONE
x.two()
# TWO
print x.last_method
# <bound method X.two of <__main__.X object at 0x1035f8210>>
x.last_method()
# TWO
x.store_last_method(False)
x.one()
# ONE
print x.last_method
# <bound method X.one of <__main__.X object at 0x1035f8210>>
gives:
ONE
<bound method X.one of <__main__.X object at 0x1035f8210>>
ONE
TWO
<bound method X.two of <__main__.X object at 0x1035f8210>>
TWO
ONE
<bound method X.one of <__main__.X object at 0x1035f8210>>
A metaclass is definitely the way to go. Your metaclass implementation is a good one, but it falls over in a couple of edge cases. That is there are several things that are callable, but will not be turned into instance methods that can exist on a class. For instance, you might have a staticmethod or a classmethod or even define a class within the parent class, or most simply an object of a class that has a __call__ method.
Your function/property implementation avoids these problems, but with the downside of recording these function calls. These functions do not access the object on which can they can be found, so do you really want to be recording them?
I've provided an implementation below. This metaclass only works with Python 3. To convert to Python 2 you must remove the name arg from the __init__ and __new__ methods on MethodLoggerMetaclass. You will also have to use the __metaclass__ name instead of providing it as an argument on the class declaration line.
from types import FunctionType, MethodType
from collections import Callable
import builtins
class MethodLoggerMetaclass(type):
"""Records the last method that was called on a class as the base function
(called an unbound method in python 2.x)
By default _last_method is used to record which method was last called.
Use `class MyClass(metaclass=MethodLoggerMetaclass, name="new_name")' to
change this behaviour.
Set record_superclass to True to also record the methods on superclasses.
Set record_hidden to True to also record attributes beginning with s
single underscore.
Set record_callable to True to also record callable objects that are *not*
instance methods. eg. static methods and class methods."""
def __init__(self, classname, parents, attributes, name="_last_method",
record_superclass=False, record_hidden=False,
record_callable=False):
type.__init__(self, classname, parents, attributes)
method_logger_names[self] = name
if record_superclass:
for attr, value, needs_self in get_superclass_functions(self,
record_hidden, record_callable):
type.__setattr__(self, attr, wrap(value, name, needs_self))
def __new__(metaclass, classname, parents, attributes,
name="_last_method", record_superclass=False,
record_hidden=False, record_callable=False):
types = FunctionType if not record_callable else Callable
for attr, value in attributes.items():
if record(attr, value, record_hidden, types):
attributes[attr] = wrap(value, name, isinstance(value,
FunctionType))
attributes[name] = MethodLoggerProperty()
return type.__new__(metaclass, classname, parents, attributes)
def __setattr__(self, attr, value):
"""Used to wrap functions that are added to the class after class
creation."""
if isinstance(value, FunctionType):
type.__setattr__(self, attr, wrap(value,
method_logger_names[self], True))
else:
type.__setattr__(self, attr, value)
class MethodLogger:
"""Used to record the last method called on a instance. Method stored as
base function. Has convenience properties for getting just the name or as a
method, or the unwrapped function.
Calling the provided function or method will also record the call if record
is set to True."""
_function = None
record = True
def __init__(self, instance):
self.instance = instance
#property
def function(self):
return wrap(self._function, method_logger_names[type(self.instance)])
#function.setter
def function(self, function):
if self.record:
self._function = function
#property
def unwrapped_function(self):
return self._function
#property
def method(self):
if self._function:
return MethodType(self.function, self.instance)
#property
def name(self):
if self._function:
return self._function.__name__
def __str__(self):
return "MethodLogger(instance={}, function={}, record={})".format(
self.instance, self._function, self.record)
__repr__ = __str__
class MethodLoggerProperty:
"""Provides initial MethodLogger for new instances of a class"""
def __get__(self, instance, cls=None):
method_logger = MethodLogger(instance)
setattr(instance, method_logger_names[cls], method_logger)
return method_logger
def wrap(function, method_logger_name, needs_self):
"""Wraps a function and in a logging function, and makes the wrapper
appear as the original function."""
if needs_self:
def wrapper(self, *args, **kwargs):
if getattr(self, method_logger_name).record:
getattr(self, method_logger_name).function = function
return function(self, *args, **kwargs)
else:
def wrapper(self, *args, **kwargs):
if getattr(self, method_logger_name).record:
getattr(self, method_logger_name).function = function
return function(*args, **kwargs)
wrapper.__name__ = getattr(function, "__name__", str(function))
wrapper.__doc__ = function.__doc__
return wrapper
# used to store name where the method logger is stored for each class
method_logger_names = {}
def record(attr, value, record_hidden, types=FunctionType):
"""Returns whether an attribute is a method and should be logged.
Never returns true for "dunder" attributes (names beginning with __)"""
return isinstance(value, types) and not attr.startswith("__") and \
(record_hidden or not attr.startswith("_"))
def get_superclass_functions(cls, include_hidden, include_callable):
"""Finds all functions derived from the superclasses of a class. Gives
the name under which the function was found and the function itself.
Returns tuples of (attribute name, function, if the function needs an
object instance). If `include_callable' is False then the the function
always needs an object instance."""
base_types = FunctionType if not include_callable else Callable
attrs = set(vars(cls).keys())
for superclass in cls.mro()[1:-1]: # exclude actual class and object
types = (base_types if not hasattr(builtins, superclass.__name__) else
Callable)
for attr, value in vars(superclass).items():
if attr not in attrs and record(attr, value, include_hidden, types):
attrs.add(attr)
yield attr, value, (isinstance(value, FunctionType) or
hasattr(builtins, superclass.__name__))
Example usage:
class MethodLoggerList(list, metaclass=MethodLoggerMetaclass,
name="_method_logger", record_superclass=True):
def f(self, kwarg="keyword argument"):
print(self, kwarg)
def g(self):
pass
# example use
t = MethodLoggerList()
print(t._method_logger)
t.f()
t.f("another value")
print(t._method_logger)
# note that methods on superclass are not recorded by default
t.append(0)
print(t._method_logger)
# won't record dunder/magic methods at all
t += [1]
print(t._method_logger)
# stop recording
t._method_logger.record = False
t.g()
print(t._method_logger)
# add methods to class after class creation, and still record them
def h(self):
pass
MethodLoggerList.h = h
t._method_logger.record = True
t.h()
print(t._method_logger)
# also records lambdas
MethodLoggerList.i = lambda self: None
t.i()
print(t._method_logger)
# does not record monkey-patched methods
def j():
pass
t.j = j
t.j()
print(t._method_logger)
# does record method or function access from _last_method
method = t._method_logger.method
t.g()
method()
print(t._method_logger)
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.