Unexpected Python Behavior

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

    Unexpected Python Behavior

    For the first time, I have been bitten by Python. The below code
    produces the results:
    False
    True

    when I initially expected the results:
    False
    False

    It took me a while to work out that default keyword argument values
    are likely only evaluated once, which caused the empty dict to be
    shared across classes...

    It certainly something newbie python coders should look out for!

    Simon W.

    ---snip-here---

    class X(object):
    def __init__(self, d={}):
    self.d = d

    a = X()
    b = X()

    print a is b
    print a.d is b.d

    ---snip-here---
  • Fredrik Lundh

    #2
    Re: Unexpected Python Behavior

    Simon Wittber wrote:
    [color=blue]
    > It took me a while to work out that default keyword argument values
    > are likely only evaluated once, which caused the empty dict to be
    > shared across classes...[/color]

    some relevant links:




    [color=blue]
    > It certainly something newbie python coders should look out for![/color]

    it's a well-known "you'll only do this once" mistake. which is a good thing,
    because when you understand why this happens, you have learned a lot about
    how "def" and objects work in Python...

    </F>



    Comment

    • David Pokorny

      #3
      Re: Unexpected Python Behavior


      "Fredrik Lundh" <fredrik@python ware.com> wrote in message >[color=blue]
      > it's a well-known "you'll only do this once" mistake. which is a good[/color]
      thing,

      "Because of this feature, it is good programming practice to not use mutable
      objects as default values." --


      Has it been discussed whether it would be a good idea to issue a warning in
      this case? It strikes me that a warning wouldn't bother veteran programmers,
      since it is really easy to avoid using a mutable default value (nearly
      trivial to modify code that does use mutable default values). I'd imagine it
      makes code more readable too.

      David Pokorny


      Comment

      • Michael Hoffman

        #4
        Re: Unexpected Python Behavior

        David Pokorny wrote:
        [color=blue]
        > "Because of this feature, it is good programming practice to not use mutable
        > objects as default values." --
        > http://www.python.org/doc/faq/genera...etween-objects
        >
        > Has it been discussed whether it would be a good idea to issue a warning in
        > this case?[/color]

        It's not.
        [color=blue]
        > It strikes me that a warning wouldn't bother veteran programmers,
        > since it is really easy to avoid using a mutable default value (nearly
        > trivial to modify code that does use mutable default values). I'd imagine it
        > makes code more readable too.[/color]

        I think you're missing the usefulness of this feature. Go back to the
        link you included and read the next paragraph, "This feature can be useful."
        --
        Michael Hoffman

        Comment

        • Fredrik Lundh

          #5
          Re: Unexpected Python Behavior

          David Pokorny wrote:
          [color=blue]
          > Has it been discussed whether it would be a good idea to issue a warning in
          > this case? It strikes me that a warning wouldn't bother veteran programmers,
          > since it is really easy to avoid using a mutable default value (nearly
          > trivial to modify code that does use mutable default values). I'd imagine it
          > makes code more readable too.[/color]

          1) you cannot tell if an object is mutable or not

          2) there are lots of valid uses for object binding (see the "This feature can
          be useful" part in the FAQ for one example)

          </F>



          Comment

          • Peter Otten

            #6
            Re: Unexpected Python Behavior

            David Pokorny wrote:
            [color=blue]
            >
            > "Fredrik Lundh" <fredrik@python ware.com> wrote in message >[color=green]
            >> it's a well-known "you'll only do this once" mistake. which is a good[/color]
            > thing,
            >
            > "Because of this feature, it is good programming practice to not use
            > mutable objects as default values." --
            >[/color]
            http://www.python.org/doc/faq/genera...etween-objects[color=blue]
            >
            > Has it been discussed whether it would be a good idea to issue a warning
            > in this case? It strikes me that a warning wouldn't bother veteran
            > programmers, since it is really easy to avoid using a mutable default
            > value (nearly trivial to modify code that does use mutable default
            > values). I'd imagine it makes code more readable too.[/color]

            You want a warning? There you are:

            $ cat mutabledefault. py

            def buggy(item, list=[]):
            list.append(ite m)
            return list
            $ pychecker mutabledefault. py
            Processing mutabledefault. ..

            Warnings...

            mutabledefault. py:3: Modifying parameter (list) with a default value may
            have unexpected consequences

            Peter

            Comment

            • Andrea Griffini

              #7
              Re: Unexpected Python Behavior

              On Fri, 24 Sep 2004 14:48:37 +0100, Michael Hoffman
              <m.h.3.9.1.with out.dots.at.cam .ac.uk@example. com> wrote:
              [color=blue]
              >I think you're missing the usefulness of this feature. Go back to the
              >link you included and read the next paragraph, "This feature can be useful."[/color]

              Given that now functions can have attributes, wouldn't be better
              stop pushing ugly, risky and cryptic syntax for poor's man static ?
              IMO one thing is backward compatibility, another is pushing the
              uglyness in the future for no good reasons.

              I am talking about the python docs that everywhere are apparently
              advocating this approach for local cache implementation. Is this
              doing any good for newbies approaching python ?

              Or may be this is more pythonic ? If yes... why ?

              Andrea

              Comment

              • Alex Martelli

                #8
                Re: Unexpected Python Behavior

                Andrea Griffini <agriff@tin.i t> wrote:
                [color=blue]
                > On Fri, 24 Sep 2004 14:48:37 +0100, Michael Hoffman
                > <m.h.3.9.1.with out.dots.at.cam .ac.uk@example. com> wrote:
                >[color=green]
                > >I think you're missing the usefulness of this feature. Go back to the
                > >link you included and read the next paragraph, "This feature can be useful."[/color]
                >
                > Given that now functions can have attributes, wouldn't be better
                > stop pushing ugly, risky and cryptic syntax for poor's man static ?[/color]

                I think it's in fact very nice syntax:

                def f(x, cache=[]):
                if x in cache: ...

                vs your apparently implied suggestion of:

                def f(x):
                if x in f.cache: ...
                f.cache = []

                which among other things suffers from f.cache having to be used in spots
                that come lexically before it's defined; or a decorator equivalent:

                @ set_function_at tributes(cache=[])
                def f(x):
                if x in f.cache: ...

                As for 'risky', both approaches are. The default argument risks the
                user mistakenly passing a corresponding actual-argment; the function
                attribute risks the user rebinding the name. It just don't work when
                the function name aint'a global, as in a closure; nor does it work
                equivalently for an overriden method, e.g.:

                class base:
                def f(self, x, cache=[]):
                if x in cache: ...

                class derived(base):
                def f(self, x, y, cache=[]):
                if y not in cache:
                return base.f(self, x)

                here you get two separate caches, one for base.f and one for derived.f,
                no sweat -- and if you want base.f to use derived.f's cache when call
                from there, just chance the last call to 'base.f(self, x, cache)' --
                obvious, simple, elementary.

                See now what happens with the use of 'self.f.cache' or 'base.f.cache':
                how do you get two separate caches, or get to share one, as easily as
                with the normal approach? You're forcing base's designer to decide for
                all derived classes, the normal approach is clearly more flexible.

                And good luck in explaining all this to beginners -- while the default
                argument approach is really trivial to explain. Simple is better than
                complex, and a general technique like using default values for caching
                is better than a technique based on attributes which is not general
                enough to be just used everywhere.

                _Plus_, teaching this use of default values to beginners helps them
                understand how default values work in all cases. Explaining the dirty
                tricks needed to extend a bit the applicability of using function
                attributes for caching offers no such nice "side advantage".

                [color=blue]
                > IMO one thing is backward compatibility, another is pushing the
                > uglyness in the future for no good reasons.[/color]

                I think there are plenty of good reasons, see above.

                In addition, accessing a local name is of course way faster than
                accessing a global name and then an attribute thereof. In 2.3, I
                repeatably measure 1.75 vs 2.50 usec; in 2.4, 1.25 vs 1.84. If that was
                all the local-argument-default advantage, one could quibble, but coming
                on top of the above-mentioned arguments, I think it's nice too.

                [color=blue]
                > I am talking about the python docs that everywhere are apparently
                > advocating this approach for local cache implementation. Is this
                > doing any good for newbies approaching python ?[/color]

                Yes, it helps them a lot to understand, realize, and remember, that
                default values are shared among all of a function's calls.
                [color=blue]
                > Or may be this is more pythonic ? If yes... why ?[/color]

                It's simpler and more general.

                If I were to use a decorator, I'd rather have it inject 'static locals'
                in some way that doesn't let them be optionally passed as arguments, but
                still in local namespace -- less flexible than the plain good old
                technique, but avoids "accidental argument-passing" and may be nice when
                you need to support *args. I think the implementation of such a
                decorator is a bit messy, though, and besides flexibility you lose the
                nice educational side effect. So, I prefer the status quo in this case.


                Alex

                Comment

                • Andrea Griffini

                  #9
                  Re: Unexpected Python Behavior

                  On Sun, 26 Sep 2004 10:39:15 +0200, aleaxit@yahoo.c om (Alex Martelli)
                  wrote:
                  [color=blue]
                  >vs your apparently implied suggestion of:
                  >
                  >def f(x):
                  > if x in f.cache: ...
                  >f.cache = [][/color]

                  I like more...

                  def f(x):
                  if not hasattr(f,"cach e"):
                  f.cache = []
                  ...
                  [color=blue]
                  >which among other things suffers from f.cache having to be used in spots
                  >that come lexically before it's defined; or a decorator equivalent:
                  >
                  >@ set_function_at tributes(cache=[])
                  >def f(x):
                  > if x in f.cache: ...[/color]

                  The best I can think to is something like

                  def f(x):
                  static cache = []
                  ...

                  In other languages (mostly C) there cases in which I found
                  nice the idea of a local name for a globally living object.
                  [color=blue]
                  >As for 'risky', both approaches are. The default argument risks the
                  >user mistakenly passing a corresponding actual-argment; the function
                  >attribute risks the user rebinding the name.[/color]

                  ?

                  Passing a parameter to a function that, by the way, is declaring
                  it *wants* it doesn't seem to me the same as messing with
                  internals of something from the outside.
                  [color=blue]
                  >It just don't work when the function name aint'a global, as in a
                  >closure; nor does it work equivalently for an overriden method, e.g.:[/color]

                  ?

                  def foo(x):
                  def bar(y):
                  if not hasattr(bar,"ca che"):
                  bar.cache = []
                  bar.cache.appen d(x+y)
                  return bar.cache
                  return bar

                  bar_1 = foo(1)
                  bar_2 = foo(2)
                  print bar_1(1),bar_1( 2),bar_1(3)
                  print bar_2(1),bar_2( 2),bar_2(3)
                  [color=blue]
                  >here you get two separate caches, one for base.f and one for derived.f,
                  >no sweat -- and if you want base.f to use derived.f's cache when call
                  >from there, just chance the last call to 'base.f(self, x, cache)' --
                  >obvious, simple, elementary.[/color]

                  When you need to mess with the those vars from the "outside" then
                  it's clear you're not looking for a static; probably you're not
                  even looking for a function as the interaction with the "state" is
                  getting too important. IMO in these cases it's *way* better to
                  use a class instead (may be one implementing the call interface).
                  [color=blue]
                  >And good luck in explaining all this to beginners -- while the default
                  >argument approach is really trivial to explain. Simple is better than
                  >complex, and a general technique like using default values for caching
                  >is better than a technique based on attributes which is not general
                  >enough to be just used everywhere.[/color]

                  Once reading that default were evaluated at function definition
                  time was enough for me; and I honestly say that I don't remember
                  ever falling for this trap of modifiable default values (so far).

                  However this seems happening quite frequently to novices; actually
                  *very* frequently. So either all the world is wrong or this very
                  specific part of the language has a problem.
                  [color=blue]
                  >_Plus_, teaching this use of default values to beginners helps them
                  >understand how default values work in all cases. Explaining the dirty
                  >tricks needed to extend a bit the applicability of using function
                  >attributes for caching offers no such nice "side advantage".[/color]

                  Sure there is a plus in clearly understanding that function
                  definition is an executable statement in python. But using
                  an unrelated arbitrary fact (that default values are evaluated
                  at that time instead than at call time) isn't IMO a good example.
                  A better one I would say is ...

                  if raw_input("A or B ?") == "A":
                  def foo(x):
                  return x * 2
                  else:
                  def foo(x):
                  return x * x

                  print foo(12)
                  [color=blue]
                  >I think there are plenty of good reasons, see above.[/color]

                  I didn't find any of them *really* good
                  [color=blue]
                  >In addition, accessing a local name is of course way faster than
                  >accessing a global name and then an attribute thereof.[/color]

                  Yeah, static are slower than necessary; and uglier also.

                  May be the direction could be fixing that instead of just
                  pushing towards an ugly hack that just happens to work.
                  [color=blue]
                  >Yes, it helps them a lot to understand, realize, and remember, that
                  >default values are shared among all of a function's calls.[/color]

                  That's the wart!
                  [color=blue]
                  >It's simpler and more general.[/color]

                  To me seems an unrelated side-effect of the decision of
                  when to evaluate default parameters. I'm not questioning
                  the decision per se (it has pros and cons... for example
                  you can't use things like "def foo(x,y,z=x+y)" ) but that
                  using fake parameters for static is ugly and error prone.


                  Just my 0.02 euros of (still) fresh eye opinion


                  Andrea

                  Comment

                  • Alex Martelli

                    #10
                    Re: Unexpected Python Behavior

                    Andrea Griffini <agriff@tin.i t> wrote:
                    [color=blue]
                    > On Sun, 26 Sep 2004 10:39:15 +0200, aleaxit@yahoo.c om (Alex Martelli)
                    > wrote:
                    >[color=green]
                    > >vs your apparently implied suggestion of:
                    > >
                    > >def f(x):
                    > > if x in f.cache: ...
                    > >f.cache = [][/color]
                    >
                    > I like more...
                    >
                    > def f(x):
                    > if not hasattr(f,"cach e"):
                    > f.cache = []
                    > ...[/color]

                    This means _every_ run of f will be loaded down by this test, just to
                    make everything less concise...?! Horrible trade-off, IMHO.
                    [color=blue]
                    > The best I can think to is something like
                    >
                    > def f(x):
                    > static cache = []
                    > ...
                    >
                    > In other languages (mostly C) there cases in which I found
                    > nice the idea of a local name for a globally living object.[/color]

                    I don't think you stand a snowball's chance in hell to make this horrid
                    change become part of Python (thanks be!) -- I suggest you look at other
                    sort-of-Pythonic languages such as (e.g.) the new Qu, which may be more
                    open to changes of this nature.
                    [color=blue][color=green]
                    > >As for 'risky', both approaches are. The default argument risks the
                    > >user mistakenly passing a corresponding actual-argment; the function
                    > >attribute risks the user rebinding the name.[/color]
                    >
                    > ?[/color]

                    import math

                    def f(x):
                    try: return f.cache[x]
                    except KeyError:
                    f.cache[x] = result = math.sin(x)
                    return result
                    f.cache = {}

                    def g(x):
                    try: return g.cache[x]
                    except KeyError:
                    g.cache[x] = result = math.cos(x)
                    return result
                    g.cache = {}

                    print f(0.2), g(0.2), f(0.2), g(0.3)

                    # note: oops coming
                    f, g = g, f

                    print f(0.2), g(0.2), f(0.2), g(0.3)


                    Basically, the idea of having f use f.cache depends on the unstated
                    assumption that global name 'f' will forevermore refer to the same
                    object it used to refer to at the time f.cache was initialized and the
                    first few entries of f.cache were set. You say you consider it "risky"
                    to use f's default attribute values (which stick to the object, not to
                    the name), and I reply, _THIS_ inferior idiom you advocate is the truly
                    risky one -- it relies on a "fixed" name<->object correspondence which
                    NOTHING in Python guarantees or even suggests.

                    [color=blue]
                    > Passing a parameter to a function that, by the way, is declaring
                    > it *wants* it doesn't seem to me the same as messing with
                    > internals of something from the outside.[/color]

                    If you want to hint that a parameter is really 'internal' name it with a
                    leading underscore, that's Python's convention.

                    [color=blue][color=green]
                    > >here you get two separate caches, one for base.f and one for derived.f,
                    > >no sweat -- and if you want base.f to use derived.f's cache when call
                    > >from there, just chance the last call to 'base.f(self, x, cache)' --
                    > >obvious, simple, elementary.[/color]
                    >
                    > When you need to mess with the those vars from the "outside" then
                    > it's clear you're not looking for a static; probably you're not
                    > even looking for a function as the interaction with the "state" is
                    > getting too important. IMO in these cases it's *way* better to
                    > use a class instead (may be one implementing the call interface).[/color]

                    And go to the huge trouble of simulating method-binding?! Puh-LEEZE.
                    Using callable classes in lieu of a class's ordinary methods, when such
                    methods with perfectly normal Python semantics will do, is just plain
                    silly -- looking for complexity where no need for it exists.

                    [color=blue][color=green]
                    > >And good luck in explaining all this to beginners -- while the default
                    > >argument approach is really trivial to explain. Simple is better than
                    > >complex, and a general technique like using default values for caching
                    > >is better than a technique based on attributes which is not general
                    > >enough to be just used everywhere.[/color]
                    >
                    > Once reading that default were evaluated at function definition
                    > time was enough for me; and I honestly say that I don't remember
                    > ever falling for this trap of modifiable default values (so far).[/color]

                    Good for you -- I did, a couple of times.
                    [color=blue]
                    > However this seems happening quite frequently to novices; actually
                    > *very* frequently. So either all the world is wrong or this very
                    > specific part of the language has a problem.[/color]

                    Are you not part of this world, if _ALL_ the world is wrong yet you
                    never had the problem? In my case, the problems were due to previous
                    experience with C++ -- a traumatic rite of passage which fortunately not
                    everybody HAS had to go through.

                    [color=blue][color=green]
                    > >_Plus_, teaching this use of default values to beginners helps them
                    > >understand how default values work in all cases. Explaining the dirty
                    > >tricks needed to extend a bit the applicability of using function
                    > >attributes for caching offers no such nice "side advantage".[/color]
                    >
                    > Sure there is a plus in clearly understanding that function
                    > definition is an executable statement in python. But using
                    > an unrelated arbitrary fact (that default values are evaluated
                    > at that time instead than at call time) isn't IMO a good example.[/color]

                    Nope, that's not what I said. I said, and I repeat (and expand since
                    clearly it wasn't clear to you): learning to use default values for a
                    cache, rather than the no-advantages-whatsoever technique you advocate
                    of using function attributes for the same purpose, besides all other
                    advantages, has the one of firmly fixing the concept that default values
                    are evaluate once, at def time.
                    [color=blue][color=green]
                    > >In addition, accessing a local name is of course way faster than
                    > >accessing a global name and then an attribute thereof.[/color]
                    >
                    > Yeah, static are slower than necessary; and uglier also.
                    >
                    > May be the direction could be fixing that instead of just
                    > pushing towards an ugly hack that just happens to work.[/color]

                    I don't find default values ugly.

                    [color=blue][color=green]
                    > >Yes, it helps them a lot to understand, realize, and remember, that
                    > >default values are shared among all of a function's calls.[/color]
                    >
                    > That's the wart![/color]

                    So here's our deep disagreement. I would find it an intolerable wart if
                    Python did _anything else_ except evaluate expressions when it meets
                    them, implicitly "saving them away somewhere or other" to be reevaluated
                    over and over AND over again -- in SOME cases (chosen HOW...?!).

                    [color=blue][color=green]
                    > >It's simpler and more general.[/color]
                    >
                    > To me seems an unrelated side-effect of the decision of
                    > when to evaluate default parameters. I'm not questioning
                    > the decision per se (it has pros and cons... for example
                    > you can't use things like "def foo(x,y,z=x+y)" ) but that
                    > using fake parameters for static is ugly and error prone.[/color]

                    You call the decision "the wart!" and then claim not to be questioning
                    it?! I've seen hypocrisy in my life, but this ridiculous combination
                    sure takes the prize!


                    Alex

                    Comment

                    • Bengt Richter

                      #11
                      Re: Unexpected Python Behavior

                      On Tue, 28 Sep 2004 19:12:16 +0200, aleaxit@yahoo.c om (Alex Martelli) wrote:
                      [...][color=blue][color=green][color=darkred]
                      >> >As for 'risky', both approaches are. The default argument risks the
                      >> >user mistakenly passing a corresponding actual-argment; the function
                      >> >attribute risks the user rebinding the name.[/color]
                      >>
                      >> ?[/color]
                      >
                      >import math
                      >
                      >def f(x):
                      > try: return f.cache[x]
                      > except KeyError:
                      > f.cache[x] = result = math.sin(x)
                      > return result
                      >f.cache = {}
                      >
                      >def g(x):
                      > try: return g.cache[x]
                      > except KeyError:
                      > g.cache[x] = result = math.cos(x)
                      > return result
                      >g.cache = {}
                      >
                      >print f(0.2), g(0.2), f(0.2), g(0.3)
                      >
                      ># note: oops coming
                      >f, g = g, f
                      >
                      >print f(0.2), g(0.2), f(0.2), g(0.3)
                      >
                      >
                      >Basically, the idea of having f use f.cache depends on the unstated
                      >assumption that global name 'f' will forevermore refer to the same
                      >object it used to refer to at the time f.cache was initialized and the
                      >first few entries of f.cache were set. You say you consider it "risky"
                      >to use f's default attribute values (which stick to the object, not to
                      >the name), and I reply, _THIS_ inferior idiom you advocate is the truly
                      >risky one -- it relies on a "fixed" name<->object correspondence which
                      >NOTHING in Python guarantees or even suggests.
                      >[/color]
                      Thanks for highlighting this. It is one of my pet peeves that there is
                      no local "self" reference available for a function or class etc., and
                      no way (other than one-at-a-time method-self binding) to get a def-time
                      expression value bound to a locally visible name. Maybe we could have
                      a triple star in the arg list to delimit syntax and effect like defaults
                      but which don't become part of the arg count? E.g.,

                      def f(x, *** cache={}, pi = __import__('mat h').pi):
                      try: return cache[x]
                      ...etc

                      Or arrange it differently, and add comments

                      def f(x, ***
                      # presets
                      cache={}, # XXX lru later ;-)
                      pi = __import__('mat h').pi
                      ): # run time
                      try: return cache[x]
                      ...etc[color=blue]
                      >[color=green]
                      >> Passing a parameter to a function that, by the way, is declaring
                      >> it *wants* it doesn't seem to me the same as messing with
                      >> internals of something from the outside.[/color]
                      >
                      >If you want to hint that a parameter is really 'internal' name it with a
                      >leading underscore, that's Python's convention.
                      >
                      >[color=green][color=darkred]
                      >> >here you get two separate caches, one for base.f and one for derived.f,
                      >> >no sweat -- and if you want base.f to use derived.f's cache when call
                      >> >from there, just chance the last call to 'base.f(self, x, cache)' --
                      >> >obvious, simple, elementary.[/color]
                      >>
                      >> When you need to mess with the those vars from the "outside" then
                      >> it's clear you're not looking for a static; probably you're not
                      >> even looking for a function as the interaction with the "state" is
                      >> getting too important. IMO in these cases it's *way* better to
                      >> use a class instead (may be one implementing the call interface).[/color]
                      >
                      >And go to the huge trouble of simulating method-binding?! Puh-LEEZE.
                      >Using callable classes in lieu of a class's ordinary methods, when such
                      >methods with perfectly normal Python semantics will do, is just plain
                      >silly -- looking for complexity where no need for it exists.
                      >[/color]
                      I'm not sure why you didn't mention the alternative of not simulating
                      but actually using the mechanism of method binding, e.g.,
                      [color=blue][color=green][color=darkred]
                      >>> import math
                      >>> def f(cache, x):[/color][/color][/color]
                      ... try: return cache[x]
                      ... except KeyError:
                      ... cache[x] = result = math.sin(x)
                      ... return result
                      ...[color=blue][color=green][color=darkred]
                      >>> f = f.__get__({}, dict)
                      >>> f[/color][/color][/color]
                      <bound method dict.f of {}>[color=blue][color=green][color=darkred]
                      >>> f(math.pi/6.)[/color][/color][/color]
                      0.4999999999999 9994[color=blue][color=green][color=darkred]
                      >>> f.im_self[/color][/color][/color]
                      {0.523598775598 29882: 0.4999999999999 9994}[color=blue][color=green][color=darkred]
                      >>> f(0.0)[/color][/color][/color]
                      0.0[color=blue][color=green][color=darkred]
                      >>> f.im_self[/color][/color][/color]
                      {0.523598775598 29882: 0.4999999999999 9994, 0.0: 0.0}

                      And you can't pass extra arguments ...
                      (BTW, (to OP) note that f sees bound "self" (the cache) as part of the arg count,
                      the way f the function sees it. This would have been be clearer if I had not rebound 'f')
                      [color=blue][color=green][color=darkred]
                      >>> f({}, 0.0)[/color][/color][/color]
                      Traceback (most recent call last):
                      File "<stdin>", line 1, in ?
                      TypeError: f() takes exactly 2 arguments (3 given)

                      Regards,
                      Bengt Richter

                      Comment

                      • Alex Martelli

                        #12
                        Re: Unexpected Python Behavior

                        Bengt Richter <bokr@oz.net> wrote:
                        ...[color=blue][color=green]
                        > >Basically, the idea of having f use f.cache depends on the unstated
                        > >assumption that global name 'f' will forevermore refer to the same
                        > >object it used to refer to at the time f.cache was initialized and the[/color][/color]
                        ...[color=blue]
                        > Thanks for highlighting this. It is one of my pet peeves that there is
                        > no local "self" reference available for a function or class etc., and
                        > no way (other than one-at-a-time method-self binding) to get a def-time
                        > expression value bound to a locally visible name. Maybe we could have[/color]

                        The reason for this is that function attributes are not meant to make
                        functions into kinda-classes-though-not-quite, so there's no special
                        interest in having a function refer to its own attributes. Rather,
                        function attributes were introduced (not all that long ago) to let
                        metainformation about a function (used by frameworks such as parsers) be
                        stored more handily than into the docstring (which is what some such
                        frameworks used to abuse for the purpose).
                        [color=blue]
                        > a triple star in the arg list to delimit syntax and effect like defaults
                        > but which don't become part of the arg count? E.g.,
                        >
                        > def f(x, *** cache={}, pi = __import__('mat h').pi):
                        > try: return cache[x][/color]

                        I'm sure that if these semantics were accepted, the syntax for them
                        would inevitably be a decorator:

                        @ initialized_loc als(cache={}, pi=__import__(' math').pi)
                        def f(x):
                        try: return cache[x]
                        ...etc...

                        If it was ok to make these "initialize d locals" non-rebindable, I think
                        this decorator could _almost_ be implemented today with a closure like:

                        def initialized_loc als(**kwds):
                        import new
                        def wrap_f(f):
                        _c = f.func_code
                        _c = new.code(_c.co_ argcount, _c.co_nlocals, _c.co_stacksize ,
                        _c.co_flags, _c.co_code, _c.co_consts, _c.co_names,
                        _c.co_varnames, _c.co_filename, _c.co_name,
                        _c.co_firstline no,
                        _c.co_lnotab, tuple(kwds), _c.co_cellvars)
                        return new.function(_c , f.func_globals, f.func_name,
                        f.func_defaults , tuple(kwds.iter values()))
                        return wrap_f

                        @ initialized_loc als(a=23, b=45)
                        def f(c, d=67):
                        print a, b, c, d

                        f(41)

                        The _almost_ is due to the "tuple(kwds.ite rvalues())" not being QUITE
                        right -- it should be something like map(_make_cell, kwds.itervalues ())
                        for a hypothetical factory '_make_cell' that's able to make new cell
                        objects. Unfortunately, the factory is hypothetical because I believe
                        there is no such thing as a Python-accessible way to make a cell: cells
                        are only made in ceval.c, specifically PyEval_EvalCode Ex.

                        Maybe (no time to check this up right now...), all that we need to
                        enable this particular "extreme sport" is a small C-coded extension
                        which exposes a _make_cell function to Python. It seems to me that
                        every other piece is already in place. If so, then we could make this
                        available for experimentation without it needing to get into the Python
                        core immediately. Another possibility would be to try and grasp the
                        _source_ for the function being wrapped, wrap it textually into the
                        outer function needed for the closure, and do an 'exec' on the resulting
                        string to make the new function object; however, that one looks
                        definitely dirty to me, while exposing the ability to make cells doesn't
                        look any blacker, in black-magic terms, than existing abilities such as
                        those offered by new.code... Finally, a pragmatic compromise might be
                        to use 'exec' on a tiny constructed closure just to build and hijack its
                        f.func_closure, a tuple of cells. This looks both dirty and black
                        magic, so it might impartially make everybody equally unhappy, but with
                        a couple hours of experimentation it might already fall into place;-).


                        IOW: once again, like in another thread going on right now, I'd advise
                        to focus on the implementation -- syntax can wait. If the
                        implementation is easy to explain, it could be a good thing...
                        [color=blue][color=green][color=darkred]
                        > >> When you need to mess with the those vars from the "outside" then
                        > >> it's clear you're not looking for a static; probably you're not
                        > >> even looking for a function as the interaction with the "state" is
                        > >> getting too important. IMO in these cases it's *way* better to
                        > >> use a class instead (may be one implementing the call interface).[/color]
                        > >
                        > >And go to the huge trouble of simulating method-binding?! Puh-LEEZE.
                        > >Using callable classes in lieu of a class's ordinary methods, when such
                        > >methods with perfectly normal Python semantics will do, is just plain
                        > >silly -- looking for complexity where no need for it exists.
                        > >[/color]
                        > I'm not sure why you didn't mention the alternative of not simulating
                        > but actually using the mechanism of method binding, e.g.,[/color]

                        Because it's just as unusable in the context we were discussing, i.e.:

                        class base:
                        def f(self, x, cache={}): ...

                        class deriv(base):
                        def f(self, x, cache={}): ...

                        the f's one and only "currying [[prebinding]] slot" is needed for the
                        appropriate self object(s), can't be "wasted" on the cache dict(s).


                        Alex

                        Comment

                        • Greg Ewing

                          #13
                          Re: Unexpected Python Behavior

                          Alex Martelli wrote:[color=blue]
                          > I think it's in fact very nice syntax:
                          >
                          > def f(x, cache=[]):
                          > if x in cache: ...[/color]

                          No, it's not, because it suggests that cache is
                          intended to be an optional parameter, whereas it
                          probably isn't.

                          This is a hack. Don't do it.

                          --
                          Greg Ewing, Computer Science Dept,
                          University of Canterbury,
                          Christchurch, New Zealand


                          Comment

                          • David Bolen

                            #14
                            Re: Unexpected Python Behavior

                            Greg Ewing <greg@cosc.cant erbury.ac.nz> writes:
                            [color=blue]
                            > Alex Martelli wrote:[color=green]
                            > > I think it's in fact very nice syntax:
                            > > def f(x, cache=[]):
                            > > if x in cache: ...[/color]
                            >
                            > No, it's not, because it suggests that cache is
                            > intended to be an optional parameter, whereas it
                            > probably isn't.[/color]

                            Depends - I'd probably read that just as you say - it's an optional
                            parameter. But why assume it probably isn't intended that way. If I
                            want to use my own cache I pass it in, otherwise I expect the function
                            to have a default cache that it will use for me.

                            -- David

                            Comment

                            • Andrea Griffini

                              #15
                              Re: Unexpected Python Behavior

                              On Thu, 30 Sep 2004 15:22:04 +1200, Greg Ewing
                              <greg@cosc.cant erbury.ac.nz> wrote:
                              [color=blue]
                              >Alex Martelli wrote:[color=green]
                              >> I think it's in fact very nice syntax:
                              >>
                              >> def f(x, cache=[]):
                              >> if x in cache: ...[/color]
                              >
                              >No, it's not, because it suggests that cache is
                              >intended to be an optional parameter, whereas it
                              >probably isn't.
                              >
                              >This is a hack. Don't do it.[/color]

                              Puh-LEEEZE!!!

                              Who are you to question what Him, the Great Alex, says about it ?

                              That's is a very nice piece of code and I among the others
                              true Alex' believers actually moved over from C to python
                              basically just for the beauty of the idea of moving local
                              statics from the body to the interface!

                              It doesn't matter *what* the great Alex says, just remember
                              *who* said it. Does the discussion about the risk of
                              rebinding names a total nonsense just because the very same
                              applies in that very same example to math.cos and math.sin ?
                              So ? Still it's Alex's word... widsom from another planet
                              you (a no-one) surely can't question.

                              Watch your mouth next time, you worst of all hypocrites...
                              or the maledition of thousands of irrelevant co_*
                              implementation details magic formulas will destroy you.

                              And don't try to say that pychecker actually emits
                              warnings when that cra^H^H^Hbeatif ul technique is
                              used. It's just because the Great still didn't wasted
                              time annihilating the misbelievers behind it.

                              Andrea

                              Comment

                              Working...