class-only methods without using metaclasses

This tweet from Raymond helped distill something that was already on my mind of late: https://twitter.com/raymondh/status/309442149588533248 For some uses of @classmethod it makes sense to expose the methods on instances too. On others I just can't see it. In those cases, using @classmethod is justifiably a practical substitute for putting the methods on a metaclass. In my mind "alternate constructors" fall into this category. Would it be worth trying to get the best of both worlds (not exposed on instances but without metaclasses)? I can imagine providing a classmethod-like decorator that does this and have an implementation below. One benefit to not exposing the class-only methods on instances is that they don't clutter the instance namespace nor run the risk of colliding with instance-specific names. Thoughts? -eric ----------------------------------------------------------------------- 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. """ # XXX or "metamethod" 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, NOTSET) 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)

On Thu, Mar 7, 2013 at 5:10 PM, Eric Snow <ericsnowcurrently@gmail.com> wrote:
Thoughts?
It's too much additional complexity to resolve a largely theoretical problem. Since class methods can be shadowed in instances, the fact they're accessible through the instances really doesn't hurt anything, and the distinction between a class method and a class only method would be too subtle to easily explain to anyone not already steeped in the details of descriptors and metaclasses. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Thu, Mar 7, 2013 at 1:21 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
and the distinction between a class method and a class only method would be too subtle to easily explain to anyone not already steeped in the details of descriptors and metaclasses.
This is definitely the big reason why it's not worth it over just using a metaclass or even just sticking with classmethods, marginally imperfect as they are for the theoretical use case. -eric

Nick Coghlan wrote:
It's too much additional complexity to resolve a largely theoretical problem.
In Python 2 you could get class-only methods like this: class Foo(object): class __metaclass__(type): def classmeth(cls): ... I'm mildly disappointed that this can't be done any more in Python 3. Sometimes you need genuine metaclass methods, e.g. __xxx__ methods for a class rather than an instance. -- Greg

On Fri, Mar 8, 2013 at 9:02 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Nick Coghlan wrote:
It's too much additional complexity to resolve a largely theoretical problem.
In Python 2 you could get class-only methods like this:
class Foo(object):
class __metaclass__(type):
def classmeth(cls): ...
I'm mildly disappointed that this can't be done any more in Python 3. Sometimes you need genuine metaclass methods, e.g. __xxx__ methods for a class rather than an instance.
You can still do that, you just have to define the metaclass in advance: class FooMeta(type): def classmeth(cls): ... class Foo(metaclass=FooMeta): ... This is the price we pay for allowing metaclasses to customise the namespace used to execute the class body in __prepare__. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Thu, Mar 7, 2013 at 4:02 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
In Python 2 you could get class-only methods like this:
Keep in mind that there is a (perhaps subtle) difference between methods on a metaclass and the class-only methods for which I was advocating. When dealing with metaclasses you have to consider things like metaclass conflicts as well as metaclass inheritance. Also, like classmethods, a class-only method is looked up on the instance's MRO and not on the class's MRO (as happens with metaclass methods). The difference isn't that big a deal in practice, though. -eric

On 07/03/13 19:21, Nick Coghlan wrote:
On Thu, Mar 7, 2013 at 5:10 PM, Eric Snow <ericsnowcurrently@gmail.com> wrote:
Thoughts?
It's too much additional complexity to resolve a largely theoretical problem. Since class methods can be shadowed in instances, the fact they're accessible through the instances really doesn't hurt anything, and the distinction between a class method and a class only method would be too subtle to easily explain to anyone not already steeped in the details of descriptors and metaclasses.
Surely that only applies to the implementation, not the concept itself? The interface is trivially easy to explain to anyone even half-way familiar with Python's OO model. Class methods are accessible from either the class or the instance, and receive the class (not the instance) as the first argument. Class-only methods are only accessible from the class. I'm not sure what use class-only methods are or what problems they solve, apart from a general dislike of being able to call classmethods from an instance. I can't think of any case where I would want to actively prohibit calling a classmethod from an instance, so I don't know that this actually solves any problems. But it looks interesting and I think Eric should put it up as a recipe on ActiveState. -- Steven

On Fri, Mar 8, 2013 at 8:45 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Surely that only applies to the implementation, not the concept itself? The interface is trivially easy to explain to anyone even half-way familiar with Python's OO model.
Class methods are accessible from either the class or the instance, and receive the class (not the instance) as the first argument.
Class-only methods are only accessible from the class.
I agree the technical distinction isn't subtle.
I'm not sure what use class-only methods are or what problems they solve, apart from a general dislike of being able to call classmethods from an instance. I can't think of any case where I would want to actively prohibit calling a classmethod from an instance, so I don't know that this actually solves any problems.
It's the "When would I recommend using this over a normal classmethod?" that I consider subtle. By only providing one of the two options directly, it means people don't even need to ask the question, let alone figure out how to answer it.
But it looks interesting and I think Eric should put it up as a recipe on ActiveState.
Yes, it's certainly a reasonable thing to post as a recipe. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Fri, Mar 8, 2013 at 4:15 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
It's the "When would I recommend using this over a normal classmethod?" that I consider subtle. By only providing one of the two options directly, it means people don't even need to ask the question, let alone figure out how to answer it.
And this is where I agree. I will say that the proper implementation isn't trivial and my recipe is only pretty close. If it were more frequently useful, I'd press for a classonlymethod() in the standard library (not builtins). If that changes I'll bring it up again, but for now I'm not convinced it is worth it. -eric

On Fri, Mar 8, 2013 at 3:45 AM, Steven D'Aprano <steve@pearwood.info> wrote:
But it looks interesting and I think Eric should put it up as a recipe on ActiveState.
I already had. :) http://code.activestate.com/recipes/578486 -eric
participants (4)
-
Eric Snow
-
Greg Ewing
-
Nick Coghlan
-
Steven D'Aprano