Alexander Tsepkov
Alexander Tsepkov

Reputation: 4186

Using arguments in Python functions

I'm aware of mutuable vs immutable arguments in Python, and which is which, but here is a weird issue I ran into with mutable arguments. The simplified version is as follows:

def fun1a(tmp):
    tmp.append(3)
    tmp.append(2)
    tmp.append(1)
    return True

def fun1(a):
    b = fun1a(a)
    print a #prints [3,2,1]
    return b

def fun2a():
    tmp = []
    tmp.append(3)
    tmp.append(2)
    tmp.append(1)
    return [True, tmp]

def fun2(a):
    [b, a] = fun2a()
    print a #prints [3,2,1]
    return b

def main():
    a=[]
    if fun1(a):
        print a #prints [3,2,1]
    if fun2(b):
        print b #prints garbage, e.g. (0,1)

As you can see the only difference is that fun2 points the passed in argument to reference a list created inside fun2a, while fun1 simply appends to the list created in main. In the end, fun1 returns the correct result, while fun2 returns random garbage rather than the result I'd expect. What's the problem here?

Thanks

Upvotes: 4

Views: 450

Answers (3)

johnsyweb
johnsyweb

Reputation: 141958

As others have pointed out, there is no name 'b' in your main() function.

A better way of asserting how your code is behaving is to unit test it. Unit-testing is very easy in Python and a great habit to get into. When I first started writing Python a few years back the guy I paired with insisted on testing everything. Since that day I have continued and have never had to use the Python debugger as a result! I digress...

Consider:

import unittest

class Test(unittest.TestCase):

    def test_fun1a_populates_tmp(self):
        some_list = []
        fun1a(tmp=some_list)
        self.assertEquals([3, 2, 1], some_list)

    def test_fun1a_returns_true(self):
        some_list = []
        ret = fun1a(tmp=some_list)
        self.assertTrue(ret)

    def test_fun1_populates_a(self):
        some_list = []
        fun1(a=some_list)
        self.assertEquals([3, 2, 1], some_list)

    def test_fun1_returns_true(self):
        some_list = []
        ret = fun1(a=some_list)
        self.assertTrue(ret)

    def test_fun2a_populates_returned_list(self):
        ret = fun2a()
        self.assertEquals([True, [3, 2, 1]], ret)

    def test_fun2_returns_true(self):
        some_list = []
        ret = fun2(some_list)
        self.assertTrue(ret)

    def test_fun2_des_not_populate_passed_list(self):
        some_list = []
        fun2(some_list)
        self.assertEqual(0, len(some_list))


if __name__ == '__main__':
    unittest.main()

Each of these unit tests pass and document how your functions behave (save for the printing, you can add the tests for those if they are needed). They also provide a harness for when you edit your code, because they should continue to pass or start failing if you break something.

I haven't unit-tested main(), since it is clearly broken.

Upvotes: 1

jon_darkstar
jon_darkstar

Reputation: 16788

This isn't so much of a mutable/immutable issue as one of scope.

"b" exists only in fun1 and fun2 bodies. It is not present in the main or global scope (at least intentionally)

--EDIT--

>>> def fun1(b):
...     b = b + 1
...     return b
... 
>>> def fun2(a):
...     b = 1
...     return b
... 
>>> fun1(5)
6
>>> fun2(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

(From my interpreter in terminal)

I'm guessing your 'b' was initialized somewhere else. What happened in the other function is of has no effect on this.

This is me running your exact code:

>>> main()
[3, 2, 1]
[3, 2, 1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in main
NameError: global name 'b' is not defined
>>> b = 'whatever'
>>> main()
[3, 2, 1]
[3, 2, 1]
[3, 2, 1]
whatever

Upvotes: 3

Eric Pauley
Eric Pauley

Reputation: 1779

The problem may be related to the difference between lists and tuples. In fun2, don't put brackets around a,b. In fun2a, return a tuple of the two objects and not a list. Python should write the varaibles correctly, if that's the problem that you're trying to solve. Also, you called fun2 with argument b when b was never defined. Of course, the parameter for fun2 is never actually used, because it is rewritten before it is read.

In the end, your code should look like this:

def fun1a(tmp):
tmp.append(3)
tmp.append(2)
tmp.append(1)
return True

def fun1(a):
    b = fun1a(a)
    print a #prints [3,2,1]
    return b

def fun2a():
    tmp = []
    tmp.append(3)
    tmp.append(2)
    tmp.append(1)
    return (True, tmp)

def fun2():
    b, a = fun2a()
    print a #prints [3,2,1]
    return b

def main():
    a=[]
    if fun1(a):
        print a #prints [3,2,1]
    if fun2():
        print b #prints garbage, e.g. (0,1)

which should print [3,2,1] both times.

Upvotes: -1

Related Questions