4

Is there a way to write a metaclass such that all the methods defined in classes based on this metaclass are class methods by default (i.e. without requiring, e.g., @classmethod decorations, etc.)?

Thanks!

Edit: I'm interested in Python 2.7.3. Please pardon the omission.

2 Answers 2

3

Sure. The real question is why you'd want to do this. I'm going to assume this is for education only. In reality, whatever this could be used for is probably better done explicitly (i.e. not using a metaclass and adding @classmethod).

I'm aware of two options: A custom namespace for the class definition, and post-processing in __new__. The first is quite cool and actually unavoidable for some cases (e.g. remembering the order of method definitions), but the latter is simpler, more flexible, can't accidentally break multiple assignments to the same name, and is available in Python 2. For your purpose, this rules out the former, but I'll leave it in for everyone who can use Python 3.

To supply a custom namespace, you have to define __prepare__ in the metaclass and return a dictionary-like object (Ferdinand Beyer noted in a comment: the mapping interface as defined in collections is sufficent). In this case, the dictionary should override __setitem__ to check if the value is callable, and if so, wrap it in classmethod prior to storing it. I owe a lot to the comments below, they vastly improved the code below. Ferdinand Beyer notices that callable(classmethod(...)) is false, and agf pointed out inconsistencies with @classmethod and Python 3 compatability.

import types

# Either this, in a dictionary class instanciated in __prepare__
# Again, Python 3 only
def __setitem__(self, name, value):
    if isinstance(value, types.FunctionType):
        value = classmethod(value)
    super().__setitem__(name, value)

# __prepare__ would be: return AutomaticClassMethodNamespace() or something

# Or this in the metaclass
def __new__(mcls, clsname, bases, ns):
    for name, value in ns.items():
        if isinstance(value, types.FunctionType):
            ns[name] = classmethod(value)
    # Any other metaclass behaviour
Sign up to request clarification or add additional context in comments.

8 Comments

You should specifically be using types.FunctionType not callable. You also mean and not. I'm not sure how using __new__ in a metaclass messes up multiple definitions of the same name, since it gets a dictionary where each identifier has already been resolved to a single value?
For the record: The object returned by __prepare__ is not required to inherit from dict, it only has to "support the mapping interface" (for example, using the collections.MutableMapping abstract base class). See docs.python.org/py3k/reference/…
Since instances of classmethod are not callable, you don't need the extra check.
@FerdinandBeyer This still tries to classmethod callables that aren't functions.
@agf: That's right. I was just commenting on the "infinite recursion" (?) bit. I would probably go for your solution, but whether to use callable or check for FunctionType ultimately is a matter of taste...
|
0

Something like this (2.7)

def m(name, bases, dct):
    for k, v in dct.items():
        if type(v) is type(m):
            dct[k] = classmethod(v)
    return type(name, bases, dct)

class A:
    __metaclass__ = m

    def foo(self):
        print self


A.foo() # <class '__main__.A'>

6 Comments

The canonical way to check for a function would be if isinstance(v, types.FunctionType):. This would also be better if you used more meaningful names than m, k, and v.
@agf: I don't believe in spells.
I have no idea what that comment is supposed to mean. Suppose I rename the metaclass function; your code breaks. Suppose I switch it to use a class that inherits from type instead of a function; your code breaks. That's why you use the actual type you mean instead of finding it indirectly. And using meaningful names, especially when explaining something to someone, is basic common sense.
There's nothing cryptic about if isinstance(v, types.FunctionType):. Read it. "if v is an instance of the type that functions are". Your version reads "if the type of v is the same object as the type of m" so the fact that you're talking about the type of functions isn't explicit. Using isinstance is also the standard and so should be preferred unless there is a good reason to use something different.
@arg: I believe the OP will be able to adapt my function to their needs. We're sharing ideas here, not chunks of production-ready code.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.