Earlier quoted context omitted.
Well, those things are called "default argument values ", not "default argument expressions ", you know. It's a value that's reused for every function call, not an expression that re-evaluated at every function call.
Sure there is a reason it is that way. But it doesn't mean it's not incredibly unintuitive and error-prone.
Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
11–20 of 101 posts
Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#12TL;DR is that multiplying a list performs a shallow copy of the list's elements. Great article. The only thing it's missing is a snippet that shows what you should do instead: [[] for _ in range(N)] Since generator-like expressions evaluates the inner expression (i.e. the []) on each iteration, the elements of the outer list are guaranteed to be unique references.
This is pretty subtle. I wish more languages explicitly distinguished references and values like C++ and Rust.
If you had done [1] * 4 you'd ended up with a list of four references to the same object (1) as well. It's just that 1 isn't mutable, so nobody cares.
Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#13I like the deep dive into the source, but big picture, I think if you understand the general order of evaluation which applies to most languages, this isn't all that unintuitive. The arguments going into the outer [ ] get evaluated before the outer [ ], meaning the inner [] gets evaluated first. Once that happens the inner list is created, and there's no code left that even visually could create more inner lists. The…
They are part of function definition, they aren't in the function body. You should expect them to be "executed" when the function is defined.
Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#14I like the deep dive into the source, but big picture, I think if you understand the general order of evaluation which applies to most languages, this isn't all that unintuitive. The arguments going into the outer [ ] get evaluated before the outer [ ], meaning the inner [] gets evaluated first. Once that happens the inner list is created, and there's no code left that even visually could create more inner lists. The…
In more sane languages, the expression [] means give me an empty list value, nothing more and nothing less. So then whatever the syntax for [[]]*4 is in such sane languages, it gives you four empty list values. If you want something more imperative, you can get it, but it most cases, you don't want or even need such things.
Python makes no distinction, at a level able to be visualized by the user, between immutable and mutable data. There are a lot of bizarre situations in Python that will bite you like this. And in most cases, it's strange that such behaviors were chosen as the deault.
Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#15I like the deep dive into the source, but big picture, I think if you understand the general order of evaluation which applies to most languages, this isn't all that unintuitive. The arguments going into the outer [ ] get evaluated before the outer [ ], meaning the inner [] gets evaluated first. Once that happens the inner list is created, and there's no code left that even visually could create more inner lists. The…
I've never understood peoples confusion about function defaults. They are part of function definition, they aren't in the function body. You should expect them to be "executed" when the function is defined.
Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#16Using * on a sequence wouldn't get past code review with me, it's an obscure feature with surprising side-effects and it doesn't explain your thinking. Only context I've ever found it useful in, is if you're unit testing something that accepts a string of max length M, then bad_str = "A" * (M + 1) Is quite nice and also, easy to interpret. But if you're calling multiply on a list[list[Any]] lol nah mate, what the hel…
On a mutable sequence. Or anything mutable. [] being a sequence is not the issue here.
If you work with immutable stuff, * is really not an issue.
Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#17I like the deep dive into the source, but big picture, I think if you understand the general order of evaluation which applies to most languages, this isn't all that unintuitive. The arguments going into the outer [ ] get evaluated before the outer [ ], meaning the inner [] gets evaluated first. Once that happens the inner list is created, and there's no code left that even visually could create more inner lists. The…
You can whitewash this away by discussing order of evaluation, but it still is a great example of how Python continually mixes up concepts of expressions and statements and immutable and mutable data. In real life, if I ask someone to go get me four empty buckets, I expect them to come back with four distinct empty buckets. The intuitive interpretation of [[]]*4 is asking just that. Instead Python creates a bucket an…
Actually, if you really want to have re-evaluting (sub)expressions, then Python does give you an option: comprehensions.
Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#18I think anybody that ever did LeetCode/HackerRank in Python had that bug at least once when making an empty 2-D DP table or a return matrix. My bigger question is how does a professor of machine learning does not know about one of the oldest Python footguns to exist: https://stackoverflow.com/questions/12791501/why-does-this-c... Very nice article otherwise, I never actually bothered looking into why it behaved this…
Because there's a lot of them. And as soon as you touch a sane language that fits your intuitive model of how things should work, going back to Python means that you'll start doing intuitive things that Python will happily and silently transform into unintuitive operations.
Re: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#19TL;DR is that multiplying a list performs a shallow copy of the list's elements. Great article. The only thing it's missing is a snippet that shows what you should do instead: [[] for _ in range(N)] Since generator-like expressions evaluates the inner expression (i.e. the []) on each iteration, the elements of the outer list are guaranteed to be unique references.
#good
a = [True] * 8 # bool is primitive
b = [[True] * 8 for i in range(8)] # range creates new lists each time
#bad
a = [{}] * 8 # dict is not a primitive
b = [[True] * 8] * 8 # list is not a primitiveRe: Why Do Python Lists Multiply Oddly? Exploring the CPython Source Code
#20TL;DR is that multiplying a list performs a shallow copy of the list's elements. Great article. The only thing it's missing is a snippet that shows what you should do instead: [[] for _ in range(N)] Since generator-like expressions evaluates the inner expression (i.e. the []) on each iteration, the elements of the outer list are guaranteed to be unique references.
This is pretty subtle. I wish more languages explicitly distinguished references and values like C++ and Rust.