We use the classmethod builtin to tie methods to the class rather than the instance. This is really useful for a variety of things, like alternate contructors. However, sometimes you don't want to expose the method on the instances, just on the class.
For instance, it usually doesn't make a lot of sense to have direct access to alternate constructors from instances; the main constructor, via the __call__ method, is only available on the class. Why? So that instances call implement their own call semantics. This is similar to why the methods (and other class attributes) on namedtuples all have names starting with an underscore.
To restrict a method just to the class, currently you must use a metaclass. Most people consider this black magic so they just use @classmethod. Furthermore, implementing a metaclass is simply more verbose than decorating a method with classmethod. Plus you have to deal with things like metaclass conflicts and the fact that metaclasses are inherited.
So...here is a sibling to classmethod that makes a method class-only without introducing metaclass complexity. You use it the same way as you would classmethod.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | import inspect
#class metamethod:
class classonlymethod:
"""Like a classmethod but does not show up on instances.
This is an alternative to putting the methods on a metaclass. It
is especially meaningful for alternate constructors.
"""
def __init__(self, method):
self.method = method
self.descr = classmethod(method)
def __get__(self, obj, cls):
name = self.method.__name__
getattr_static = inspect.getattr_static
if obj is not None:
# look up the attribute, but skip cls
dummy = type(cls.__name__, cls.__bases__, {})
attr = getattr_static(dummy(), name, None)
getter = getattr_static(attr, '__get__', None)
# try data descriptors
if (getter and getattr_static(attr, '__set__', False)):
return getter(attr, obj, cls)
# try the instance
try:
instance_dict = object.__getattribute__(obj, "__dict__")
except AttributeError:
pass
else:
try:
return dict.__getitem__(instance_dict, name)
except KeyError:
pass
# try non-data descriptors
if getter is not None:
return getter(attr, obj, cls)
raise AttributeError(name)
else:
descr = vars(self)['descr']
return descr.__get__(obj, cls)
#################################################
# tests
import unittest
class ClassOnlyMethodTests(unittest.TestCase):
def test_class(self):
class Spam:
@classonlymethod
def from_nothing(cls):
return Spam()
spam = Spam.from_nothing()
self.assertIsInstance(spam, Spam)
def test_instance(self):
class Spam:
@classonlymethod
def from_nothing(cls):
return Spam()
with self.assertRaises(AttributeError):
Spam().from_nothing()
if __name__ == '__main__':
unittest.main()
|
Class-only methods is the topic of a python-ideas thread:
http://mail.python.org/pipermail/python-ideas/2013-March/019848.html
I proposed the concept there. It became clear that, while useful in a few ways, class-only methods would not be useful frequently enough to be added to the standard library, even if tucked away in a corner of it. They are similar enough to normal class methods that the burden of explaining the distinction would be disproportionate to the gain.
However, the implementation is relatively non-trivial which is a key bar that something must clear for stdlib inclusion. If you have use-cases where class-only methods are a better fit for you over normal class methods and over methods on a metaclass, write them down here. If there enough usefulness, I'll bring it back up on python-ideas.
I'm also keeping the code in a mercurial repo:
https://bitbucket.org/ericsnowcurrently/odds_and_ends/src/default/metamethod.py