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()