2

I wonder if any python folks can fill me in on how/why the following happens:

# define a list and send it as an arg to another function
def foo():
    nums=[1,2,3]
    bar(nums)
    print(nums)

# Take the list as an arg and pop the last element off
def bar(numbrs):
    numbrs.pop()

# Call the first function
foo()

# The nums local variable has been modified
[1, 2]

As a rubyist I find it really strange that a locally defined variable (nums) in the foo function can be changed by an action performed in the bar function! Is this kind of entanglement normal? Is there a name for it?

The two functions don't even use the same name to refer to the list. It's very peculiar. I kind of like it though.

falsetru
  • 357,413
  • 63
  • 732
  • 636
stephenmurdoch
  • 34,024
  • 29
  • 114
  • 189

3 Answers3

1

The behavior is exactly same with Ruby:

def foo()
  nums = [1,2,3]
  bar(nums)
  p(nums)
end

def bar(numbers)
  numbers.pop
end

foo()
# prints [1, 2]

DEMO

You can not change the reference of the local variable to reference other variable. But you can call the method that change the state in-place. list.pop is one of such methods. (Same for Array#pop in Ruby)

falsetru
  • 357,413
  • 63
  • 732
  • 636
1

A list is an object, and calling a method on an object - such as pop() - affects its state.

To be more precise, let's walk through your code and see what's happening:

def foo():
    nums=[1,2,3]  # create a list and give it a name. nums contains a reference to the list
    bar(nums)     # call the method bar, passing it the reference to that list
    print(nums)   # print the list.

Okay, so that's pretty clear. What happens in bar is what's concerning to you:

# Take the list as an arg and pop the last element off
def bar(numbrs):  # method gets a value. In this case the value is a reference to a list
    numbrs.pop()  # call a method on that object

So what happens when you call numbrs.pop()? Presumably, in the definition of list, you'll find a method definition:

def pop(self):

which modifies the state of the self object by removing one of its members, and returns the removed member.

What is self in that case? It's a reference to a list. Specifically, it's the reference that was called numbrs when you did the pop() operation, which is the same reference that you stored as nums when you created it.

I hope this helps - it's a lot of indirection, but if you follow the reference around you'll see how it all goes.

Jon Kiparsky
  • 7,499
  • 2
  • 23
  • 38
  • Thanks a lot, I had no idea this happened in Ruby as well as in Python. Your explanation really helps me understand this. Cheers. – stephenmurdoch Oct 16 '14 at 16:20
  • 1
    Yes, this is pretty standard for object-oriented languages. There's really a triple-indirection going on here. The name "nums" points to a value on the stack, which points to an object on the heap. Where ruby will be different is that it's more so: in java and python, the value 5 will be on the stack as just 5, but I believe in ruby it's also going to be a reference to an object on the heap. (I may be wrong on this, my ruby is weak... corrections are will be gladly acccepted!) – Jon Kiparsky Oct 16 '14 at 16:24
  • 1
    @JonKiparsky: Numbers are immutable in Ruby. Since there is no way to mutate a number, you cannot tell the difference between whether it is allocated as an object on the heap or directly allocated on the stack. The actual truth is: neither! Most Ruby implementations optimize small integers (for C implementations that would be integers that fit into a single machine word - 1 bit, for JRuby that's 64 bit) completely away, so that they are represented *inside* the pointer (aka tagged pointer representation). – Jörg W Mittag Oct 16 '14 at 19:25
0

this is kinda strange, and im not sure if this is an answer to the question, but if you run this:

def foo():
    nums=[1,2,3]
    print "foo"
    print locals()
    print globals()
    bar(nums)
    print "foo-after"
    print locals()
    print globals()
    print(nums)


def bar(numbrs):
    print "bar"
    print locals()
    print globals()
    numbrs.pop()
    print "bar-after"
    print locals()
    print globals()

foo()

output:

>>> foo()
foo
{'nums': [1, 2, 3]}
{'bar': <function bar at 0x024759B0>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x024593B0>, '__doc__': None}
bar
{'numbrs': [1, 2, 3]}
{'bar': <function bar at 0x024759B0>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x024593B0>, '__doc__': None}
bar-after
{'numbrs': [1, 2]}
{'bar': <function bar at 0x024759B0>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x024593B0>, '__doc__': None}
foo-after
{'nums': [1, 2]}
{'bar': <function bar at 0x024759B0>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x024593B0>, '__doc__': None}
[1, 2]
TehTris
  • 3,139
  • 1
  • 21
  • 33