4

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.

Community
  • 1
  • 1
Sticky
  • 3,671
  • 5
  • 34
  • 58
  • Possible duplicate of [How do I pass a variable by reference?](http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference) – Ken Y-N Mar 22 '17 at 03:19
  • "I thought since None is a NoneType that it would be passed in by reference" - could you explain why you thought that? What was the link in your mind between "None is a NoneType" and "therefore, it will be passed by reference"? Nothing in Python is passed by reference. – user2357112 Mar 22 '17 at 03:31
  • Oh my understanding before this was that objects (typically mutable) are pass by reference in Python. I thought since None is a NoneType *object*, it must be pass by reference. My logic has failed me :p – Sticky Mar 22 '17 at 03:37

2 Answers2

2

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.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • My mind is blown right now. Thank you!! (also, confirmed that your "in contrast" snippet works) – Sticky Mar 22 '17 at 03:40
0

The problem is that function calls pass values, not references. Specifically, the reference passed to the function remains the same until it is rebound (it's value set) at which point a copy is created local to the function. Any line of the form other = ... will cause this to happen. If you want this behavior, you need to use a mutable object, such as a list. For example:

class MyClass:
    def __init__(self):
        self.value = [None]

    def assign(self, object):
        object[0] = OtherClass()

example = MyClass()
example.assign(example.value)

Hope this helps!

DW42
  • 101
  • 7
  • Oh I wasn't sure if NoneType object is mutable or not. I guess not in this case. I also wanted to reword my question to ask that but it felt like dumb question to ask. – Sticky Mar 22 '17 at 03:22
  • 2
    @Sticky: It doesn't matter if `None` is mutable or not. Assigning to an unqualified name replaces it; it doesn't matter what the old value was. – ShadowRanger Mar 22 '17 at 03:26
  • Sometimes a quick google search can be the best coding advice. – DW42 Mar 22 '17 at 03:36