Live data from Hacker News

Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

codeconfessions.substack.com

91–100 of 101 posts

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#91

Earlier quoted context omitted.

And it's literally impossible for [[]]*4 to be a shorthand for a list comprehension, because nothing in Python can be a shorthand for a list comprehension. Instead, it is but a method call.

I mean, there is no reason Python couldn't special case something that looked like an operator call into shorthand for a comprehension instead of shorthand for a method call, other than the fact that it would make the parser more complex and make it harder to understand the language. It would be bad for this case, though. As confusing as the particular case of: [[]] * 4 might be to people who haven't learned how it w…

Well, Python is not Raku, so while it could be possible to make "[expr1, expr2, ..., exprN] * exprM" to mean "[*(expr1), *(expr2), ..., *(exprN) for _ in range(exprM)]", it would never happen.

Besides, that still doesn't help with the case of

    x = function_that_returns_a_list()
    x = x * 4
where there is no literal list expression.

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#92
post #85

Earlier quoted context omitted.

> these people need to roll up their sleeves and just learn the damn language You really have no choice but to do that. But the critique here is that some languages make this hard. And some languages, like Python, appear deceptively simple and consistent, when they are anything but. And as I pointed out, these decisions were not really required or designed to solve certain problems. They just kind of came about in Py…

> What problems is it solving that require the confusion that it generates? I think I answered that already. It keeps the language spec consistent and simpler. Imagine the complexity you have to add to the language spec to say that when we write [] we deal with the reference to this list except in the multiplication syntax a = [[]] * 5 where the inner [] is not a reference to the list but the list value! Such special…

I'm asking what makes introducing the multiplication syntax worth it in the first place.

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#93
post #84

Earlier quoted context omitted.

The language spec isn't wrong, you just don't like it.

The language spec isn't right you just like it. Sure, there are a lot of subjective aesthetics that go into the spec, but in this case, there are objective reasons for not liking this. It's a well-known footgun that causes bugs. And it's almost never what you want, so you end up doing something like this: def f(xs = None): # Are these two lines actually faster than the # interpreter creating defaults at call time? if…

>

   # Are these two lines actually faster than the
   # interpreter creating defaults at call time?
You're proposing that the interpreter add a check for every default parameter in every function signature; that it should optionally fire off arbitrary code for each and every one. And when you consider that high-performance Python involves writing C extensions, your proposal would be to move that check out of the compiled code and into the slow interpreted space is, yes, a major performance hit.

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#94
post #93

Earlier quoted context omitted.

The language spec isn't right you just like it. Sure, there are a lot of subjective aesthetics that go into the spec, but in this case, there are objective reasons for not liking this. It's a well-known footgun that causes bugs. And it's almost never what you want, so you end up doing something like this: def f(xs = None): # Are these two lines actually faster than the # interpreter creating defaults at call time? if…

> # Are these two lines actually faster than the # interpreter creating defaults at call time? You're proposing that the interpreter add a check for every default parameter in every function signature; that it should optionally fire off arbitrary code for each and every one. And when you consider that high-performance Python involves writing C extensions, your proposal would be to move that check out of the compiled…

> You're proposing that the interpreter add a check for every default parameter in every function signature

No, that's not what I'm proposing. Why would it check anything? Just evaluate the given default expression at call time. If you don't want the overhead of an expression, don't put a default.

You can also do defaults like:

    LIST_OF_X = []

    def foo(xs = LIST_OF_X):
        ...
...if you want the other behavior. This does add a variable lookup (oh no!).

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#95
post #93

Earlier quoted context omitted.

> # Are these two lines actually faster than the # interpreter creating defaults at call time? You're proposing that the interpreter add a check for every default parameter in every function signature; that it should optionally fire off arbitrary code for each and every one. And when you consider that high-performance Python involves writing C extensions, your proposal would be to move that check out of the compiled…

> You're proposing that the interpreter add a check for every default parameter in every function signature No, that's not what I'm proposing. Why would it check anything? Just evaluate the given default expression at call time. If you don't want the overhead of an expression, don't put a default. You can also do defaults like: LIST_OF_X = [] def foo(xs = LIST_OF_X): ... ...if you want the other behavior. This does a…

Your list there is mutable. Isn't that what you meant to solve with this?

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#96

Earlier quoted context omitted.

In that case what would you expect the following to return? a = [] b = [a]*4 a.append(1) print(b) I mean it is possible to change Python and make [...]*n a shorthand for "repeat this statement n times" but that would be odd on its own. Lists pass by reference, tuples pass by value, if you want to understand Python you're just going to have to get used to that distinction. There are other options, but most are worse.

> Lists pass by reference, tuples pass by value, if you want to understand Python you're just going to have to get used to that distinction. Is that true? I haven't looked at the implementation so I can't say it's impossible, but it would be extremely surprising to me if tuples passed by value, because tuples longer than the size operated upon my individual processor instructions would become expensive to pass around…

I can't tell what the underlying implementation is, but either way you can't modify them without creating a new tuple so they behave as if they use value-semantics not reference-semantics. (this is what makes tuples safe as defaults in a method, and lists not)

If you compare their ids it does look like python makes multiple instances and passes them around by reference, but without a way to modify them in place it's hard to tell the difference, also Python tries to avoid making unnecessary copies of the same thing if possible.

You can try to test it a bit with something like the following:

    t = (1,2)
    def f(x):
        return x is t

   f(t), f((1,2)), (1,2) is (1,2) # -> True, False, True
but the results might be implementation dependent.

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#97

Earlier quoted context omitted.

> Lists pass by reference, tuples pass by value, if you want to understand Python you're just going to have to get used to that distinction. Is that true? I haven't looked at the implementation so I can't say it's impossible, but it would be extremely surprising to me if tuples passed by value, because tuples longer than the size operated upon my individual processor instructions would become expensive to pass around…

I can't tell what the underlying implementation is, but either way you can't modify them without creating a new tuple so they behave as if they use value-semantics not reference-semantics. (this is what makes tuples safe as defaults in a method, and lists not) If you compare their ids it does look like python makes multiple instances and passes them around by reference, but without a way to modify them in place it's…

> I can't tell what the underlying implementation is, but either way you can't modify them without creating a new tuple so they behave as if they use value-semantics not reference-semantics. (this is what makes tuples safe as defaults in a method, and lists not)

Ahh, you were talking about the semantics, not the implementation. Understood.

Another way to think of this might be in terms of mutability, i.e. tuples are immutable. Pass-by-value versus pass-by-reference for immutable objects doesn't make much difference in the behavior of objects in a language like Python which doesn't have explicit pointers, but it's a big difference for performance.

> If you compare their ids it does look like python makes multiple instances and passes them around by reference

I didn't think of answering my own question this way. Nice!

> but the results might be implementation dependent.

It's possible since Python isn't standardized, but I strongly doubt you'll find a mature implementation that passes potentially-large, variable-sized objects like tuples around by value, for two reasons:

1. Once you get above the size operated on by processor instructions (64 bits on most modern desktop/laptop processors) pass by value requires a memcopy, which is orders of magnitude slower. Tuples can grow past the L1 or L2 cache size, which corresponds to another two orders of magnitude in performnance loss. Comparing this to passing around a 64-bit pointer which is about as fast as passing around any other value.

2. Virtual machines usually implement their own stack. This is easiest to do as an array of same-sized values. Implementing a performant stack with variable-sized elements is a nightmare I wouldn't wish on my worst enemy.

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#98
post #95

Earlier quoted context omitted.

> You're proposing that the interpreter add a check for every default parameter in every function signature No, that's not what I'm proposing. Why would it check anything? Just evaluate the given default expression at call time. If you don't want the overhead of an expression, don't put a default. You can also do defaults like: LIST_OF_X = [] def foo(xs = LIST_OF_X): ... ...if you want the other behavior. This does a…

Your list there is mutable. Isn't that what you meant to solve with this?

Ugh. You're just ignoring my entire post except the one part where you (wrongly) think you can correct me?

The code I posted is showing how you can explicitly get the mutable behavior if you want it when the default expressions are evaluated at call time.

That is to say, if you evaluate default expressions at call time, you can get either behavior by being explicit with minimal loss in performance:

    def foo(xs = []):
        xs.append(1)
        print(xs) # always prints [1]

    DEFAULT = []
    def bar(xs = DEFAULT):
        xs.append(1)
        print(xs) # prints [1], [1,1], [1,1,1], etc.
To reiterate, the above code is what would happen if default expressions were evaluated at function call time.

You're clearly not as knowledgeable as you think you are on this and you're just cherry picking things you don't understand to feel smart.

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#99
post #92

Earlier quoted context omitted.

> What problems is it solving that require the confusion that it generates? I think I answered that already. It keeps the language spec consistent and simpler. Imagine the complexity you have to add to the language spec to say that when we write [] we deal with the reference to this list except in the multiplication syntax a = [[]] * 5 where the inner [] is not a reference to the list but the list value! Such special…

I'm asking what makes introducing the multiplication syntax worth it in the first place.

Ah! Misunderstood your original comment. My apologies! Yes, I am with you when you question the usefulness of the multiplication syntax.

I prefer simplicity and consistency in a programming language grammar, syntax and semantics as I advocated above. So yes I'd be happy to lose that multiplication syntax. It is not worth it.

Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code

#100
post #72

Earlier quoted context omitted.

I wouldn't recommend just throwing the idea away, it just needs to be approached with care. You can do it in a type safe way. Research Rank Polymorphism and Array Languages: https://en.m.wikipedia.org/wiki/Array_programming https://arxiv.org/abs/1907.00509

I mean, is there anything there that couldn't just be a library function? Not everything has to be an operator.

Philosophically, I don't know what "must" or "has" to be an operator.

I certainly don't want to tell you how to design your language.

I'm just trying to say, don't rule the pattern out because it's perceived as a type error; you may have other reasons that are good, but my citations provide a basis of how to make the typing work (if you were inclined).

If you have other reasons, even if it's just aesthetic, that's fine.

Post reply on HN