Discussion:
Overriding of the type.__call__() method in a metaclass
Marco Buttu
2013-10-06 18:17:33 UTC
Permalink
Hi all, I have a question about class creation and the __call__ method.
... def __call__(metacls, name, bases, namespace):
... print("FooMeta.__call__()")


From what I undestood, at the end of the class statement happens
... print("FooMeta.__call__()")
...
FooMeta = type('FooMeta', (type,), {'__call__': __call__})
The call to the metaclass type causes the call to type.__call__(), so
__call__})

Now I expected the output `FooMeta.__call__()` from the following Foo
... pass

because I thought at the end of the class Foo suite this should have
Foo = FooMeta.__call__(FooMeta, 'Foo', (), {})
FooMeta.__call__()
... def __call__(metacls, name, bases, namespace):
... print("FooMeta.__call__()")
...
... pass
...
How come? Is it because the first argument of metaclass.__call__() is
always type or I am thinking something wrong?
Thanks in advance, Marco
--
Marco
Peter Otten
2013-10-06 19:04:20 UTC
Permalink
Post by Marco Buttu
Hi all, I have a question about class creation and the __call__ method.
... print("FooMeta.__call__()")
From what I undestood, at the end of the class statement happens
... print("FooMeta.__call__()")
...
FooMeta = type('FooMeta', (type,), {'__call__': __call__})
The call to the metaclass type causes the call to type.__call__(), so
__call__})
Now I expected the output `FooMeta.__call__()` from the following Foo
... pass
because I thought at the end of the class Foo suite this should have
Foo = FooMeta.__call__(FooMeta, 'Foo', (), {})
FooMeta.__call__()
... print("FooMeta.__call__()")
...
... pass
...
How come? Is it because the first argument of metaclass.__call__() is
always type or I am thinking something wrong?
Thanks in advance, Marco
Forget about metaclasses for the moment and ask yourself what happens when a
regular class

class A:
def __init__(...): ...
def __call__(...): ...

is "called":

a = A(...) # invokes __init__()
a(...) # invokes __call__()


The metaclass is just the class of a class, i. e. the Foo object is an
instance of FooMeta, so making Foo invokes (__new__() and) __init__(), and
... def __call__(self, *args): print("__call__%r" % (args,))
...
Post by Marco Buttu
class Foo(metaclass=FooMeta): pass
...
Post by Marco Buttu
Foo()
__call__()

If you follow that logic you can easily see that for FooMeta to invoke your
... def __call__(*args): print(args)
...
... pass
...
... pass
...
(<class '__main__.FooMeta'>, 'Foo', (), {'__module__': '__main__',
'__qualname__': 'Foo'})
Post by Marco Buttu
Foo is None
True

So, yes, it's turtles all the way down...
Steven D'Aprano
2013-10-07 02:27:21 UTC
Permalink
Post by Marco Buttu
Hi all, I have a question about class creation and the __call__ method.
... print("FooMeta.__call__()")
At this point, FooMeta is nothing special, it's just an ordinary class.
Calling it "FooMeta" doesn't make it special. Like every ordinary class,
__call__ will only be called when an *instance* is called. Since you have
no FooMeta instances yet, FooMeta.__call__ won't be called.
Post by Marco Buttu
From what I undestood, at the end of the class statement happens
... print("FooMeta.__call__()")
...
FooMeta = type('FooMeta', (type,), {'__call__': __call__})
You're referring to the class statement ("class FooMeta(type): ...")
being syntactic sugar for the above direct call to type. Correct. Again,
this applies to *all* classes, not just metaclasses.
Post by Marco Buttu
The call to the metaclass type causes the call to type.__call__(), so
__call__})
Yes but no...

Your code snippet is correct. The "class FooMeta(type)..." statement is
syntactic sugar for type.__call__(...). But your description is
incorrect. This doesn't occur when you "call the metaclass type", not in
the sense you mean. You don't have a "metaclass type" yet, except for
type itself. You have a class *called* FooMeta, but it hasn't been
called. It can't be called yet, because it hasn't yet been created!
You're still executing the "class FooMeta..." statement, creating FooMeta.

So at the point FooMeta is created, the only metaclass involved is type
itself. Hence, it is only type.__call__ which is involved, not
FooMeta.__call__. FooMeta.__call__ is used when you call an instance of
FooMeta:

class Foo(metaclass=FooMeta):
...

obj = Foo()


Here, Foo is an instance of FooMeta, so calling Foo calls
FooMeta.__call__.


What I think you are looking for is FooMeta.__new__, which gets called
when the instance is created. What's the instance of FooMeta again? It's
class Foo. So at the end of "class Foo(metaclass=FooMeta)..." the
FooMeta.__new__ method is called to create Foo. Then, once Foo is
created, instantiating it using "obj = Foo()" calls FooMeta.__call__.
Post by Marco Buttu
Now I expected the output `FooMeta.__call__()` from the following Foo
... pass
No, not at the class creation. Metaclass.__new__ is called when you
create an instance; the instance here is class Foo, so FooMeta.__new__
will be called.
Post by Marco Buttu
because I thought at the end of the class Foo suite this should have
Foo = FooMeta.__call__(FooMeta, 'Foo', (), {})
FooMeta.__call__()
Go back to the rule for class creation:

class Spam(bases):
<namespace>

is syntactic sugar for:

type('Spam', bases, namespace)

used to instantiate the new instance, Spam.

If a metaclass is given, it is used instead of type. So you'll have:

Meta('Spam', bases, namespace)

and Meta.__new__ will be called to create the instance.

Note: you don't need to use a *class* as metaclass! Bizarre but true: any
callable object will do, so long as it matches the expected signature.
Watch this:


py> class Wot(metaclass=lambda name, bases, namespace: 42):
... a = 12
... def __len__(self):
... return 9999
...
py> Wot
42


But I digress. In your case, you are using a subclass of type as your
metaclass, and it is creating a new instance of FooMeta. When a new
instance is created, FooMeta.__new__ is called.

To get the effect you are after, you can:


1) Use FooMeta.__new__ instead of __call__;

2) Use a metaclass of the metaclass, FooMetaMeta.__call__; or

3) Use a function that takes the same signature as type.
--
Steven
Marco Buttu
2013-10-07 05:22:02 UTC
Permalink
Post by Steven D'Aprano
Post by Marco Buttu
... print("FooMeta.__call__()")
...
Post by Steven D'Aprano
Post by Marco Buttu
From what I undestood, at the end of the class statement...
... print("FooMeta.__call__()")
...
FooMeta = type('FooMeta', (type,), {'__call__': __call__})
Your code snippet is correct. The "class FooMeta(type)..." statement is
syntactic sugar for type.__call__(...). But your description is
incorrect. This doesn't occur when you "call the metaclass type"...
Oh damn! Your are right :) Thanks to you and Peter. Now (I hope...) it
... def __call__(meta, name, bases, namespace):
... print('FooMeta.__call__()')
...
... pass
...
... pass
...
FooMeta.__call__()
... def __call__(meta, name, bases, namespace):
... print('FooMeta.__call__()')
...

This means at the end of the suite:

FooMeta = type('FooMeta', (type,), {...})

So Python calls type. But type is an instance of type itself, so:

FooMeta = type.__call__(type, 'FooMeta', (type,), {...})
... pass

This causes:

InstanceOfFooMeta= FooMeta('InstanceOfFooMeta', (type,), {...})

Python is calling FooMeta, so it is calling an instance of type, so the
code above becomes:

InstanceOfFooMeta = type.__call__(FooMeta, 'InstanceOfMeta', \
(type,), {...})
... pass
...
FooMeta.__call__()

In fact at the end of the suite of the class statement, Python calls an
instance of FooMeta:

Foo = InstanceOfFooMeta('Foo', (), {...})

so, definitively:

Foo = FooMeta.__call__(InstanceOfFooMeta, 'Foo', (), {...})

Foo is None, but never mind. I just wanted to clarify me the class
creation process.
Thanks again and congratulations for your PEP, it is written very very well
--
Marco Buttu
Loading...