Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #56275 > unrolled thread

Overriding of the type.__call__() method in a metaclass

Started byMarco Buttu <marco.buttu@gmail.com>
First post2013-10-06 20:17 +0200
Last post2013-10-07 07:22 +0200
Articles 4 — 3 participants

Back to article view | Back to comp.lang.python


Contents

  Overriding of the type.__call__() method in a metaclass Marco Buttu <marco.buttu@gmail.com> - 2013-10-06 20:17 +0200
    Re: Overriding of the type.__call__() method in a metaclass Peter Otten <__peter__@web.de> - 2013-10-06 21:04 +0200
    Re: Overriding of the type.__call__() method in a metaclass Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-10-07 02:27 +0000
      Re: Overriding of the type.__call__() method in a metaclass Marco Buttu <marco.buttu@gmail.com> - 2013-10-07 07:22 +0200

#56275 — Overriding of the type.__call__() method in a metaclass

FromMarco Buttu <marco.buttu@gmail.com>
Date2013-10-06 20:17 +0200
SubjectOverriding of the type.__call__() method in a metaclass
Message-ID<l2s9fr$eus$1@speranza.aioe.org>
Hi all, I have a question about class creation and the __call__ method. 
I have the following metaclass:

 >>> class FooMeta(type):
...     def __call__(metacls, name, bases, namespace):
...         print("FooMeta.__call__()")


 From what I undestood, at the end of the class statement happens 
something like this:

 >>> def __call__(metacls, name, bases, namespace):
...     print("FooMeta.__call__()")
...
 >>> FooMeta = type('FooMeta', (type,), {'__call__': __call__})

The call to the metaclass type causes the call to type.__call__(), so 
that's happened is:

 >>> FooMeta = type.__call__(type, 'FooMeta', (type,), {'__call__': 
__call__})

Now I expected the output `FooMeta.__call__()` from the following Foo 
class creation:

 >>> class Foo(metaclass=FooMeta):
...     pass

because I thought at the end of the class Foo suite this should have 
been happened:

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

but I thought wrong:

 >>> class FooMeta(type):
...     def __call__(metacls, name, bases, namespace):
...         print("FooMeta.__call__()")
...
 >>> class Foo(metaclass=FooMeta):
...     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

[toc] | [next] | [standalone]


#56276

FromPeter Otten <__peter__@web.de>
Date2013-10-06 21:04 +0200
Message-ID<mailman.791.1381086267.18130.python-list@python.org>
In reply to#56275
Marco Buttu wrote:

> Hi all, I have a question about class creation and the __call__ method.
> I have the following metaclass:
> 
>  >>> class FooMeta(type):
> ...     def __call__(metacls, name, bases, namespace):
> ...         print("FooMeta.__call__()")
> 
> 
>  From what I undestood, at the end of the class statement happens
> something like this:
> 
>  >>> def __call__(metacls, name, bases, namespace):
> ...     print("FooMeta.__call__()")
> ...
>  >>> FooMeta = type('FooMeta', (type,), {'__call__': __call__})
> 
> The call to the metaclass type causes the call to type.__call__(), so
> that's happened is:
> 
>  >>> FooMeta = type.__call__(type, 'FooMeta', (type,), {'__call__':
> __call__})
> 
> Now I expected the output `FooMeta.__call__()` from the following Foo
> class creation:
> 
>  >>> class Foo(metaclass=FooMeta):
> ...     pass
> 
> because I thought at the end of the class Foo suite this should have
> been happened:
> 
>  >>> Foo = FooMeta.__call__(FooMeta, 'Foo', (), {})
> FooMeta.__call__()
> 
> but I thought wrong:
> 
>  >>> class FooMeta(type):
> ...     def __call__(metacls, name, bases, namespace):
> ...         print("FooMeta.__call__()")
> ...
>  >>> class Foo(metaclass=FooMeta):
> ...     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 
calling Foo invokes FooMeta.__call__():

>>> class FooMeta(type):
...     def __call__(self, *args): print("__call__%r" % (args,))
... 
>>> class Foo(metaclass=FooMeta): pass
... 
>>> Foo()
__call__()

If you follow that logic you can easily see that for FooMeta to invoke your 
custom __call__() method you'd have to define it in FooMeta's metaclass:

>>> class FooMetaMeta(type):
...     def __call__(*args): print(args)
... 
>>> class FooMeta(metaclass=FooMetaMeta):
...     pass
... 
>>> class Foo(metaclass=FooMeta):
...     pass
... 
(<class '__main__.FooMeta'>, 'Foo', (), {'__module__': '__main__', 
'__qualname__': 'Foo'})
>>> Foo is None                                                                                                                                                                                                   
True                                                                                                                                                                                                              

So, yes, it's turtles all the way down...

[toc] | [prev] | [next] | [standalone]


#56300

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-10-07 02:27 +0000
Message-ID<52521c08$0$29984$c3e8da3$5496439d@news.astraweb.com>
In reply to#56275
On Sun, 06 Oct 2013 20:17:33 +0200, Marco Buttu wrote:

> Hi all, I have a question about class creation and the __call__ method.
> I have the following metaclass:
> 
>  >>> class FooMeta(type):
> ...     def __call__(metacls, name, bases, namespace): 
> ...        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.


>  From what I undestood, at the end of the class statement happens
> something like this:
> 
>  >>> def __call__(metacls, name, bases, namespace):
> ...     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.


> The call to the metaclass type causes the call to type.__call__(), so
> that's happened is:
> 
>  >>> FooMeta = type.__call__(type, 'FooMeta', (type,), {'__call__':
> __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__.


> Now I expected the output `FooMeta.__call__()` from the following Foo
> class creation:
> 
>  >>> class Foo(metaclass=FooMeta):
> ...     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.


> because I thought at the end of the class Foo suite this should have
> been happened:
> 
>  >>> 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

[toc] | [prev] | [next] | [standalone]


#56303

FromMarco Buttu <marco.buttu@gmail.com>
Date2013-10-07 07:22 +0200
Message-ID<l2tgdp$b6c$1@speranza.aioe.org>
In reply to#56300
On 10/07/2013 04:27 AM, Steven D'Aprano wrote:

> On Sun, 06 Oct 2013 20:17:33 +0200, Marco Buttu wrote:
>> >
>> >  >>> class FooMeta(type):
>> >...     def __call__(metacls, name, bases, namespace):
>> >...        print("FooMeta.__call__()")
...
>> >  From what I undestood, at the end of the class statement...
>> >
>> >  >>> def __call__(metacls, name, bases, namespace):
>> >...     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 
should be clear for me:

 >>> class FooMeta(type):
...     def __call__(meta, name, bases, namespace):
...         print('FooMeta.__call__()')
...
 >>> class InstanceOfFooMeta(type, metaclass=FooMeta):
...     pass
...
 >>> class Foo(metaclass=InstanceOfFooMeta):
...     pass
...
FooMeta.__call__()

I try to summarize the execution flow. The metaclass type creates FooMeta:

 >>> class FooMeta(type):
...     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,), {...})

At this point FooMeta is created. The next step is:

 >>> class InstanceOfFooMeta(type, metaclass=FooMeta):
...     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,), {...})


Finally:

 >>> class Foo(metaclass=InstanceOfFooMeta):
...     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

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web