How do you test a mutable, implied result? - python

I have a function that doesn't "return" anything but relies on altering a dictionary/list using its mutability.
i.e.:
def func(my_list):
my_list.append(4)
I want to test this function using pytest and parameterisation:
#pytest.mark.parametrize("input1, result", [
([1], [1, 4]),
([33, 44], [33,44, 4])
])
def test_mytest(input1, result):
assert func(input1) == result
Problem is, this obviously won't work because my function doesn't actually "return" my_list.
Is it possible to test the value of my_list using pytest, and if so how?

You could compare the value of list1 to result after the call to the function.
def test_mytest(input1):
func(input1)
assert input1 == result
Note that input1 will be modified after the call to the function and can now be compared to the expected result

Related

Accumulate does not work with sum, but does work with equivalent lambda function. Why?

I was playing around with the accumulate function, and I thought that accumulate(<int[]>,sum) would yield a cumulative sum. However, running the following code results in an error.
from itertools import accumulate
print([*accumulate([1,2,3],sum)])
Specifically, I get TypeError: 'int' object is not iterable. On the other hand, running the same code with a lambda function that does the exact same thing leads to the expected result.
from itertools import accumulate
print([*accumulate([1,2,3],lambda *args:sum(args))])
# [1, 3, 6]
When I run this code using a named, custom function which does the same thing, I get yet another bizarre result.
from itertools import accumulate
def my_sum(*args): return sum(args)
print([*accumulate([1,2,3]),my_sum])
#[1, 3, 6, <function my_sum at 0x7fd57139caf0>]
It's not clear what is leading to the difference in behavior. sum,my_sum, and the anonymous function are are of the type "function", so the type alone isn't determining things. I also did the following to see if I could get any other lead; the only difference I noticed is that sum is a built in function.
print(lambda *args:sum(args),my_sum,sum,sep='\n')
# <function <lambda> at 0x7fd57139cb80>
# <function my_sum at 0x7fd57139cc10>
# <built-in function sum>
So what's going on here?
From the docs: for itertools.accumulate(iterable[, func, *, initial=None])
If func is supplied, it should be a function of two arguments. Elements of the input iterable may be any type that can be accepted as arguments to func. (For example, with the default operation of addition, elements may be any addable type including Decimal or Fraction.)
sum() does accept two arguments, but the first argument must be an iterable, and the second is the start value. Docs
Let's see what accumulate() passes to its func argument by printing the args in my_sum()
def my_sum(*args):
print(args)
return sum(args)
accumulate([1, 2, 3], my_sum)
# (1, 2)
# (3, 3)
So accumulate() passes the last accumulated value and the next number to func. Since the first argument to sum() must be iterable (which an int is not), you get that error.
Your lambda is not equivalent to sum(): sum() takes one iterable and returns the sum of its elements. Your lambda takes any number of arguments and returns the sum of those arguments. To test this, see what you get when you do sum([1, 2, 3]), and my_sum([1, 2, 3]).
In your final example you have a typo. You didn't pass my_sum to accumulate(). You created a list containing the result of accumulate([1, 2, 3]), and then the function my_sum. Fix it to print([*accumulate([1,2,3], my_sum)]) and you get the same output as the lambda case.
Note that providing no func behaves as if func=operator.add and will give you a cumulative sum.
>>> accumulate([1, 2, 3])
[1, 3, 6]

Unit test a Mutated List

I have a function that is intended to mutate a list.
I wanted to build some unit tests for this function, but I don't know how to test for a change in values when the function doesn't return something...
For example:
def square_list(lst):
"""
Input: a list of numbers
Doesn't return anything, mutates given list
"""
for i in range(len(lst)-1):
lst[i] **= 2
Obviously I can build a unit test for this, but without a retun value it won't work
import unittest
from square_list import square_list
class TestSquareList(unittest.TestCase):
def test_square_list(self):
self.assertEqual(square_list([1, 2]), [1, 4])
self.assertEqual(square_list([0]), [0])
How would you test this function mutates the input to the function appropriately?
You can set it up like this:
import unittest
from square_list import square_list
class TestSquareList(unittest.TestCase):
def test_square_list(self):
provided = [1, 2]
expected = [1, 4]
square_list(provided) # mutates provided
self.assertEqual(provided, expected)

When is it best to use a python map function with a user defined function with parentheses or without parentheses

Sorry if this sound like a dumb question, I really do not understand why am i getting values back and why the complier is not screaming error, first the below code the function add_to_five receive an (x) in the (parentheses) and return the value of x + 5; however when called in the map function i only pass in add_to_five without parentheses and provided a iterable sequence of l for the second arg and the new_value is [6, 7, 8, 9, 10]
how is x getting assigned, when called in the the map the add_to_five clearly does not get the parentheses explicitly assigned and the value of (x) is not provided to the function, however some how l is been replaced into the parentheses of add_to_five that why the result is [6, 7, 8, 9, 10].
when i take away (x +) in the return and just return only 5 though still left an (x) in the parentheses of add_to_five the new_value become [5,5,5,5,5] and i do not get an error when add_to_five referenced in the map function.
why does add_to_five work in the map function with out parentheses and also worked when i returned just (5) and still leave the (x) in the parentheses with out binding (x) to anything or value (5).
when is it best to use a function with parentheses as i know when invoked (type of add_to_five(5)) it return (int) as object type and when invoked (type of add_to_five) it returns function and random memory address.
I know lambda is a way to work around but not looking for quick solution thank you.
def add_to_five(x):
return x + 5
l = [1, 2, 3, 4, 5]
new_value = list(map(add_to_five, l))
print(new_value)
Think about functions in Python as simply objects. Objects can be passed to functions and also can be returned from functions. This is actually functional-style programming.
In your map function, you pass in a function and a list or any other object/collection that can be iterated.
Why you don't include the parantheses is because it is not a function call.
You'll always hear variables store data or references to data. But here the variable actually holds a reference to code.
When you create a function:
def func(arg1, arg2):
...
func is a function object. It is a variable that holds a reference to the code that is contained within the function.
If I do:
x = func
Here I have not used parantheses even though func is a function.
I can now do this:
x(1, 2)
It is because I assigned x as func. Calling x is equivalent to calling func.
This is a simplified form of how the map function works:
def map(function_to_call, iterable):
result = []
for element in iterable:
r = function_to_call(element)
result.append(r)
return result
Now I can go ahead and call map like this:
def square(x):
return x * x
map(square, [1, 2, 3, 4])
Map function does not return a list, it returns a map object that can be converted to a list. Here I have simplified it to prevent distraction.
So map takes in a function and a sequence, iterates over that sequence and calls the passed function on each and every element and creates a new sequence that contains the results. Map only takes a reference to the function so that it can call that function later during execution.
add_to_five similarly is a function object. It is passed to map so that map can call it later multiple times for each element.
This is advanced, so the confusion is common.
Hope this helps, let me know if something is not clear.
Maybe this helps explaining it:
Map is a function by it self, like any other function you provide the parameters for the function in (parentheses) and seperated by comma. Map works the same, only difference is that you do not insert an integer or string but a function, see here:
def add_to_five(x):
return x + 5
l = [1, 2, 3, 4, 5]
def map_function(a_function, lst):
for i, element in enumerate(lst):
lst[i] = a_function(element)
return lst
print(map_function(add_to_five, l))
#[6, 7, 8, 9, 10]

Copy a variable in python (jupyter) / Use different functions with same variables

I wrote a small Programm in python but it don't work like expected.
Here's the code:
puzzle = [8, 7, 5, 4, 1, 2, 3, 0, 6]
def count(p):
p[0] += 1
return p
def main(p):
print(p)
l = count(p)
print(l)
print(p)
b1 = main(puzzle)
I expect that print(p) will be different from print(l), but the result of both is the same, it's the result that print(l) should have. But p did change also, however I would need it to be unchanged… Is this a special python behavior? Is there something I missed?
I also tried to change the variable names in the functions, but that didn't help.
I restarted the Compiler, but that didn't help either.
Is there a solution to store a function output and than call the function again without let the function change the given parameters?
So that l will be the result after the calculation and p will stay the value before?
Kind Regards,
Joh.
You are passing a List parameter. Parameter passing is Call-by-Object. Since a List is a mutable object in this situation it is similar to pass by reference and changes to your List object will persist. If you were passing an immutable, such as an Integer or String, it would be akin to pass by copy/value, and changes would not persist. E.g.:
def s2asdf(s):
s = "asdf"
s = "hello world"
s2asdf(s)
print s
... results in:
$ python example.py
hello world
The reason for this is because Python passes function parameters by reference. When you call the count function it allows the function to modify the list inside the function and the changes will be applied to the original object.
If you want to have the function not modify the list but instead return a different list, you will have to make a copy of the list either by passing a copy to the function or make a copy inside the function itself. There are many ways to copy a list in Python, but I like to use the list() function to do it.
This should fix your problem:
puzzle = [8, 7, 5, 4, 1, 2, 3, 0, 6]
def count(p):
new_list = list(p) # copy values of p to new_list
new_list[0] += 1
return new_list
def main(p):
print(p)
l = count(p)
print(l) # l is the new_list returned from count
print(p) # p stays the original value
b1 = main(puzzle)

Python: Why assigning list values inline returns a list of "None" elements?

I'm still new to Python, and I've been making a small function that reverses a list of lists, both the original list and the lists inside. This is my code:
def deep_reverse(L):
L.reverse()
L = [i.reverse() for i in L]
Now this code works perfectly, but if I do a small change and rearrange the lines like this:
def deep_reverse(L):
L = [i.reverse() for i in L]
L.reverse()
suddenly it stops working! It only reverses the internal lists but not the original one. Putting some debugging print() statements inside, I can see the first code reverses the original list after the first line and it's printed, but the second code actually prints a list containing 'None' as elements after reversing the list. Can anyone please explain why this behavior and what is the difference between the two codes?
The reverse() function reverses a list in-place and returns None, that explains the weird behavior. A correct implementation would be:
def deep_reverse(L):
ans = [i[::-1] for i in L]
ans.reverse()
return ans
Also, it's a bad idea to reassign and/or mutate a parameter to a function, it can lead to unexpected results. Sometimes functions in the standard library do it for efficiency reasons (for example, sort() and reverse()), that's ok - but it can lead to confusions, like the one you just experienced. Your code doesn't have to be written in that fashion unless strictly necessary.
Your first deep_reverse function reassigned L, but it is not a global parameter and is not returned in your function. Hence this variable is lost. HOWEVER, you are mutating the list in place, hence the modifications remain which is why it still works! Your original function is equivalent to the following (note there is no final assignment):
def deep_reverse(L):
L.reverse()
[i.reverse() for i in L]
This should probably be written using a for-loop:
def deep_reverse_2(L):
L.reverse()
for i in L:
i.reverse()
L = [[1, 2, 3], [2, 3, 4]]
deep_reverse_2(L)
>>> L
[[4, 3, 2], [3, 2, 1]]
The second function does not work because you reassign L inside the function (it is now local to the function and not the same L variable you passed in to the function). They would have different memory locations if you checked using id. Given that nothing is returned, this new L list is lost, as are the modifications made to it.

Categories

Resources