FAQ
Hello,

Below startup definitions:

x = 1

def f():
n = 1
def g0(a):
print (x + n + a)
return g0


I'm surprised the snippet below works as expected (py 2.6) without any trick:

g = f()
g(1) # --> 3

This means a (real) closure is built for g0, or what?
Thought I would need instead to use the old trick of pseudo default-parameters:

def f():
n = 1
def g0(a, n=n, x=x):
print (x + n + a)
return g0

to let the inner func g0 "remember" outer values. Why is this idiom used, then? Has something changed, or do I miss a relevant point?

The bit below also works:

x = 2
...
g(1) # --> 4

which seems to indicate python really embeds "symbolic references" (*) to outer *variables*, when creating a closure for g0. Not "pointer references" (**), otherwise the replacement of x would not be seen by the closure --like in the case of default-parameter.
Actually, I find this _Bad_. Obviously, the func's behaviour and result depend on arbitrary external values (referentially opaque). What do you think?

Denis

(*) by name, indirect access, second table lookup
(**) by address, direct access, no second lookup
________________________________

la vita e estrany

http://spir.wikidot.com/

Search Discussions

  • Wayne Werner at Nov 30, 2009 at 12:21 pm

    On Mon, Nov 30, 2009 at 4:24 AM, spir wrote:

    which seems to indicate python really embeds "symbolic references" (*) to
    outer *variables*, when creating a closure for g0. Not "pointer references"
    (**), otherwise the replacement of x would not be seen by the closure --like
    in the case of default-parameter.
    Actually, I find this _Bad_. Obviously, the func's behaviour and result
    depend on arbitrary external values (referentially opaque). What do you
    think?
    I'm not sure *why*/how this behaviour really works, other than it treats x
    as a global variable... and probably treats n as something similar.

    I don't know how bad I find it - you should be declaring the variables
    you're planning to use in your function anyway... I'm sure there's *some*
    case that it would end out problematic, but I can't think of one ATM.

    -Wayne

    --
    To be considered stupid and to be told so is more painful than being called
    gluttonous, mendacious, violent, lascivious, lazy, cowardly: every weakness,
    every vice, has found its defenders, its rhetoric, its ennoblement and
    exaltation, but stupidity hasn?t. - Primo Levi
    -------------- next part --------------
    An HTML attachment was scrubbed...
    URL: <http://mail.python.org/pipermail/tutor/attachments/20091130/fe56003f/attachment.htm>
  • Kent Johnson at Nov 30, 2009 at 3:16 pm

    On Mon, Nov 30, 2009 at 5:24 AM, spir wrote:
    Hello,

    Below startup definitions:

    x = 1

    def f():
    ?n = 1
    ?def g0(a):
    ? ?print (x + n + a)
    ?return g0


    I'm surprised the snippet below works as expected (py 2.6) without any trick:

    g = f()
    g(1) ? ?# --> 3

    This means a (real) closure is built for g0, or what?
    Yes, Python has had real (read-only) closures since 2.1 when nested
    scopes where introduced:
    http://docs.python.org/dev/whatsnew/2.1.html#pep-227-nested-scopes

    Python 3 introduces the 'nonlocal' keyword which allows assignment to
    names in enclosing scopes, presumably ending at last the debate about
    whether Python has 'real' closures:
    http://www.python.org/dev/peps/pep-3104/
    Thought I would need instead to use the old trick of pseudo default-parameters:

    def f():
    ?n = 1
    ?def g0(a, n=n, x=x):
    ? ?print (x + n + a)
    ?return g0

    to let the inner func g0 "remember" outer values. Why is this idiom used, then? Has something changed, or do I miss a relevant point?
    That has not been needed since 2.1 though it is still useful when
    closures are created in a loop (because closures are kind of late
    bound - I'm not sure the exact technical explanation):
    In [13]: def f():
    ....: l = []
    ....: for i in range(3):
    ....: def g():
    ....: print i
    ....: l.append(g)
    ....: return l

    In [14]: for g in f(): g()
    ....:
    2
    2
    2


    But with the default argument it captures the value of i each time
    through the loop:
    In [15]: def f():
    ....: l = []
    ....: for i in range(3):
    ....: def g(i=i):
    ....: print i
    ....: l.append(g)
    ....: return l

    In [16]: for g in f(): g()
    ....:

    1
    2
    The bit below also works:

    x = 2
    ...
    g(1) ? ?# --> 4

    which seems to indicate python really embeds "symbolic references" (*) to outer *variables*, when creating a closure for g0. Not "pointer references" (**), otherwise the replacement of x would not be seen by the closure --like in the case of default-parameter.
    In your first definition of f(), x is global and not included in the
    closure. This is the same behaviour you would have in older versions.
    In your second definition of f(), x is bound to a default argument and
    changing the global x doesn't change the result of g().
    Actually, I find this _Bad_. Obviously, the func's behaviour and result depend on arbitrary external values (referentially opaque). What do you think?
    That is always the case when a function accesses globals. Globals are
    _Bad_, yes.

    Kent
  • Hugo Arts at Nov 30, 2009 at 4:38 pm

    On Mon, Nov 30, 2009 at 4:16 PM, Kent Johnson wrote:

    That has not been needed since 2.1 though it is still useful when
    closures are created in a loop (because closures are kind of late
    bound - I'm not sure the exact technical explanation):
    In [13]: def f():
    ? ....: ? ? l = []
    ? ....: ? ? for i in range(3):
    ? ....: ? ? ? ? def g():
    ? ....: ? ? ? ? ? ? print i
    ? ....: ? ? ? ? l.append(g)
    ? ....: ? ? return l

    In [14]: for g in f(): g()
    ? ....:
    2
    2
    2
    This doesn't really have anything to do with closures specifically.
    Variable lookup is done at runtime, not definition time. So when these
    lookups for i are performed the value of i is indeed 2.

    This wouldn't happen if closures used pointer references (as spir
    called them). Then again, even with pointer references modifications
    to mutable variables are still visible inside a closure.

    Hugo
  • Stefan Behnel at Nov 30, 2009 at 3:17 pm

    spir, 30.11.2009 11:24:
    Below startup definitions:

    x = 1

    def f():
    n = 1
    def g0(a):
    print (x + n + a)
    return g0

    I'm surprised the snippet below works as expected (py 2.6) without any trick:

    g = f()
    g(1) # --> 3

    This means a (real) closure is built for g0, or what?
    Yes.

    Thought I would need instead to use the old trick of pseudo default-parameters:

    def f():
    n = 1
    def g0(a, n=n, x=x):
    print (x + n + a)
    return g0

    to let the inner func g0 "remember" outer values. Why is this idiom used, then? Has something changed, or do I miss a relevant point?
    Different use case. The above uses default arguments for n and x that can
    be overridden by callers, but that have a value if callers do not pass
    them. Values in closures can only be modified by the owner(s) of the names
    that participate in the closure (i.e. the function f in this case).

    The bit below also works:

    x = 2
    ...
    g(1) # --> 4
    x is not in the closure, it's a global name.

    the func's behaviour and result depend on arbitrary external values (referentially opaque). What do you think?
    It's a matter of how you use it. Closures make a lot of sense for many
    cases, but there are certainly also cases where using them feels like a
    goto - just like global variables.

    Stefan
  • Hugo Arts at Nov 30, 2009 at 4:19 pm

    On Mon, Nov 30, 2009 at 11:24 AM, spir wrote:
    Hello,

    Below startup definitions:

    x = 1

    def f():
    ?n = 1
    ?def g0(a):
    ? ?print (x + n + a)
    ?return g0


    I'm surprised the snippet below works as expected (py 2.6) without any trick:

    g = f()
    g(1) ? ?# --> 3

    This means a (real) closure is built for g0, or what?
    Thought I would need instead to use the old trick of pseudo default-parameters:

    def f():
    ?n = 1
    ?def g0(a, n=n, x=x):
    ? ?print (x + n + a)
    ?return g0

    to let the inner func g0 "remember" outer values. Why is this idiom used, then? Has something changed, or do I miss a relevant point?
    A real closure is indeed created, but you are missing something.
    Consider this python session:
    x = 0
    def f():
    x = x + 1
    f()
    Traceback (most recent call last):
    File "<pyshell#27>", line 1, in <module>
    f()
    File "<pyshell#26>", line 2, in f
    x = x + 1
    UnboundLocalError: local variable 'x' referenced before assignment

    The python docs offers some insight:

    The execution of a function introduces a new symbol table used for the
    local variables of the function. More precisely, all variable
    assignments in a function store the value in the local symbol table;
    whereas variable references first look in the local symbol table, then
    in the local symbol tables of enclosing functions, then in the global
    symbol table, and finally in the table of built-in names. Thus, global
    variables cannot be directly assigned a value within a function
    (unless named in a global statement), although they may be referenced.

    ( from http://docs.python.org/tutorial/controlflow.html#defining-functions )

    In short, the problem is that writing "x =" will create a new
    (unbound) local name for x, hiding the global one. The following
    reference to x will find the local unbound variable and start
    complaining.

    If you wanted to create a local variable, your idiom is the fix you
    want. If you want to modify the global one, use the 'global x'
    statement to tell the interpreter that explicitly.
    The bit below also works:

    x = 2
    ...
    g(1) ? ?# --> 4

    which seems to indicate python really embeds "symbolic references" (*) to outer *variables*, when creating a closure for g0. Not "pointer references" (**), otherwise the replacement of x would not be seen by the closure --like in the case of default-parameter.
    Actually, I find this _Bad_. Obviously, the func's behaviour and result depend on arbitrary external values (referentially opaque). What do you think?
    I don't quite understand the point you're trying to make. The code
    you're showing above seems like what is 'proper' behaviour to me. Can
    you show an example demonstrating why it is bad?

    Hugo
  • Eike Welk at Nov 30, 2009 at 5:36 pm

    On Monday 30 November 2009, Hugo Arts wrote:
    Consider this python session:
    x = 0
    def f():
    x = x + 1
    f()
    Traceback (most recent call last):
    File "<pyshell#27>", line 1, in <module>
    f()
    File "<pyshell#26>", line 2, in f
    x = x + 1
    UnboundLocalError: local variable 'x' referenced before assignment
    Ah... what a pity I didn't try this. I used similar code in my
    response to Spir and thought it would be somehow connected to
    closures. Sending nonsense statements to the list again...
    The python docs offers some insight:

    The execution of a function introduces a new symbol table used for
    the local variables of the function. More precisely, all variable
    assignments in a function store the value in the local symbol
    table; whereas variable references first look in the local symbol
    table, then in the local symbol tables of enclosing functions, then
    in the global symbol table, and finally in the table of built-in
    names. Thus, global variables cannot be directly assigned a value
    within a function (unless named in a global statement), although
    they may be referenced.

    ( from
    http://docs.python.org/tutorial/controlflow.html#defining-functions
    )

    In short, the problem is that writing "x =" will create a new
    (unbound) local name for x, hiding the global one. The following
    reference to x will find the local unbound variable and start
    complaining.
    I find that behavior quite counterintuitive. I expect:
    1. 'x' is looked up, global variable 'x' is found;
    2. the addition is performed;
    3. a local name 'x' is created and bound to the result of the
    addition.

    Producing the error is not insane. Because referencing a global
    variable and shadowing it in the same statement is bad style.


    Eike.
  • Spir at Nov 30, 2009 at 9:00 pm

    Eike Welk <eike.welk at gmx.net> dixit:
    On Monday 30 November 2009, Hugo Arts wrote:
    Consider this python session:
    x = 0
    def f():
    x = x + 1
    f()
    Traceback (most recent call last):
    File "<pyshell#27>", line 1, in <module>
    f()
    File "<pyshell#26>", line 2, in f
    x = x + 1
    UnboundLocalError: local variable 'x' referenced before assignment
    Ah... what a pity I didn't try this. I used similar code in my
    response to Spir and thought it would be somehow connected to
    closures. Sending nonsense statements to the list again...
    My answer was stupid as well. Actually, guess I hadn't understood the real sense of Hugo's comment... until I tried to answer your post, Eike.

    Denis

    PS:
    I just found by chance an article (actually a pair of) by Paul Graham (Lisp advocate) that really show how pitifully helpless python is (I'm half joking) compared to "Lisp/Perl/Smalltalk/Javascript".
    http://www.paulgraham.com/icad.html (see end of article)
    http://www.paulgraham.com/icadmore.html (search Paul Prescod's reply)

    Quote:
    << I was actually surprised at how badly Python did. I had never realized, for example, that a Python lambda-expression couldn't contain the same things as a named function, or that *variables from enclosing scopes are visible but not modifiable*. Neither Lisp nor Perl nor Smalltalk nor Javascript impose either restriction. >>
    (I highlight)

    Please don't take my quoting as an occasion for flamewar -- I'm just quoting to bring an external point of view into the thread.

    An interesting thing he points earlier is that (provided anonymous funcs were real funcs and changing external vars were possible) python,'s idiom for his example would not be:

    def accum(n):
    lambda i: n += i

    but instead:

    def accum(n):
    return lambda i: return n += i

    But actually he's wrong, I guess, it would even be:

    def accum(n):
    return lambda i: n += i ; return n

    (cannot return a statement)

    Denis
    ________________________________

    la vita e estrany

    http://spir.wikidot.com/
  • Eike Welk at Nov 30, 2009 at 5:09 pm
    Hello Spir!
    On Monday 30 November 2009, spir wrote:
    which seems to indicate python really embeds "symbolic references"
    (*) to outer *variables*, when creating a closure for g0. Not
    "pointer references" (**), otherwise the replacement of x would not
    be seen by the closure --like in the case of default-parameter.
    Actually, I find this _Bad_. Obviously, the func's behaviour and
    result depend on arbitrary external values (referentially opaque).
    If I understand you right, you are proposing that the inner function
    g0 should get a separate copy of the global namespace. The copy should
    be done when the function is defined.

    When this would be implemented, inner functions (g0) would be treated
    very differently from outer functions (f), with respect to global
    variables. I would not like this different treatment. When a global
    variable is changed all functions should see the change.

    I think currently there are three 'containers' where functions can
    find variables in Python:
    - local variables
    - the closure, which is empty in outer functions (f.func_closure)
    - global variables, this is probably f.__module__.__dict__.

    The exact semantics of closures seem to be a bit tricky. Below is some
    behavior I find surprising. I had expected that a local variable 'i'
    is created in function 'inc'. Instead the function fails with an
    error. (Producing the error is IMHO surprising, but sane behavior.):

    In [25]:def clos():
    i = 0
    def inc():
    i = i + 1
    print i
    def pri():
    print i
    return inc, pri

    In [33]:inc, pri = clos()

    In [34]:pri()


    In [35]:inc()
    -------------------------------------------------------------------
    UnboundLocalError Traceback (most recent call last)

    /home/eike/<ipython console> in <module>()
    /home/eike/<ipython console> in inc()
    UnboundLocalError: local variable 'i' referenced before assignment


    ---
    Eike.
  • Spir at Nov 30, 2009 at 6:02 pm
    Hello Eike!

    Eike Welk <eike.welk at gmx.net> dixit:
    Hello Spir!
    On Monday 30 November 2009, spir wrote:
    which seems to indicate python really embeds "symbolic references"
    (*) to outer *variables*, when creating a closure for g0. Not
    "pointer references" (**), otherwise the replacement of x would not
    be seen by the closure --like in the case of default-parameter.
    Actually, I find this _Bad_. Obviously, the func's behaviour and
    result depend on arbitrary external values (referentially opaque).
    If I understand you right, you are proposing that the inner function
    g0 should get a separate copy of the global namespace. The copy should
    be done when the function is defined.

    When this would be implemented, inner functions (g0) would be treated
    very differently from outer functions (f), with respect to global
    variables. I would not like this different treatment. When a global
    variable is changed all functions should see the change.

    I think currently there are three 'containers' where functions can
    find variables in Python:
    - local variables
    - the closure, which is empty in outer functions (f.func_closure)
    - global variables, this is probably f.__module__.__dict__.

    The exact semantics of closures seem to be a bit tricky. Below is some
    behavior I find surprising. I had expected that a local variable 'i'
    is created in function 'inc'. Instead the function fails with an
    error. (Producing the error is IMHO surprising, but sane behavior.):

    In [25]:def clos():
    i = 0
    def inc():
    i = i + 1
    print i
    def pri():
    print i
    return inc, pri

    In [33]:inc, pri = clos()

    In [34]:pri()


    In [35]:inc()
    -------------------------------------------------------------------
    UnboundLocalError Traceback (most recent call last)

    /home/eike/<ipython console> in <module>()
    /home/eike/<ipython console> in inc()
    UnboundLocalError: local variable 'i' referenced before assignment
    Well, this is certainly not specific to closures.

    i = 0
    def f():
    i = i+1
    print i
    f()
    ==> UnboundLocalError

    Imo, in this case, "i = i+1" is a kind of "paradoxal injonction" (lol! not sure of the exact idiom in english). You tell python both to create a local i (thus ignore any other scope to lookup for variables called 'i') and to use global i to define the local one.
    If I were the victim of such a "paradoxal injonction" I would reply with a naughty word!
    ---
    Eike.
    Denis
    ________________________________

    la vita e estrany

    http://spir.wikidot.com/
  • Dave Angel at Dec 1, 2009 at 1:04 am

    spir wrote:
    Hello Eike!

    Eike Welk <eike.welk at gmx.net> dixit:


    <snip>
    Well, this is certainly not specific to closures.

    i = 0
    def f():
    i = i+1
    print i
    f()
    ==> UnboundLocalError

    Imo, in this case, "i = i+1" is a kind of "paradoxal injonction" (lol! not sure of the exact idiom in english). You tell python both to create a local i (thus ignore any other scope to lookup for variables called 'i') and to use global i to define the local one.
    If I were the victim of such a "paradoxal injonction" I would reply with a naughty word!

    I believe the easiest model to understand the behavior is:

    The compiler scans the entire function to find which variables are
    assigned (via =, as, or other syntax) anywhere in the function. Then
    for those variables, all references are done without any dictionary
    accesses. Thus an assignment anywhere (not just on the same line) cause
    all references to be to the (unbound) local.

    DaveA
  • Alan Gauld at Nov 30, 2009 at 5:38 pm
    "spir" <denis.spir at free.fr> wrote
    x = 1

    def f():
    n = 1
    def g0(a):
    print (x + n + a)
    return g0


    I'm surprised the snippet below works as expected (py 2.6) without any
    trick:
    I'm not sure how else it could work.
    x is a global name so the function must reference it.
    n is a local name so it musdt evaluate it (it will disappear otherwise)
    a is a parameter delivered at run time.
    This means a (real) closure is built for g0, or what?
    Thought I would need instead to use the old trick of pseudo
    default-parameters:

    def f():
    n = 1
    def g0(a, n=n, x=x):
    print (x + n + a)
    return g0
    I did wonder if you would need n=n but I didn't think you would need x=x.

    Its an interesting example and I confess I don't fully understand how
    Python's
    naming/reference rules are working here.
    to let the inner func g0 "remember" outer values.
    Why is this idiom used, then? Has something changed, or do I miss
    a relevant point?
    I thought you might need to do it if n had been a parameter of f()... but
    having tried it no, it works as above.

    I look forward to the explanation.

    Alan G.
  • Dave Angel at Dec 1, 2009 at 12:50 am

    Alan Gauld wrote:
    <div class="moz-text-flowed" style="font-family: -moz-fixed">
    "spir" <denis.spir at free.fr> wrote
    <snip>
    I did wonder if you would need n=n but I didn't think you would need x=x.

    Its an interesting example and I confess I don't fully understand how
    Python's
    naming/reference rules are working here.
    to let the inner func g0 "remember" outer values.
    Why is this idiom used, then? Has something changed, or do I miss
    a relevant point?
    I thought you might need to do it if n had been a parameter of f()... but
    having tried it no, it works as above.

    I look forward to the explanation.

    Alan G.
    Maybe a more complex example might show the various linkages.

    glob = 42

    def outer(parm1):
    free = 12
    free3 = 19
    def inner(parm2, parm3=free3):
    print "global", glob, ", free vars", parm1, free, free3, ",
    locals", parm2, parm3
    free = 49
    free3 = 48
    return inner

    newfunc = outer(10)
    newfunc(45)


    produces output:
    global 42 , free vars 10 49 48 , locals 45 19

    So when the inner() function is actually called, glob is just a global.
    parm1, fre, and free3 hold the values they ended up with when outer()
    returned, and local parm2 is passed by top-level code, while local parm3
    gets its default value assigned when "def inner(...) was executed.

    Notice that the free variables free, free3, and parm1 are referring to
    the function's ending state, not to the state when the function was
    defined. This has an impact when you've got inner being defined in a
    loop. And this example could be made more complex if outer() is a
    generator, in which case it may not have actually ended when inner gets
    called.

    HTH
    DaveA
  • Kent Johnson at Dec 1, 2009 at 3:00 am

    On Mon, Nov 30, 2009 at 7:50 PM, Dave Angel wrote:

    And this
    example could be made more complex if outer() is a generator, in which case
    it may not have actually ended when inner gets called.
    Indeed.

    In [8]: def gen():
    ...: for i in range(5):
    ...: def inner():
    ...: print i
    ...: yield inner

    In [9]: g = gen()

    In [10]: outer = g.next()

    In [11]: outer()


    In [12]: g.next()

    In [13]: outer()
    1

    Kent
  • Spir at Dec 1, 2009 at 7:42 am

    Dave Angel <davea at ieee.org> dixit:

    Maybe a more complex example might show the various linkages.

    glob = 42

    def outer(parm1):
    free = 12
    free3 = 19
    def inner(parm2, parm3=free3):
    print "global", glob, ", free vars", parm1, free, free3, ",
    locals", parm2, parm3
    free = 49
    free3 = 48
    return inner

    newfunc = outer(10)
    newfunc(45)


    produces output:
    global 42 , free vars 10 49 48 , locals 45 19

    So when the inner() function is actually called, glob is just a global.
    parm1, fre, and free3 hold the values they ended up with when outer()
    returned, and local parm2 is passed by top-level code, while local parm3
    gets its default value assigned when "def inner(...) was executed.

    Notice that the free variables free, free3, and parm1 are referring to
    the function's ending state, not to the state when the function was
    defined. This has an impact when you've got inner being defined in a
    loop. And this example could be made more complex if outer() is a
    generator, in which case it may not have actually ended when inner gets
    called.

    HTH
    DaveA
    Great example, thank you.

    By the way, do you know the idiom:

    def makeInc(start):
    def inc():
    inc.n += 1
    print inc.n
    inc.n = start
    # 'start' may change now
    # ...
    return inc

    inc= makeInc(start=3)
    inc()

    I find it much nicer than a pseudo default value, for it explicitely shows that 'n' is, conceptually speaking, an attribute of the func (read: a closure upvalue). Let's take advantage of the fact python funcs are real objects!

    Denis
    ________________________________

    la vita e estrany

    http://spir.wikidot.com/
  • Hugo Arts at Dec 1, 2009 at 8:36 am

    On Tue, Dec 1, 2009 at 8:42 AM, spir wrote:
    Great example, thank you.

    By the way, do you know the idiom:

    def makeInc(start):
    ? def inc():
    ? ? ?inc.n += 1
    ? ? ?print inc.n
    ? inc.n = start
    ? # 'start' may change now
    ? # ...
    ? return inc

    inc= makeInc(start=3)
    inc()

    I find it much nicer than a pseudo default value, for it explicitely shows that 'n' is, conceptually speaking, an attribute of the func (read: a closure upvalue). Let's take advantage of the fact python funcs are real objects!
    Well, if you need an attribute maintained between calls like that I
    think a generator is much nicer to write:

    def inc(start):
    while True:
    yield start
    start += 1
    i = inc(3)
    i.next()
    3
    i.next()
    4

    There might be a use-case where function attributes fit better, can't
    think of one right now though.

    Hugo
  • Spir at Dec 1, 2009 at 8:28 am
    May I suggest that upvalues are analog to parameters passed by name? (which is indeed not Python's paradigm)

    Denis
    ________________________________

    la vita e estrany

    http://spir.wikidot.com/
  • Eike Welk at Jan 27, 2010 at 1:04 pm
    Hello Denis!
    On Monday November 30 2009 11:24:45 spir wrote:
    which seems to indicate python really embeds "symbolic references" (*) to
    outer *variables*, when creating a closure for g0. Not "pointer
    references" (**), otherwise the replacement of x would not be seen by the
    closure --like in the case of default-parameter. Actually, I find this
    _Bad_. Obviously, the func's behaviour and result depend on arbitrary
    external values (referentially opaque). What do you think?

    Denis

    (*) by name, indirect access, second table lookup
    (**) by address, direct access, no second lookup
    I just skimmed over introductions to Oz and Ocaml, and both programming
    languages seem to behave the way that you prefer. The closure consists of
    pointers to the objects and can't be modified. (Both languages however have
    special syntax for global variables that can be modified.)

    So, Python's behavior is just a design decision; and more people dislike it,
    like you.

    Learning exotic programming languages seems to widen my horizon.


    Eike.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouptutor @
categoriespython
postedNov 30, '09 at 10:24a
activeJan 27, '10 at 1:04p
posts18
users8
websitepython.org

People

Translate

site design / logo © 2022 Grokbase