I'm trying to pass by object a None
value in hopes of reassigning it to an actual value. Can someone please explain why this isn't working? I thought since None
is a NoneType
that it would be passed in by reference and when I use the passed in variable, I would alter the original None
object. Is this incorrect? I have provided a snippet below to demonstrate my case:
class MyClass:
def __init__(self):
self.value = None
def assign(self, object):
object = OtherClass()
example = MyClass()
example.assign(example.value)
The result is that example.value
is still None
. Why is that? I have read a some SO posts but none of them are clear in explaining this.
EDIT: Not a duplicate because I wanted to know why it was behaving like pass by value when I thought it should be pass by reference. In the end, I have concluded that NoneType
objects are immutable and therefore always pass by value. I would need to use list
type object or something mutable.
EDIT again: My example code is just a general case. I was trying to implement a BST and my initial root node was None
and when I assigned the root node to be a Node
object, it would still be NoneType
, which caused me to be confused and was the cause of this question.
Last edit: answer below provides a very good tl;dr summary of pass by object. I was unable to understand just by searching/reading forums.
This is precisely the case where call by object (Python rules) differs from call by reference (C++ reference semantics).
The difference is that assigning to an unqualified name rebinds the name, it has no effect whatsoever on whatever that name might previously have been bound to (aside from possibly destroying it, if no other references remain). The name has no connection to any other names that might reference the same object, so those other names aren't changed.
So in this case, object
is initially set to point to the same object example.value
points to. But on the next line, you rebind object
(unqualified name assignment), and it points to a whole different object.
By contrast, if you had:
def assign_value(self, object):
object.value = OtherClass()
and called it with:
example.assign(example)
then example
and object
would refer to the same object, and your assignment to a qualified name would replace value
on that one object.
All that said, your use case here doesn't need any such changes. Calling example.assign(example.value)
doesn't make any sense, because self
is passed implicitly and would give you access to value
(qualified access no less) automatically. Seems like what you really wanted is just lazy initialization when requested with no arguments at all:
def assign(self):
self.value = OtherClass()
called as:
example.assign()
In response to your edits: Python documents this call behavior explicitly:
The actual parameters (arguments) to a function call are introduced in the local symbol table of the called function when it is called; thus, arguments are passed using call by value (where the value is always an object reference, not the value of the object).[1]
Where footnote 1 clarifies:
[1] Actually, call by object reference would be a better description, since if a mutable object is passed, the caller will see any changes the callee makes to it (items inserted into a list).
This is intended; C++ reference semantics are unworkable when you don't have other calling semantics available, because there is no obvious way to opt-out, meaning every variable along a huge call chain ends up referencing the same object, causing tons of action-at-a-distance.