13

I read What is a metaclass in Python?

and I tried to replicate the upper metaclass from the example and found that this doesn't work in all cases:

def upper(cls_name, cls_parents, cls_attr):                                     
    """ Make all class attributes uppper case """                               
    attrs = ((name, value) for name, value in cls_attr.items()                  
            if not name.startswith('__'))                                       
    upper_atts = dict((name.upper(), value) for name, value in attrs)           
    return type(cls_name, cls_parents, upper_atts)                              

__metaclass__ = upper #Module level
class Foo:                                                                      
    bar = 1                                                                     
f =  Foo()
print(f.BAR) #works in python2.6

The above fails (with an attribute error) in python3 which I think is natural because all classes in python3 already have object as their parent and metaclass resolution goes into the object class.

The question:

How do I make a module level metaclass in python3?

Community
  • 1
  • 1
Alex Plugaru
  • 2,209
  • 19
  • 26
  • Probably what you need is rather a class decorator than a metaclass. – Sven Marnach Aug 11 '11 at 09:59
  • @Sven Marnach: `__metaclass__` may indeed be bound to a function. That function only needs to accept the same arguments as a metaclass' `__init__()` method does and return a type object. – pillmuncher Aug 11 '11 at 10:11
  • @Sven Marnach: _needs to accept the same arguments as a metaclass' __init__() method_ - except `self`, that is. – pillmuncher Aug 11 '11 at 10:17
  • @pillmuncher: you are basically right, except it's the same arguments as the metaclass' `__new__()` method. I wasn't aware of this, though it's actually quite clear that any callable will do. (Note that it's not even necessary for the callable to return a type object. Whatever object the callable returns will be bound to the class name.) – Sven Marnach Aug 11 '11 at 10:39
  • 1
    So, what _is_ the python3 equivalent of a _module-level_ metaclass? Seems like nobody has answered your actual question yet. Has the feature been scrapped? Inquiring minds wants to know. – Lauritz V. Thaulow Aug 11 '11 at 11:12
  • @lazyr: It was never more than a misfeature. :-) – Lennart Regebro Aug 12 '11 at 11:41

2 Answers2

12

The module level metaclass isn't really "module level", it has to do with how class initialization worked. The class creation would look for the variable "__metaclass__" when creating the class, and if it wasn't in the local environment it would look in the global. Hence, if you had a "module level" __metaclass__ that would be used for every class afterwards, unless they had explicit metaclasses.

In Python 3, you instead specify the metaclass with a metaclass= in the class definition. Hence there is no module level metaclasses.

So what do you do? Easy: You specify it explicitly for each class.

It's really not much extra work, and you can even do it with a nice regexp search and replace if you really have hundreds of classes and don't want to do it manually.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
  • 2
    I suggest you use a module wide superclass e.g. `class Base(object, metaclass=upper): pass` Then base all relevant classes on this base class. – Paul Whipp Nov 13 '17 at 03:51
4

If you want to change all the attributes to upper case, you should probably use the __init__ method to do so, than use a metaclass.

Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why).

-- Python Guru Tim Peters

If you need something deeper, you should also evaluate using Class Decorators.

Using MetaClasses and understanding how the classes are created is so unnecessary as long as you want to do something that you can do using class decorators or initialization.

That said, if you really want to use a Metaclass tho' pass that as a keyword argument to the class.

class Foo(object, metaclass=UpperCaseMetaClass)

where UpperCaseMetaClass is a class that extends type and not a method.

class UpperCaseMetaClass(type):
    def __new__():
        #Do your Magic here.
Community
  • 1
  • 1
lprsd
  • 84,407
  • 47
  • 135
  • 168
  • The Tim Peters link is broken; here's an archived version: https://web.archive.org/web/20110713033903/http://www.ibm.com/developerworks/linux/library/l-pymeta/index.html – jwd Mar 15 '17 at 19:55