dannymi 6 days ago

There's always tension between language simplicity (and thus cognitive load of the programmers) and features. Compare Scheme with Common Lisp.

The idea in Python is:

1. Statements are executed line by line in order (statement by statement).

2. One of the statements is "def", which executes a definition.

3. Whatever arguments you have are strictly evaluated. For example f(g(h([]))), it evaluates [] (yielding a new empty list), then evaluates h([]) (always, no matter whether g uses it), then evaluates g(...), then evaluates f(...).

So if you have

def foo(x = []): ...

that immediately defines

foo = (lambda x = []: ...)

For that, it has to immediately evaluate [] (like it always does anywhere!). So how is this not exactly what it should do?

Some people complain about the following:

    class A:
        x = 3
        y = x + 2
That now, x is a class variable (NOT an instance variable). And so is y. And the latter's value is 5. It doesn't try to second-guess whether you maybe mean any later value of x. No. The value of y is 5.

For example:

    a = A()
    assert a.__class__.x == 3
    assert a.x == 3
    a.__class__.x = 10
    b = A()
    assert b.x == 10
succeeds.

But it just evaluates each line in the class definition statement by statement when defining the class. Simple!

Complicating the Python evaluation model (that's in effect what you are implying) is not worth doing. And in any case, changing the evaluation model of the world's most used programming language (and in production in all countries of the world) in 2025 or any later date is a no go right there.

If you want a complicated (more featureful) evaluation model, just use C++ or Ruby. Sometimes they are the right choice.

2
greiskul 6 days ago

> foo = (lambda x = []: ...)

> For that, it has to immediately evaluate [] (like it always does anywhere!). So how is this not exactly what it should do?

It has a lambda there. In many programming languages, and the way human beings read this, say that "when there is a lambda, whatever is inside is evaluated only when you call it". Python evaluating default arguments at definition time is a clear footgun that leads to many bugs.

Now, there is no way of fixing it now, without probably causing other bugs and years of backwards compatibility problems. But it is good that people are aware that it is an error in design, so new programming languages don't fall into the same error.

For an equivalent error that did get fixed, many Lisps used to have dynamic scoping for variables instead of lexical scoping. It was people critizing that decision that lead to pretty much all modern programming languages to use lexical scoping, including python.

shwouchk 6 days ago

dynamic variables (esp default) when you are collaborating with many people. when you you know the code well they are incredibly useful

dannymi 6 days ago

>It has a lambda there. In many programming languages, and the way human beings read this, say that "when there is a lambda, whatever is inside is evaluated only when you call it".

What is inside the lambda is to the right of the ":". That is indeed evaluated only when you call it.

>But it is good that people are aware that it is an error in design, so new programming languages don't fall into the same error.

Python didn't "fall" into that "error". That was a deliberate design decision and in my opinion it is correct. Scheme is the same way, too.

Note that you only have a "problem" if you mutate the list (instead of functional programming) which would be weird to do in 2025.

>For an equivalent error that did get fixed, many Lisps used to have dynamic scoping for variables instead of lexical scoping. It was people critizing that decision that lead to pretty much all modern programming languages to use lexical scoping, including python.

Both are pretty useful (and both are still there, especially in Python and Lisp!). I see what you mean, though: lexical scoping is a better default for local variables.

But having weird lazy-sometimes evaluation would NOT be a better default.

If you had it, when exactly would it force the lazy evaluation?

    def g():
        print('HA')
        return 7

    def f(x=lazy: [g()]):
        pass
^ Does that call g?

    def f(x=lazy: [g()]):
        print(x)
^ How about now?

    def f(x=lazy: [g()]):
        if False:
            print(x)
^ How about now?

    def f(x=lazy: [g()]):
        if random() > 42: # If random() returns a value from 0 to 1
            print(x)
^ How about now?

    def f(x=lazy: [g()]):
        if random() > 42:
            print(x)
        else:
            print(x)
            print(x)
^ How about now? And how often?

    def f(x=lazy: [g()]):
        x = 3
        if random() > 42:
            print(x)
^ How about now?

Think about the implications of what you are suggesting.

Thankfully, we do have "lazy" and it's called "lambda" and it does what you would expect:

If you absolutely need it (you don't :P) you can do it explicitly:

    def f(x=None, x_defaulter=lambda: []):
        x = x if x is not None else x_defaulter()
Or do it like a normal person:

    def f(x=None):
        x = x if x is not None else []
Explicit is better than implicit.

Guido van Rossum would (correctly) veto anything that hid control flow from the user like having a function call sometimes evaluate the defaulter and sometimes not.

9dev 6 days ago

That’s a very academic viewpoint. People initialize variables with defaults, and sometimes, that default needs to be an empty list. They are just holding it wrong, right?

owl57 6 days ago

Most people writing any language without a linter are holding it wrong.

When a linter warns me about such an expression, it usually means that even if it doesn't blow up, it increases the cognitive load for anyone reviewing or maintaining the code (including future me). And I'm not religious — if I can't easily rewrite the expression in an obviously safe way, I just concede that its safety is not 100% obvious and add a nolint comment with explanation.

9dev 6 days ago

My point was that no matter the conceptual purity or implementation elegance, if a language design decision leads to most people getting it wrong–then that's a bad decision.

owl57 6 days ago

But it's not about that. I don't like this decision either, but the other side of the trade-off is not just about some abstract concepts or implementation, it's about complexity of the model you need to keep in your head to know what will a piece of code do. And this has always been a priority for Python.