5 ms·
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" b
by mattclarkdotnet 7mo 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.
- Izkata 7mo agoExecution 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 7mo agoLet's not get started on the cached shared object refs for small integers....
- zahlman 7mo agoWhat 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 7mo ago> 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.
- fwip 7mo agoCould you expand on this? For example, this works just fine: def silly_append(item, orig=[]): return orig + [item] Edit: Oh, I think you probably mean in cases where you're mutating the input list.
- zahlman 7mo agoI assume you refer to the `is None` idiom. That happens often enough, but I count it as exactly one use case, and I think it's usually poorly considered anyway. Again, you probably don't actually want the default value to be an empty list, because it doesn't make a lot of sense to mutate something that the caller isn't actually required to provide (unless the caller never provides it and you're just abusing the default-argument behaviour for some kind of cache). Using, for example, `()` as a default argument, and cleaning up your logic to not do those mutations, is commonly simpler and more expressive. A lot of the community has the idea that a tuple should represent heterogeneous fixed-length data and a list should be homogeneous; but I consider (im)mutability to be a much more interesting property of types.
- germandiago 7mo agoif v is not None as opposed to if not v is one of those use cases if you store 0 or False or an empty list, etc.
- mattclarkdotnet 7mo agoIt'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.
- boxed 7mo agoYou 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 7mo agoyou 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 7mo agoWhat 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 7mo agoI 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.
- zahlman 7mo agoWe 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 7mo agoIt'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.
- zahlman 7mo agoYou don't need to use `None`. If you indeed aren't planning to mutate the argument, then use something immutable that provides the necessary interface. Typically, this will be `()`, and then your logic doesn't require the special case. I genuinely don't understand, after 20+ years of this, why everyone else has decided that the `None` check should be idiomatic. It's just, ugh. I'm pretty sure I've even seen people do this where a string is expected and `''` is right there staring at them as the obvious option.
- zeratax 7mo agothere 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
- exyi 7mo agoIf you change this you break a common optimization: https://github.com/python/cpython/blob/3.14/Lib/json/encoder.py#L267 https://github.com/python/cpython/blob/3.14/Lib/json/encoder... Default value is evaluated once, and accessing parameter is much cheaper than global