Why doesn't __call__ lead to infinite recursion?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Patrick Lioi

    Why doesn't __call__ lead to infinite recursion?

    def foo(): pass

    foo is a function
    foo is a callable object
    foo has method __call__ defined

    foo.__call__ is a function
    foo.__call__ is a callable object
    foo.__call__ has method __call__ defined

    foo.__call__.__ call__ is a function...


    This seems to go on forever. How does calling foo() not lead to
    an infinite loop while trying to execute?
  • Aahz

    #2
    Re: Why doesn't __call__ lead to infinite recursion?

    In article <bhjg3v$q3m$1@a bsinth.dialog.n et.pl>,
    bromden <bromden@gazeta .pl.no.spam> wrote:[color=blue][color=green]
    >> foo is a function
    >> foo is a callable object
    >> foo has method __call__ defined[/color]
    >
    >true
    >true
    >false[/color]

    Wrong. See my followup.
    --
    Aahz (aahz@pythoncra ft.com) <*> http://www.pythoncraft.com/

    This is Python. We don't care much about theory, except where it intersects
    with useful practice. --Aahz

    Comment

    • Aahz

      #3
      Re: Why doesn't __call__ lead to infinite recursion?

      In article <2a82921f.03081 51158.b8c9154@p osting.google.c om>,
      Patrick Lioi <patrick@novaro ot.com> wrote:[color=blue]
      >
      >def foo(): pass
      >
      >foo is a function
      >foo is a callable object
      >foo has method __call__ defined
      >
      >foo.__call__ is a function
      >foo.__call__ is a callable object
      >foo.__call__ has method __call__ defined[/color]

      You're mixing up the distinction between objects and types. If you do

      print foo.__dict__

      you'll see that __call__ isn't there. However, if you try

      print type(foo).__dic t__

      you'll see __call__ there. When you do foo(), Python actually does
      type(foo).__cal l__(foo). Because type(foo).__cal l__ is manipulating
      foo, you don't get the circular reference.
      --
      Aahz (aahz@pythoncra ft.com) <*> http://www.pythoncraft.com/

      This is Python. We don't care much about theory, except where it intersects
      with useful practice. --Aahz

      Comment

      • John J. Lee

        #4
        Re: Why doesn't __call__ lead to infinite recursion?

        aahz@pythoncraf t.com (Aahz) writes:
        [...][color=blue]
        > you'll see __call__ there. When you do foo(), Python actually does
        > type(foo).__cal l__(foo). Because type(foo).__cal l__ is manipulating
        > foo, you don't get the circular reference.[/color]

        Still seems weird that type(foo).__cal l__.__call__ (etc.) is defined.


        John

        Comment

        • Andrew Dalke

          #5
          Re: Why doesn't __call__ lead to infinite recursion?

          Aahz:[color=blue]
          > you'll see __call__ there. When you do foo(), Python actually does
          > type(foo).__cal l__(foo). Because type(foo).__cal l__ is manipulating
          > foo, you don't get the circular reference.[/color]

          Not quite, but I don't understand how everything works so what I
          say may also need corrections.

          The call syntax "foo()" does two things. The first is to
          get the 'thing' used for the call and the second is to actually
          call it. The latter is not done recursively - if the returned
          thing can't be called, the attempt at making the call fails.

          If 'foo' is an instance, then the implementation code is
          something like

          thing_to_call = getattr(foo, "__call__")
          if thing_to_call is not None:
          DO_CALL(thing_t o_call, args, kwargs)

          The 'DO_CALL' is not a Python function, it's part of
          how the implementation works.

          The getattr implementation for an instance first tries to
          find "__call__" in foo's instance __dict__. If that fails, it
          looks in the parent class, and returns a bound method,
          that is, a new 'thing' with references to the class method
          and to the instance itself. The DO_CALL does the
          actual call to this thing with the new arguments. The
          bound method thing prepends the self parameter and
          does the actual call to the underlying code.

          Pretty complicated, and I don't think I was very clear
          on that. Here's an example though to show that

          foo() != foo.__class__._ _call__(foo)
          [color=blue][color=green][color=darkred]
          >>> class SPAM:[/color][/color][/color]
          .... def __init__(self):
          .... def callme(x):
          .... print "Hello", x
          .... self.__call__ = callme
          .... def __call__(self, x):
          .... print "Hej", x
          ....[color=blue][color=green][color=darkred]
          >>> spam = SPAM()
          >>> spam("world")[/color][/color][/color]
          Hello world[color=blue][color=green][color=darkred]
          >>>
          >>> spam.__class__. __call__(spam, "world")[/color][/color][/color]
          Hej world[color=blue][color=green][color=darkred]
          >>>[/color][/color][/color]
          [color=blue][color=green][color=darkred]
          >>> getattr(spam, "__call__")[/color][/color][/color]
          <function callme at 0x014DA9B0>[color=blue][color=green][color=darkred]
          >>> getattr(spam, "__init__")[/color][/color][/color]
          <bound method SPAM.__init__ of <__main__.SPA M instance at 0x013CF148>>[color=blue][color=green][color=darkred]
          >>> getattr(SPAM, "__call__")[/color][/color][/color]
          <unbound method SPAM.__call__>[color=blue][color=green][color=darkred]
          >>>[/color][/color][/color]

          I'm also missing something because I don't know how
          functions work. I thought it was always 'use
          getattr(obj, "__call__") to get the thing to call then do
          the call machinery on that thing", but it doesn't seem
          to do that for functions.
          [color=blue][color=green][color=darkred]
          >>> def f(x):[/color][/color][/color]
          .... print "f(%s)" % x
          ....[color=blue][color=green][color=darkred]
          >>> def g(x):[/color][/color][/color]
          .... print "g(%s)" % x
          ....[color=blue][color=green][color=darkred]
          >>> f(5)[/color][/color][/color]
          f(5)[color=blue][color=green][color=darkred]
          >>> f(7)[/color][/color][/color]
          f(7)[color=blue][color=green][color=darkred]
          >>> f.__call__ = g.__call__
          >>> f(4)[/color][/color][/color]
          f(4)[color=blue][color=green][color=darkred]
          >>> f.__call__[/color][/color][/color]
          <method-wrapper object at 0x014AD6F0>[color=blue][color=green][color=darkred]
          >>> f.__call__[/color][/color][/color]
          <method-wrapper object at 0x014AD6F0>[color=blue][color=green][color=darkred]
          >>> g.__call__[/color][/color][/color]
          <method-wrapper object at 0x014A02D0>[color=blue][color=green][color=darkred]
          >>> g.__call__[/color][/color][/color]
          <method-wrapper object at 0x014AD5B0>[color=blue][color=green][color=darkred]
          >>>[/color][/color][/color]

          I expected the 'f(4)' call to return 'g(4)' since I replaced
          the function's __call__ with g's __call__.

          What's throwing me off is that g.__call__ returns
          a new wrapper object each time, while once I
          assigned f.__call__, it persistently stayed that way.
          So there's some getattr shenanigans with functions
          I don't understand. To make it worse, there's also
          [color=blue][color=green][color=darkred]
          >>> import types
          >>> types.FunctionT ype.__call__[/color][/color][/color]
          <slot wrapper '__call__' of 'function' objects>[color=blue][color=green][color=darkred]
          >>>[/color][/color][/color]

          I'll leave the clarification to someone else.

          Andrew
          dalke@dalkescie ntific.com


          Comment

          • Andrew Dalke

            #6
            Re: Why doesn't __call__ lead to infinite recursion?

            Aahz:[color=blue]
            > No time to investigate further, but all your examples used classic
            > classes instead of new-style classes; I'm pretty sure that new-style
            > classes will more closely emulate the way functions work. There's also
            > the wrinkle I didn't mention that functions use a dict proxy IIRC.[/color]

            Interesting. Very interesting.
            [color=blue][color=green][color=darkred]
            >>> class XYZ(object):[/color][/color][/color]
            .... def __init__(self):
            .... def abc(x):
            .... print "Hello", x
            .... self.__call__ = abc
            .... def __call__(self, x):
            .... print "Yo", x
            ....[color=blue][color=green][color=darkred]
            >>> xyz = XYZ()
            >>> xyz("fred")[/color][/color][/color]
            Yo fred[color=blue][color=green][color=darkred]
            >>>
            >>> getattr(xyz, "__call__")[/color][/color][/color]
            <function abc at 0x0168CB70>[color=blue][color=green][color=darkred]
            >>>[/color][/color][/color]
            I wonder if this will affect any of my code.

            It does explain the observed differences better, since FunctionType
            in 2.3 is derived from object while my class was not.

            Andrew
            dalke@dalkescie ntific.com


            Comment

            • Michael Hudson

              #7
              Re: Why doesn't __call__ lead to infinite recursion?

              "Andrew Dalke" <adalke@mindspr ing.com> writes:
              [color=blue]
              > Aahz:[color=green]
              > > No time to investigate further, but all your examples used classic
              > > classes instead of new-style classes; I'm pretty sure that new-style
              > > classes will more closely emulate the way functions work. There's also
              > > the wrinkle I didn't mention that functions use a dict proxy IIRC.[/color]
              >
              > Interesting. Very interesting.[/color]

              Yes :-)

              You have to have something like this when you do things like 'print
              type(foo)'. This should call the *types* *bound* __str__ method, not
              try to call the *instances* *unbound* __str__ method...

              Cheers,
              mwh

              --
              Its unmanageable complexity has spawned more fear-preventing tools
              than any other language, but the solution _should_ have been to
              create and use a language that does not overload the whole goddamn
              human brain with irrelevant details. -- Erik Naggum, comp.lang.lisp

              Comment

              • Beni Cherniavsky

                #8
                Re: Why doesn't __call__ lead to infinite recursion?

                In article <87ekzldf3d.fsf @pobox.com>, John J. Lee wrote:[color=blue]
                >aahz@pythoncra ft.com (Aahz) writes:
                >[...][color=green]
                >> you'll see __call__ there. When you do foo(), Python actually does
                >> type(foo).__cal l__(foo). Because type(foo).__cal l__ is manipulating
                >> foo, you don't get the circular reference.[/color]
                >
                >Still seems weird that type(foo).__cal l__.__call__ (etc.) is defined.
                >[/color]
                [I'm afraid I sent this 2 or 3 already in private by mistake; resending in
                public. I apologize, it's late...]

                The point is that down there, sits the C level which doesn't go
                through the Python definition. When you call ``foo()``, ``type(foo)``
                is asked at the *C* level how to call it (read: the type struct is
                accessed and the call-behavior slot, if non-NULL, is called by plain
                C function call - which can't be intercepted in C so there is no futher
                recursion).

                It so happens that classes, when asked this, go back into the Python
                level and look for the `__call__` attribute. They do this for all
                operations, giving you the dynamism and flexibility we all love.

                OTOH, functions, when asked this, simply execute their code objects with the
                given arguments. The attribute `__call__` on function is a "proxy" attribute.
                It is not used by function objects for the call, it only exposes the C-level
                call machinery at Python level. The same convention is used for other
                built-in types and operations, so that from Python you see Python-level and
                C-level methods in the same way. You only have to be aware of the distinction
                when working with bizzare extension types that don't respect this convention,
                or asking questions like this one about how it all works... (Another one: why
                __getattribute_ _ is not infinitely recusive? Same explanation.)

                --
                Beni Cherniavsky <[email protected] on.ac.il>

                Look, Mom, no viruses! [Hint: I use GNU/Linux]

                Comment

                Working...