Unexpected Python Behavior

Alex Martelli aleaxit at yahoo.com
Wed Sep 29 08:41:43 CEST 2004


Bengt Richter <bokr at oz.net> wrote:
   ...
> >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
   ...
> 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

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).

> 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__('math').pi):
>         try: return cache[x]

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

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

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

def initialized_locals(**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_firstlineno,
            _c.co_lnotab, tuple(kwds), _c.co_cellvars)
        return new.function(_c, f.func_globals, f.func_name,
            f.func_defaults, tuple(kwds.itervalues()))
    return wrap_f

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

f(41)

The _almost_ is due to the "tuple(kwds.itervalues())" 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_EvalCodeEx.

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...

> >> 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).
> >
> >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.
> >
> I'm not sure why you didn't mention the alternative of not simulating
> but actually using the mechanism of method binding, e.g.,

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



More information about the Python-list mailing list