Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error when __new__ is called from a metaclass

Tags:

python

The following code produces an error:

class FooMeta(type):
  def __new__(mcl, classname, bases, classdict):
   return type.__new__(mcl, classname, bases, dict())

  def __init__(cls, name, bases, classdict):
    super(FooMeta, cls).__init__(name, bases, classdict)

    d = dict()
    for key, item in classdict.items():
      if key.startswith("_"):
        setattr(cls, key, item)
      else:
        d[key] = item
    setattr(cls, '_items', d)

class Foo(object):
  __metaclass__ = FooMeta

class Passing(Foo):
  def __init__(self, *args, **kw):
    pass

class Failing(Foo):
  def __new__(cls, *args, **kw):
    return super(Failing, cls).__new__(cls)

  def __init__(self, *args, **kw):
    pass

fail = Failing()

The error is

Traceback (most recent call last):
  File ".../FooMeta,py", line 30, in <module>
    fail = Failing()
TypeError: unbound method __new__() must be called with Failing instance as first argument (got FooMeta instance instead)

There appears to be some difference in the way __new__ is called when called from a metaclass.

1) Anyone know why this is happening? 2) Is there some way to fix it?

like image 390
Mike Miller Avatar asked Dec 03 '12 22:12

Mike Miller


People also ask

What is __ new __?

The __new__ method is called with the class as its first argument; its responsibility is to return a new instance of that class. Compare this to __init__ : __init__ is called with an instance as its first argument, and it doesn't return anything; its responsibility is to initialize the instance.

How do you use metaclass in Python?

To create your own metaclass in Python you really just want to subclass type . A metaclass is most commonly used as a class-factory. When you create an object by calling the class, Python creates a new class (when it executes the 'class' statement) by calling the metaclass.

How do I change metaclass in Python?

In order to set metaclass of a class, we use the __metaclass__ attribute. Metaclasses are used at the time the class is defined, so setting it explicitly after the class definition has no effect. The best idea I can think of is to re-define whole class and add the __metaclass__ attribute dynamically somehow.

What is a metaclass in Smalltalk?

Together this implies that a class in Smalltalk is an object and that, therefore a class needs to be an instance of a class (called metaclass). As an example, a car object c is an instance of the class Car . In turn, the class Car is again an object and as such an instance of the metaclass of Car called Car class .


1 Answers

If you allow the attributes to be set as is normally done:

class FooMeta(type):
    def __new__(mcl, classname, bases, classdict):
        return type.__new__(mcl, classname, bases, classdict)

and do not fiddle with them in __init__, you see that Failing.__new__ is normally a staticmethod:

class FooMeta(type):
    def __new__(mcl, classname, bases, classdict):
        return type.__new__(mcl, classname, bases, classdict)
        # return type.__new__(mcl, classname, bases, dict())

    def __init__(cls, name, bases, classdict):
        super(FooMeta, cls).__init__(name, bases, classdict)
        d = dict()
        for key, item in classdict.items():
            if key.startswith("_"):
                # setattr(cls, key, item)
                pass
            else:
                d[key] = item
        setattr(cls, '_items', d)

class Foo(object):
    __metaclass__ = FooMeta

class Failing(Foo):
    def __new__(cls, *args, **kw):
        return super(Failing, cls).__new__(cls)

    def __init__(self, *args, **kw):
        pass

print(Failing.__dict__['__new__'])
foo = Failing()

yields:

<staticmethod object at 0xb774ba94>

But if you run the code you posted with this print statement tacked on the end:

print(Failing.__dict__['__new__'])
foo = Failing()

you see that __new__ has been changed to a function:

<function __new__ at 0xb747372c>

You can fix this by allowing all the attributes in classdict to be set as would normally be done, and then deleting those attributes which do not start with _:

class FooMeta(type):
    def __new__(mcl, classname, bases, classdict):
        return type.__new__(mcl, classname, bases, classdict)

    def __init__(cls, name, bases, classdict):
        super(FooMeta, cls).__init__(name, bases, classdict)
        d = dict()
        for key, item in classdict.items():
            if not key.startswith("_"):
                delattr(cls, key)
                d[key] = item
        setattr(cls, '_items', d)

class Foo(object):
    __metaclass__ = FooMeta

class Passing(Foo):
    def __init__(self, *args, **kw):
        pass

class Failing(Foo):
    def __new__(cls, *args, **kw):
        return super(Failing, cls).__new__(cls)

    def __init__(self, *args, **kw):
        pass

print(Failing.__dict__['__new__'])
foo = Failing()
like image 189
unutbu Avatar answered Nov 14 '22 23:11

unutbu