A lot of comments here are talking about how it's "really" just call-by-value. I want to explain why this is unproductive.
First, let's acknowledge that the context we're discussing is explaining to someone new to Python how Python works with function arguments.
Now, if someone asks if lead-acid batteries work using chemistry, and you say "they use physics", then they will probably end up with a fairly inaccurate picture of how lead-acid batteries work. What you've told them is technically correct, but it isn't practically useful.
It is the same if someone asks how Python passes arguments to functions and you say "it's pass-by-value". You've told them something technically correct, but they're going to think inaccurate things like that passing an object to a function copies all of the object's properties like a struct in C.
Even if you say "it's pass-by-value but the values are not what you think", you haven't actually added to their understanding by that phrase. You're better off explaining conceptually how it works, describing it as "call-by-sharing", and then explaining what that means. It will signal their brain to allocate space for a new concept, and not to drag in erroneous ideas from existing concepts. After they understand how it works, you can explain that call-by-sharing is actually just a spoonful of sugar to help the call-by-value go down.
Here's the thing: words are there to help us communicate. If you use a word in a way that is technically correct but conveys the wrong idea to your listener, you're doing it wrong.
Here's the other thing: this is computer science. In computer science, as in all math, you often encounter a concept that is "really just" some other concept. When you come to that realization about a concept that you understand superficially, it can be transformative, taking you to a deeper level of comprehension. When someone asks you about the concept, you'll want to share that deep comprehension with them, so it's natural to want to jump right into it and explain it by connecting it to the general case. But they will not understand if you do that.
You can't teach someone addition by starting with ring theory, after all.
On the contrary, I think inventing a new class of of "call-by-X" function arguments makes it harder to explain Python to newcomers. I would clarify that I think I'm talking about seasoned programmers, and you probably mean somebody who's a fairly new to programming in general. It's a tough balance to strike, but I still think common semantics are best.
The concepts of call-by-value vs call-by-reference are simple and easy to distinguish once somebody has understood this basic concept in languages. I don't mean to disregard newcomers, though. And in-fact I think that it is doing a disservice to them to make python sound like it is radically different and more complicated than other languages. Instead I think a newcomer is best served by helping them grasp basic, common language concepts as they apply to python as well as other languages.
I really also need call out that there seem to be a lot of inaccuracies with regards to other languages than python on this thread and even some confusing terminology and analogies by the article's author. Specifically there seem to be a lot of misunderstandings about C (and even C++) flying around and being perpetuated here.
For example, you compared passing a python object to passing a struct in C. Passing a C struct to a function does _not_ copy all the fields of the struct. It passes a reference (pointer) to the function, which the function then uses to access the fields (or properties if you will) in the struct directly at their original location in memory.
You do get automatic copies of primitive numeric types when you pass them to functions in C. The reasoning and distinction is pretty simple: they fit in hardware registers.
But, a struct, an array, or a string (or character array) is always going to be passed as a pointer. When you pass it to a function, the only way you get a copy of it is if you explicitly copy the value to a new location in memory and point to it. You can of-course also pass a pointer to a numeric value to a function as well (as in func(int *var)) instead of the actual value (as in func(int var)), in which case the same semantics apply.
but it's not a question of opinion. it's a question of experience. the call-by-sharing approach comes, afaict, from comp.lang.python where they spend most of their time explaining things to newbies. and this is apparently what works best for them.
personally, i find it confusing and near useless. but then i don't need to read some blog entry to understand python's semantics. so this really isn't about me.
again: this is a cultural thing, from c.l.p. - it apparently works well for them.
>On the contrary, I think inventing a new class of of "call-by-X" function arguments makes it harder to explain Python to newcomers.
Then I think you may have forgotten what it's like to move to a call-by-sharing language for the first time.
>Passing a C struct to a function does _not_ copy all the fields of the struct. It passes a reference (pointer) to the function, which the function then uses to access the fields (or properties if you will) in the struct directly at their original location in memory.
Wow ok.. I'm going to try to be polite. I don't think _you_ tried it as you suggest I do:
first shot:
~ gcc -o foo -ggdb foo.c
foo.c: In function ‘main’:
foo.c:31: error: ‘flippedPoint’ undeclared (first use in this function)
foo.c:31: error: (Each undeclared identifier is reported only once
foo.c:31: error: for each function it appears in.)
Typo fixed: s/flippedPoint/secondPoint
what's the output?
"{1, 2}{-1, -2}"
EDIT: Yours is actually an interesting example, and you're right, i responded to quick from the typo fix and didn't actually grok it.
But, actually we're both wrong... and right? The real answer turns out to be a bit more complicated. In your example the copies are made (automatically) in the caller (main) which actually passes a pointer to flipPoint. Try disassembling your executable. You'll see what I mean.
[Double posting because I was out of my edit window by the time I saw your edit]
>But, actually we're both wrong... and right? The real answer turns out to be a bit more complicated. In your example the copies are made (automatically) in the caller (main) which actually passes a pointer to flipPoint. Try disassembling your executable. You'll see what I mean.
I'm really not sure that what the assembly code does is relevant. First off, once it's in assembly, the concepts of "caller" and "function" are gone completely. There are structures that correspond to what compilers typically emit when you define and call functions, but there is no "copy in the caller and pass a pointer to the function". There's just "copy this data on the stack, put the location into a register, and then jump to this location".
Furthermore, this is looking at implementation details, and what we're concerned here with are the abstract concepts of how these languages work. If you look at the assembly, you're seeing how your compiler decided to handle it. If you have optimization on, flipPoint is going to be inlined anyway. The spec doesn't really care how the compiler achieves the result as long as it does achieve the correct result. One compiler might have it copy before jumping and pass a pointer, but another compiler could just as easily pass a pointer before jumping and then have the copy made after the jump. You would not know the difference.
But the way C acts is as if the fields of the struct were being copied when they were passed to the function.
Yeah, I renamed something and then had to do an edit, but I guess you saw the original broken version. Sorry.
The output of {-1, -2} is because it returned the other point by value. The first point is unmodified, which is why it prints out {1, 2}. If the value was being passed as a reference, the output would be {-1, -2}{-1, -2}.
Edit: "If the value was being passed as a reference" is sort of a weird thing to say. What I meant, of course, was "if the struct was being passed as a reference".
I agree if you're starting with a blank slate. If someone asks "How does Python handle arguments", then you can just omit any mention of call-by-value until they understand the concept, and have total accuracy the whole time.
But if they come to you with "Is Python call-by-value or -reference?", then for the sake of getting them to break away from those contexts, I think your best bet is to say "neither", which is technically-but-not-practically slightly inaccurate.
You could try saying "It's technically call-by-value but you seriously need to forget everything about call-by-value in this context until I explain it to you," but depending on how their mind works, they may be looking for the connection to call-by-value the whole time you explain it (certainly that's what my mind would do if it were explained that way). And that's just a distraction.
So as long as you tell them at the end, "OK, so technically I lied earlier; this is call-by-value, but that's not how it's understood conceptually", I think the marginally inaccurate route is better.
Comments
A lot of comments here are talking about how it's "really" just call-by-value. I want to explain why this is unproductive.
First, let's acknowledge that the context we're discussing is explaining to someone new to Python how Python works with function arguments.
Now, if someone asks if lead-acid batteries work using chemistry, and you say "they use physics", then they will probably end up with a fairly inaccurate picture of how lead-acid batteries work. What you've told them is technically correct, but it isn't practically useful.
It is the same if someone asks how Python passes arguments to functions and you say "it's pass-by-value". You've told them something technically correct, but they're going to think inaccurate things like that passing an object to a function copies all of the object's properties like a struct in C.
Even if you say "it's pass-by-value but the values are not what you think", you haven't actually added to their understanding by that phrase. You're better off explaining conceptually how it works, describing it as "call-by-sharing", and then explaining what that means. It will signal their brain to allocate space for a new concept, and not to drag in erroneous ideas from existing concepts. After they understand how it works, you can explain that call-by-sharing is actually just a spoonful of sugar to help the call-by-value go down.
Here's the thing: words are there to help us communicate. If you use a word in a way that is technically correct but conveys the wrong idea to your listener, you're doing it wrong.
Here's the other thing: this is computer science. In computer science, as in all math, you often encounter a concept that is "really just" some other concept. When you come to that realization about a concept that you understand superficially, it can be transformative, taking you to a deeper level of comprehension. When someone asks you about the concept, you'll want to share that deep comprehension with them, so it's natural to want to jump right into it and explain it by connecting it to the general case. But they will not understand if you do that.
You can't teach someone addition by starting with ring theory, after all.
On the contrary, I think inventing a new class of of "call-by-X" function arguments makes it harder to explain Python to newcomers. I would clarify that I think I'm talking about seasoned programmers, and you probably mean somebody who's a fairly new to programming in general. It's a tough balance to strike, but I still think common semantics are best.
The concepts of call-by-value vs call-by-reference are simple and easy to distinguish once somebody has understood this basic concept in languages. I don't mean to disregard newcomers, though. And in-fact I think that it is doing a disservice to them to make python sound like it is radically different and more complicated than other languages. Instead I think a newcomer is best served by helping them grasp basic, common language concepts as they apply to python as well as other languages.
I really also need call out that there seem to be a lot of inaccuracies with regards to other languages than python on this thread and even some confusing terminology and analogies by the article's author. Specifically there seem to be a lot of misunderstandings about C (and even C++) flying around and being perpetuated here.
For example, you compared passing a python object to passing a struct in C. Passing a C struct to a function does _not_ copy all the fields of the struct. It passes a reference (pointer) to the function, which the function then uses to access the fields (or properties if you will) in the struct directly at their original location in memory.
You do get automatic copies of primitive numeric types when you pass them to functions in C. The reasoning and distinction is pretty simple: they fit in hardware registers.
But, a struct, an array, or a string (or character array) is always going to be passed as a pointer. When you pass it to a function, the only way you get a copy of it is if you explicitly copy the value to a new location in memory and point to it. You can of-course also pass a pointer to a numeric value to a function as well (as in func(int *var)) instead of the actual value (as in func(int var)), in which case the same semantics apply.
but it's not a question of opinion. it's a question of experience. the call-by-sharing approach comes, afaict, from comp.lang.python where they spend most of their time explaining things to newbies. and this is apparently what works best for them.
personally, i find it confusing and near useless. but then i don't need to read some blog entry to understand python's semantics. so this really isn't about me.
again: this is a cultural thing, from c.l.p. - it apparently works well for them.
>On the contrary, I think inventing a new class of of "call-by-X" function arguments makes it harder to explain Python to newcomers.
Then I think you may have forgotten what it's like to move to a call-by-sharing language for the first time.
>Passing a C struct to a function does _not_ copy all the fields of the struct. It passes a reference (pointer) to the function, which the function then uses to access the fields (or properties if you will) in the struct directly at their original location in memory.
I'm sorry, but everything you said there is wrong. Try it out for yourself: https://gist.github.com/4073329
Wow ok.. I'm going to try to be polite. I don't think _you_ tried it as you suggest I do:
first shot: ~ gcc -o foo -ggdb foo.c foo.c: In function ‘main’: foo.c:31: error: ‘flippedPoint’ undeclared (first use in this function) foo.c:31: error: (Each undeclared identifier is reported only once foo.c:31: error: for each function it appears in.)
Typo fixed: s/flippedPoint/secondPoint
what's the output?
EDIT: Yours is actually an interesting example, and you're right, i responded to quick from the typo fix and didn't actually grok it.But, actually we're both wrong... and right? The real answer turns out to be a bit more complicated. In your example the copies are made (automatically) in the caller (main) which actually passes a pointer to flipPoint. Try disassembling your executable. You'll see what I mean.
[Double posting because I was out of my edit window by the time I saw your edit]
>But, actually we're both wrong... and right? The real answer turns out to be a bit more complicated. In your example the copies are made (automatically) in the caller (main) which actually passes a pointer to flipPoint. Try disassembling your executable. You'll see what I mean.
I'm really not sure that what the assembly code does is relevant. First off, once it's in assembly, the concepts of "caller" and "function" are gone completely. There are structures that correspond to what compilers typically emit when you define and call functions, but there is no "copy in the caller and pass a pointer to the function". There's just "copy this data on the stack, put the location into a register, and then jump to this location".
Furthermore, this is looking at implementation details, and what we're concerned here with are the abstract concepts of how these languages work. If you look at the assembly, you're seeing how your compiler decided to handle it. If you have optimization on, flipPoint is going to be inlined anyway. The spec doesn't really care how the compiler achieves the result as long as it does achieve the correct result. One compiler might have it copy before jumping and pass a pointer, but another compiler could just as easily pass a pointer before jumping and then have the copy made after the jump. You would not know the difference.
But the way C acts is as if the fields of the struct were being copied when they were passed to the function.
Yeah, I renamed something and then had to do an edit, but I guess you saw the original broken version. Sorry.
The output of {-1, -2} is because it returned the other point by value. The first point is unmodified, which is why it prints out {1, 2}. If the value was being passed as a reference, the output would be {-1, -2}{-1, -2}.
Edit: "If the value was being passed as a reference" is sort of a weird thing to say. What I meant, of course, was "if the struct was being passed as a reference".
Sure, but I think it's still possible to explain something well without being inaccurate.
I agree if you're starting with a blank slate. If someone asks "How does Python handle arguments", then you can just omit any mention of call-by-value until they understand the concept, and have total accuracy the whole time.
But if they come to you with "Is Python call-by-value or -reference?", then for the sake of getting them to break away from those contexts, I think your best bet is to say "neither", which is technically-but-not-practically slightly inaccurate.
You could try saying "It's technically call-by-value but you seriously need to forget everything about call-by-value in this context until I explain it to you," but depending on how their mind works, they may be looking for the connection to call-by-value the whole time you explain it (certainly that's what my mind would do if it were explained that way). And that's just a distraction.
So as long as you tell them at the end, "OK, so technically I lied earlier; this is call-by-value, but that's not how it's understood conceptually", I think the marginally inaccurate route is better.