today I did something like this:
class MyClass(object):
@classmethod
def myClassMethod(self):
print "ham"
myProperty = property(myClassMethod, None, None)
As many of you know this doesn't work and returns a TypeError: the
object passed to the property is not a callable function but a
classmethod object, which isn't callable at all. So, how do I do this?
Ultimately all I want is a non-callable class-level attribute
MyClass.myProperty that gives the result of MyClass.myClassMethod().
Can it be done?
Manu
<ot> Usually, the first argument of classmethods is named 'cls' </ot>
> print "ham"
>
> myProperty = property(myClassMethod, None, None)
>
> As many of you know this doesn't work and returns a TypeError: the
> object passed to the property is not a callable function but a
> classmethod object, which isn't callable at all. So, how do I do this?
> Ultimately all I want is a non-callable class-level attribute
> MyClass.myProperty
properties *are* class attributes.
> that gives the result of MyClass.myClassMethod().
>
> Can it be done?
You could write your own custom descriptor. Or just use an additional
level of indirection, ie:
myProperty = property(lambda self: self.myClassMethod())
but since you already have myClassMethod available, I don't see the
point. What problem are you trying to solve exactly ?
That code runs fine for me. Although I doubt the behavior is what you
wanted to do.
> So, how do I do this?
> Ultimately all I want is a non-callable class-level attribute
> MyClass.myProperty that gives the result of MyClass.myClassMethod().
This works like what you seem to want (it's ugly):
class MyClass(object):
class _MyClass(object):
@classmethod
def myClassMethod(cls):
return 'ham'
@property
def myProperty(self):
return MyClass._MyClass.myClassMethod()
@classmethod
def myClassMethod(cls):
return MyClass._MyClass.myClassMethod()
@property
def myProperty(self):
return MyClass._MyClass.myClassMethod()
def __call__(self, *args, **kargs):
# this is __init__
return MyClass._MyClass(*args, **kargs)
# note this is NOT a real MyClass instantiation
MyClass = MyClass()
$ python -i ./strangeclass.py
>>> MyClass.myClassMethod()
'ham'
>>> MyClass.myProperty
'ham'
>>> mc = MyClass()
>>> mc.myProperty
'ham'
>>> mc.myClassMethod()
'ham'
Ugly, indeed. And an extreme case of arbitrary overcomplexification too :-/
(snip rube goldberg code)
properties affect instances, and classes are instances of types.
What you want is a new metaclass:
class MyType(type):
@property
def demo(class_):
return class_.a + 3
class MyClass(object):
__metaclass__ = MyType
a = 5
print MyClass.a, MyClass.demo
--Scott David Daniels
Scott....@Acm.Org
Can't think of anything simpler than that without meddling with
descriptor. I'm not even sure descriptor can help here as it seems
descriptor needs an instance? (I've just skimmed it, so I may be wrong)
The ugliness of the code hints to two possible reasons:
- there should be a better way
- if there isn't an easier way, then something is wrong the class' design
Hmmm... Rereading the OP's spec, I guess you understood it better than I
did - seems the OP wants to be able to call the "property" on the class
object itself - which won't work with the builtin property type. So my
own proposed solution won't do :-/
But still, "meddling with descriptor" is *way* simpler than your
proposed solution. Here's a simple non-binding descriptor that do the job:
class ClsProperty(object):
def __init__(self, fget):
if not isinstance(fget, (classmethod, staticmethod)):
raise ValueError(
"fget must be a classmethod or staticmethod"
)
self.fget = fget
def __get__(self, obj, cls=None):
if cls is None:
assert obj is not None
cls = type(obj)
return self.fget.__get__(obj, cls)()
# example use
class Foo(object):
@classmethod
def bar(cls):
return "%s.bar" % cls.__name__
quux = ClsProperty(bar)
> I'm not even sure descriptor can help here as it seems
> descriptor needs an instance?
wrt/ descriptors, no, they don't "need" an instance, at least for
non-binding descriptors. Else, MyClass.MyMethod would return the
MyClass.__dict__['MyMethod'] function, not an unbound method object !-)
Sorry, looks like I didn't read carefully enough. The above code won't
work if you intend to lookup the property directly on the class object,
ie "MyClass.myProperty". If that was your intention, you'll need a
custom descriptor. The following code should do the job, or at least get
you started:
# python 2.5.x
# the custom (non binding) descriptor
class ClsProperty(object):
def __init__(self, fget):
if not isinstance(fget, (classmethod, staticmethod)):
# XXX better error message
raise ValueError(
"fget must be a classmethod or staticmethod"
)
self.fget = fget
def __get__(self, obj, cls=None):
if cls is None:
assert obj is not None
cls = type(obj)
return self.fget.__get__(obj, cls)()
# helper -> a simple decorator
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClsProperty(func)
# example use
class Foo(object):
# the hard way
@classmethod
def bar(cls):
return "%s.bar" % cls.__name__
quux = ClsProperty(bar)
# the simple way
@classproperty
def baaz(cls):
return "%s.baaz" % cls
Given your example, this should be enough. If you need a binding
descriptor (one with a setter), you'll have to implement the __set__
method (and possibly __del__). Google for "python descriptor" to find
more doc about the descriptor protocol.
HTH