kommmy
kommmy

Reputation: 28

python metaclass pass __init__ params

I'm learning metaclass programming in python 3, but I have some problems

class UpperAttrMetaClass(type): # to uppercase all attrs
    def __new__(mcs, class_name, class_parents, class_attr):
        attrs = ((name, value) for name, value in class_attr.items() if not 
            name.startswith('__'))
        uppercase_attrs = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaClass, mcs).__new__(mcs, class_name, 
                     class_parents, uppercase_attrs)

class Base(metaclass=UpperAttrMetaClass):
    bar = 12
    def __init__(self, params):
        super(Base, self).__init__()
        self.params = params

t = Base(1)
print(t.BAR)
print(t.params)

This code can uppercase all attrs.

I want to pass a parameter to init, but when I run this code, I'm prompted to make a mistake that

TypeError: object() takes no parameters

How can I solve this problem?

Upvotes: 0

Views: 663

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121784

You are filtering out the __init__ method:

attrs = ((name, value) for name, value in class_attr.items() if not 
    name.startswith('__'))

attrs are all the attributes that do not start with __. You then uppercase those attrs and use those for the class you create, so __init__ is never used for the new class. Because the resulting Bar class has no __init__ method, object.__init__ is used, and that method doesn't take parameters:

>>> sorted(vars(Base))
['BAR', '__dict__', '__doc__', '__module__', '__weakref__']
>>> Base.__init__
<slot wrapper '__init__' of 'object' objects>

Include all attributes, do not filter, but only uppercase those without __:

class UpperAttrMetaClass(type): # to uppercase all attrs
    def __new__(mcs, class_name, class_parents, class_attr):
        attrs = {name if name.startswith('__') else name.upper(): value for name, value in class_attr.items()}
        return super().__new__(mcs, class_name, class_parents, attrs)

I used a dictionary comprehension here; note the name if name.startswith('__') else name.upper() conditional expression, which produces an uppercase attribute name when the name does not start with __.

I also used the 0-argument form of super(), this is Python 3 after all.

Now the metaclass works correctly and Base.__init__ exists:

>>> sorted(vars(Base))
['BAR', '__dict__', '__doc__', '__init__', '__module__', '__weakref__']
>>> t = Base(1)
>>> t.BAR
12
>>> t.params
1

Upvotes: 1

Related Questions