I am relatively familiar with Python, but might have forgotten how it is expected to behave in certain instances, as I've been working more lately with Rust, which has a lot of memory protections. I should stipulate that I'm using Python 3.9.4.
I'm working some Project Euler problems for practice and am writing unit tests using the unittest framework to go along with my code. What is surprising me is that the values of local variables--namely, a list--seem to be persisting across function calls. I am wondering if this is expected behaviour, either of Python in general or of the unittest framework? I must admit that I was quite surprised. My example is below.
This code is for Problem 3 of Project Euler. (I believe it's ok to share solutions to the first 100 problems, so I'm not violating any Project Euler's agreements). My code finds the prime factors (including repeats) of a given non-negative number, it looks like this:
import math
# Returns prime factors of a given non-negative number
def prime_factors(n, primes=[]):
if n < 0:
raise ValueError('Negative values are not valid')
if n < 2:
return primes
for i in range(2, math.floor(n/2) + 1):
if n % i == 0:
primes.append(i)
return prime_factors(n/i, primes=primes)
primes.append(n)
return primes
I've written some unittests to go along with this code (I've abbreviated slightly for clarity):
import unittest
class LargestPrimeTestCase(unittest.TestCase):
def test_prime_factors(self):
# Test obvious simple cases
self.assertEqual([2], prime_factors(2))
self.assertEqual([3], prime_factors(3))
I would expect these tests to pass, but they fail on the second assert statement. Namely, I'm receiving an AssertionError
:
Traceback (most recent call last):
File "/path/to/file", line 20, in test_largest_prime
self.assertEqual([3], prime_factors(3))
AssertionError: Lists differ: [3] != [2, 3]
Yet, if I run just the first or the second line without the other, with a line commented out, the test passes. I've tested my code with more complex cases, so I know that in individual cases, the function correctly identifies the prime factors of a given number. What it appears to me, is that the values stored in primes
are being persisted across function calls, even if I call the function without stipulating the value of primes
, which should reset the value of the variable. I.e. calling
x = prime_factors(2)
y = prime_factors(3)
print(x)
print(y)
should result in:
[2]
[3]
and not:
[2]
[2, 3]
Yet, the latter seems to be occurring.
If I extract the second line of the test to another function, say:
def test_prime_factors_2(self):
self.assertEqual([3], prime_factors(3))
I'm still receiving the same error.
I tried adding some setUp
and tearDown
functions:
def setUp(self):
primes = []
def tearDown(self):
primes=[]
But this also did not solve the problem.
Is this expected behavior of unittest? Is this my own confusion with Python about how variables are stored and copied? I was able to fix the problem by modifying my code to:
import math
import copy
# Returns prime factors of a given non-negative number
def prime_factors(n, primes=[]):
prime_numbers = copy.deepcopy(primes) # Adding a deepcopy here to avoid Python copying issues?
if n < 0:
raise ValueError('Negative values are not valid')
if n < 2:
return prime_numbers
for i in range(2, math.floor(n/2) + 1):
if n % i == 0:
prime_numbers.append(i)
return prime_factors(n/i, primes=prime_numbers)
prime_numbers.append(n)
return prime_numbers
This suggests to me that my issue is related to the way Python copies values. Would someone be able to enlighten me here?