Function creation (what happened?)

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

    Function creation (what happened?)

    Can somebody give me an explanation what happened here (or point me to
    some docs)?

    Code:

    HMMM = None

    def w(fn):
    print 'fn:', id(fn)
    HMMM = fn
    print 'HMMM:', id(HMMM)
    def wrapper(*v, **kw):
    fn(*v, **kw)
    wrapper.i = fn
    print 'wrapper:', id(wrapper)
    return wrapper

    class A:
    @w
    def __init__(self): pass

    print 'A.__init__:', id(A.__init__)
    print 'A.__init__.i:' , id(A.__init__.i )
    print 'HMMM:', id(HMMM)



    Output:

    fn: 10404208
    HMMM: 10404208
    wrapper: 10404272
    A.__init__: 10376136
    A.__init__.i: 10404208
    HMMM: 505264624



    Why did HMMM changed his id?!
  • Chris Hulan

    #2
    Re: Function creation (what happened?)

    On May 9, 8:25 am, Viktor <alefn...@gmail .comwrote:
    Can somebody give me an explanation what happened here (or point me to
    some docs)?
    >
    Code:
    >
    HMMM = None
    >
    def w(fn):
    print 'fn:', id(fn)
    HMMM = fn
    print 'HMMM:', id(HMMM)
    def wrapper(*v, **kw):
    fn(*v, **kw)
    wrapper.i = fn
    print 'wrapper:', id(wrapper)
    return wrapper
    >
    class A:
    @w
    def __init__(self): pass
    >
    print 'A.__init__:', id(A.__init__)
    print 'A.__init__.i:' , id(A.__init__.i )
    print 'HMMM:', id(HMMM)
    >
    Output:
    >
    fn: 10404208
    HMMM: 10404208
    wrapper: 10404272
    A.__init__: 10376136
    A.__init__.i: 10404208
    HMMM: 505264624
    >
    Why did HMMM changed his id?!
    The HMMM inside w is local to that function and is not the same HMMM
    that is defined just before w.
    Have a look at http://docs.python.org/ref/global.html

    cheers

    Comment

    • Duncan Booth

      #3
      Re: Function creation (what happened?)

      Viktor <alefnula@gmail .comwrote:
      Can somebody give me an explanation what happened here (or point me to
      some docs)?
      >
      Code:
      >
      HMMM = None
      >
      def w(fn):
      print 'fn:', id(fn)
      HMMM = fn
      print 'HMMM:', id(HMMM)
      def wrapper(*v, **kw):
      fn(*v, **kw)
      wrapper.i = fn
      print 'wrapper:', id(wrapper)
      return wrapper
      >
      class A:
      @w
      def __init__(self): pass
      >
      print 'A.__init__:', id(A.__init__)
      print 'A.__init__.i:' , id(A.__init__.i )
      print 'HMMM:', id(HMMM)
      >
      >
      >
      Output:
      >
      fn: 10404208
      HMMM: 10404208
      wrapper: 10404272
      A.__init__: 10376136
      A.__init__.i: 10404208
      HMMM: 505264624
      >
      >
      >
      Why did HMMM changed his id?!
      It didn't: global HMMM refers to None both before and after executing
      the rest of your code. The other HMMM is local to a particular
      invocation of w. Try the same steps interactively (and try printing the
      values not just the ids) and it may be more obvious:
      >>HMMM = None
      >>print 'HMMM:', id(HMMM)
      HMMM: 505264624
      >>def w(fn):
      print 'fn:', id(fn)
      HMMM = fn
      print 'HMMM:', id(HMMM)
      def wrapper(*v, **kw):
      fn(*v, **kw)
      wrapper.i = fn
      print 'wrapper:', id(wrapper)
      return wrapper
      >>class A:
      @w
      def __init__(self): pass


      fn: 18299952
      HMMM: 18299952
      wrapper: 18300016
      >>print 'HMMM:', id(HMMM), HMMM
      HMMM: 505264624 None
      >>>

      Comment

      • Peter Otten

        #4
        Re: Function creation (what happened?)

        Viktor wrote:
        Can somebody give me an explanation what happened here (or point me to
        some docs)?
        >
        Code:
        >
        HMMM = None
        >
        def w(fn):
        print 'fn:', id(fn)
        HMMM = fn
        print 'HMMM:', id(HMMM)
        This prints the id() of the local (to the function w()) HMMM variable
        def wrapper(*v, **kw):
        fn(*v, **kw)
        wrapper.i = fn
        print 'wrapper:', id(wrapper)
        return wrapper
        >
        class A:
        @w
        def __init__(self): pass
        >
        print 'A.__init__:', id(A.__init__)
        print 'A.__init__.i:' , id(A.__init__.i )
        print 'HMMM:', id(HMMM)
        while this prints the id() of the global HMMM variable. Python assumes that
        a variable is local to a function if there is an assignment to that
        variable anywhere inside that function.

        If you want to change the variable (Python-lingo "rebind the name") HMMM
        declare it as global in the function:

        def w(fn):
        global HMMM
        # ...
        HMMM = fn
        # ...

        Otherwise I've no idea what you are trying to do here...

        Peter

        Comment

        • Viktor

          #5
          Re: Function creation (what happened?)

          This completely slipped of my mind... :)

          I'm trying to change the:


          So if the function is method it prints ClassName.Metho dName instead of
          MethodName(self |klass|cls=<... ClassName>).

          But it turned out that in the decorator, the wrapped function is
          always just a TypeFunction (I cannot find out if the function is
          method, classmethod, staticmethod or just a plain function - tried
          with inspect also)... And what is most interesting, when I do:

          def w(fn):
          print 'fn:', id(fn)
          return fn

          class A:
          @w
          def __init__(self): pass

          print 'A.__init__:', id(A.__init__)

          It turns out that the function I receive in the wrapper (even when I
          return the same function) is not the function which will finally be
          attached to the class...

          Is there a way to find out in the decorator "what will the decorated
          function be"?

          Comment

          • George Sakkis

            #6
            Re: Function creation (what happened?)

            On May 9, 9:02 am, Viktor <alefn...@gmail .comwrote:
            This completely slipped of my mind... :)
            >
            I'm trying to change the:http://wordaligned.org/svn/etc/echo/echo.py
            >
            So if the function is method it prints ClassName.Metho dName instead of
            MethodName(self |klass|cls=<... ClassName>).
            >
            But it turned out that in the decorator, the wrapped function is
            always just a TypeFunction (I cannot find out if the function is
            method, classmethod, staticmethod or just a plain function - tried
            with inspect also)... And what is most interesting, when I do:
            >
            def w(fn):
                print 'fn:', id(fn)
                return fn
            >
            class A:
                @w
                def __init__(self): pass
            >
            print 'A.__init__:', id(A.__init__)
            >
            It turns out that the function I receive in the wrapper (even when I
            return the same function) is not the function which will finally be
            attached to the class...
            >
            Is there a way to find out in the decorator "what will the decorated
            function be"?
            The decorator does receive the correct function. The problem is that
            at this point __init__ is still a plain function, not a method, i.e.
            the sequence is:
            function -decorated function -method

            There are several workarounds if you really want to differentiate
            between functions and methods, none of them perfect:

            - Apply the decorator after the class has been built, i.e. after the
            functions have been wrapped in (unbound) methods:
            A.__init__ = w(A.__init__)

            - (Risky Hack): Guess whether a function is intended to be wrapped in
            a method by checking whether its first argument is named "self".
            Obviously this is not foolproof and it doesn't work for static/class
            methods.

            - Have two different decorators, one intended for plain functions and
            one for functions-to-be-methods (e.g. @deco, @decomethod).

            George

            Comment

            • Viktor

              #7
              Re: Function creation (what happened?)

              I figured out the first two solutions, but the third looks like the
              most cleaner, think I'll use that one...

              Thank you everyone. :)

              On May 9, 3:24 pm, George Sakkis <george.sak...@ gmail.comwrote:
              The decorator does receive the correct function. The problem is that
              at this point __init__ is still a plain function, not a method, i.e.
              the sequence is:
              function -decorated function -method
              >
              There are several workarounds if you really want to differentiate
              between functions and methods, none of them perfect:
              >
              - Apply the decorator after the class has been built, i.e. after the
              functions have been wrapped in (unbound) methods:
              A.__init__ = w(A.__init__)
              >
              - (Risky Hack): Guess whether a function is intended to be wrapped in
              a method by checking whether its first argument is named "self".
              Obviously this is not foolproof and it doesn't work for static/class
              methods.
              >
              - Have two different decorators, one intended for plain functions and
              one for functions-to-be-methods (e.g. @deco, @decomethod).
              >
              George

              Comment

              • Gabriel Genellina

                #8
                Re: Function creation (what happened?)

                En Fri, 09 May 2008 10:02:01 -0300, Viktor <alefnula@gmail .comescribió:
                This completely slipped of my mind... :)
                >
                I'm trying to change the:

                >
                So if the function is method it prints ClassName.Metho dName instead of
                MethodName(self |klass|cls=<... ClassName>).
                >
                But it turned out that in the decorator, the wrapped function is
                always just a TypeFunction (I cannot find out if the function is
                method, classmethod, staticmethod or just a plain function - tried
                with inspect also)... And what is most interesting, when I do:
                The decorator receives the original, plain function (unless you chain decorators) and whatever it returns is used instead of the original function.
                def w(fn):
                print 'fn:', id(fn)
                return fn
                >
                class A:
                @w
                def __init__(self): pass
                >
                print 'A.__init__:', id(A.__init__)
                >
                It turns out that the function I receive in the wrapper (even when I
                return the same function) is not the function which will finally be
                attached to the class...
                Is there a way to find out in the decorator "what will the decorated
                function be"?
                Whatever you return from the decorator.
                But the decorator returns a *function* and A.__init__ is a *method*, an instance method in fact. The function can be found as A.__dict__['__init__']. Try this:

                m = A.__init__
                print m, type(m), id(m)
                f = A.__dict__['__init__']
                print f, type(f), id(f)

                A method combines a function with an instance that becomes the "self" argument. In your case you're building an "unbound" method because it's not tied to a particular instance, but even such unbound method is not the same thing as a plain function (it must ensure that its first argument, when called, is an A instance and not any other object).
                The "magic" that converts a simple function into an instance method, for old-style classes like yours, was in the Class type itself. For new style classes, the descriptor protocol is used. See http://www.python.org/doc/newstyle/

                --
                Gabriel Genellina

                Comment

                • Bruno Desthuilliers

                  #9
                  Re: Function creation (what happened?)

                  Viktor a écrit :
                  This completely slipped of my mind... :)
                  >
                  I'm trying to change the:

                  >
                  So if the function is method it prints ClassName.Metho dName instead of
                  MethodName(self |klass|cls=<... ClassName>).
                  >
                  But it turned out that in the decorator, the wrapped function is
                  always just a TypeFunction
                  s/TypeFunction/function/
                  (I cannot find out if the function is
                  method, classmethod, staticmethod or just a plain function
                  The latter, unless you decorate it with a classmethod or staticmethod
                  object before.
                  - tried
                  with inspect also)... And what is most interesting, when I do:
                  >
                  def w(fn):
                  print 'fn:', id(fn)
                  return fn
                  >
                  class A:
                  @w
                  def __init__(self): pass
                  >
                  print 'A.__init__:', id(A.__init__)
                  >
                  It turns out that the function I receive in the wrapper (even when I
                  return the same function) is not the function which will finally be
                  attached to the class...
                  Yes it is. But A.__init__ is *not* a function, it's a method. To get at
                  the function, you must use A.__dict__['__init__'] or A.__init__.im_f unc
                  Is there a way to find out in the decorator "what will the decorated
                  function be"?
                  Yes : the decorated function is the function you decorate with the
                  decorator.

                  Ok, this requires some explanations (nb: only valid for new-style classes):

                  - First point : what you declare in the class statement are plain
                  ordinary function. When the class statement is executed - that is,
                  usually[1], at import time - these function objects become attributes of
                  the class object.
                  [1] IOW : if your class statement is at the top-level of your module

                  - Second point: the function class implements the descriptor
                  protocol[2], so when an attribute lookup resolves to a function object,
                  the function's class __get__ method is invoked
                  [2] http://users.rcn.com/python/download/Descriptor.htm

                  - Third point: the function's __get__ method returns a method object,
                  either bound (if lookup was done on an instance) or unbound (if the
                  lookup was done on a class).

                  The function's class __get__ method could be implemented this way:

                  def __get__(self, instance, cls):
                  return types.MethodTyp e(self, instance, cls)


                  - Fourth point: a method object is a thin callable wrapper around the
                  function, the instance (if provided), and the class. It could look like
                  this:

                  class Method(object):
                  def __init__(self, func, instance, cls):
                  self.im_func = func
                  self.im_self = instance
                  self.im_class = cls

                  def __repr__(self):
                  if self.im_self is None:
                  # unbound
                  return "<unbound method %s.%s>" \
                  % (self.im_class. __name__, self.im_func.__ name__)
                  else:
                  # bound
                  return "<bound method %s.%s of %s>" \
                  % (self.im_class. __name__,
                  self.im_func.__ name__,
                  self.im_self)

                  def __call__(self, *args, **kw):
                  if self.im_self is None:
                  try:
                  instance, args = args[0], args[1:]
                  except IndexError:
                  raise TypeError(
                  "unbound method %s() must be called with %s instance "
                  " as first argument (got nothing instead)" \
                  % (self.im_func._ _name__, self.im_class._ _name__)
                  if not isinstance(inst ance, self.im_class):
                  raise TypeError(
                  "unbound method %s() must be called with %s instance "
                  " as first argument (got %s instead)" \
                  % (self.im_func._ _name__, self.im_class._ _name__, instance)

                  else:
                  instance = self.im_self
                  return self.im_func(in stance, *args, **kw)

                  The classmethod and staticmethod classes (yes, they are classes...) have
                  their own implementation for the descriptor protocol -
                  classmethod.__g et__ returns a Method instanciated with func, cls,
                  type(cls), and staticmethod.__ get__ returns the original function.


                  So as you can see - and classmethods and staticmethods set aside -, what
                  you decorate is *always* a function. And you just can't tell from within
                  the decorator if this function is called "directly" or from a method
                  object. The only robust solution is to decorate the function with your
                  own custom callable descriptor. Here's a Q&D untested example that
                  should get you started (warning : you'll have to check for classmethods
                  and staticmethods)

                  class wmethod(object) :
                  def __init__(self, method):
                  self.method = method

                  def __call__(self, *args, **kw):
                  # called as a method
                  # your tracing code here
                  # NB : you can access the method's
                  # func, class and instance thru
                  # self.method.im_ *,
                  return self.method(*ar gs, **kw)

                  class w(object):
                  def __init__(self, func):
                  self.func = func

                  def __get__(self, instance, cls):
                  return wmethod(self.fu nc.__get__(inst ance, cls))

                  def __call__(self, *args, **kw):
                  # called as a plain function
                  # your tracing code here
                  return self.func(*args , **kw)


                  HTH

                  Comment

                  Working...