Remix.run Logo
mattclarkdotnet 9 hours ago

Oh, and while we're at it, fix the "empty array is instantiated at parse time so all your functions with a default empty array argument share the same object" bullshit.

zahlman 7 hours ago | parent | next [-]

We don't call them "arrays".

It has nothing to do with whether the list is empty. It has nothing to do with lists at all. It's the behaviour of default arguments.

It happens at the time that the function object is created, which is during runtime.

You only notice because lists are mutable. You should already prefer not to mutate parameters, and it especially doesn't make sense to mutate a parameter that has a default value because the point of mutating parameters is that the change can be seen by the caller, but a caller that uses a default value can't see the default value.

The behaviour can be used intentionally. (I would argue that it's overused intentionally; people use it to "bind" loop variables to lambdas when they should be using `functools.partial`.)

If you're getting got by this, you're fundamentally expecting Python to work in a way that Pythonistas consider not to make sense.

Revisional_Sin 5 hours ago | parent [-]

It's best practice to avoid mutable defaults even if you're not planning to mutate the argument.

It's just slightly annoying having to work around this by defaulting to None.

exyi an hour ago | parent | prev | next [-]

If you change this you break a common optimization:

https://github.com/python/cpython/blob/3.14/Lib/json/encoder...

Default value is evaluated once, and accessing parameter is much cheaper than global

Izkata 9 hours ago | parent | prev | next [-]

Execution time, not parse time. It's a side effect of function declarations being statements that are executed, not the list/dict itself. It would happen with any object.

mattclarkdotnet 9 hours ago | parent | next [-]

It's still ridiculous. A hypothetical Python4 would treat function declarations as declarations not executable statements, with no impact on real world code except to remove all the boilerplate checks.

zahlman 7 hours ago | parent | next [-]

There is no such thing as a "function declaration" in Python. The keyword is "def", which is the first three letters of the word "define" (and not a prefix of "declare"), for a reason.

The entire point of it being an executable statement is to let you change things on the fly. This is key to how the REPL works. If I have `def foo(): ...` twice, the second one overwrites the first. There's no need to do any checks ahead of time, and it works the same way in the REPL as in a source file, without any special logic, for the exact same reason that `foo = 1` works when done twice. It's actually very elegant.

People who don't like these decisions have plenty of other options for languages they can use. Only Python is Python. Python should not become not-Python in order to satisfy people who don't like Python and don't understand what Python is trying to be.

1718627440 3 hours ago | parent | prev | next [-]

You are describing a completely different language, that differs in very major ways from Python. You can of course create that, but please don't call it Python 4 !

boxed 7 hours ago | parent | prev [-]

You think so but then you write a function with a default argument pointing to some variable that is a list and now suddenly the semantics of that are... what?

codesnik 7 hours ago | parent [-]

you could just treat argument initialization as an executable expression which is called every time you call a function. If you have a=[], then it's a new [] every time. If a=MYLIST then it's a reference to the same MYLIST. Simple. And most sane languages do it this way, I really don't know why python has (and maintain) this quirk.

1718627440 3 hours ago | parent [-]

What are the semantics of the following:

    b = ComplexObject (...)
    # do things with b

    def foo (self, arg=b):
        # use b

    return foo
Should it create a copy of b every time the function is invoked? If you want that right now, you can just call b.copy (), when you always create that copy, then you can not implement the current choice.

Should the semantic of this be any different? :

    def foo (self, arg=ComplexObject (...)):
Now imagine a:

    ComplexObject = list
codesnik 2 hours ago | parent [-]

I wonder, why that kind of ambiguity or complexity even comes to your mind at all. Just because python is weird?

def foo(self, arg=expression):

could, and should work as if it was written like this (pseudocode)

def foo(self, arg?): if is_not_given(arg): arg=expression

if "expression" is a literal or a constructor, it'd be called right there and produce new object, if "expression" is a reference to an object in outer scope, it'd be still the same object.

it's a simple code transformation, very, very predictable behavior, and most languages with closures and default values for arguments do it this way. Except python.

1718627440 2 hours ago | parent [-]

What you want is for an assignment in a function definition to be a lambda.

  def foo (self, arg=lambda : expression):
Assignment of unevaluated expressions is not a thing yet in Python and would be really surprising. If you really want that, that is what you get with a lambda.

> most languages with closures and default values for arguments do it this way.

Do these also evaluate function definitions at runtime?

codesnik 7 minutes ago | parent [-]

yes they do. check ruby for example.

mattclarkdotnet 9 hours ago | parent | prev [-]

Let's not get started on the cached shared object refs for small integers....

zahlman 7 hours ago | parent [-]

What realistic use case do you have for caring about whether two integers of the same value are distinct objects? Modern versions of Python warn about doing unpredicatble things with `is` exactly because you are not supposed to do those things. Valid use cases for `is` at all are rare.

thaumasiotes 5 hours ago | parent [-]

> Valid use cases for `is` at all are rare.

There might not be that many of them, depending on how you count, but they're not rare in the slightest. For example, you have to use `is` in the common case where you want the default value of a function argument to be an empty list.

zeratax 4 hours ago | parent | prev [-]

there is PEP 671 for that, which introduces extra syntax for the behavior you want. people rely on the current behavior so you can't really change it