Pytest: Carthesian product of dependent fixtures - fixtures

Imagine the following testsuite
import pytest
#pytest.fixture(params=1, 2, 3)
def shape(request):
return request.param
#pytest.fixture
def data(shape):
return shape
def test_resize(data, shape):
pass
where I have two fixtures data and shape. data depends on the fixture shape and is being generated for each of the possible values. But in test_resize I want to test over all possible combinations of data and shape:
1, 1
1, 2
1, 3
2, 1
etc. With the implementation above it does not expand the carthesian product though:
1, 1
2, 2
3, 3
Is there a way to make py.test expand the fixtures to all possible combinations?

As your output shows, shape is parameterized but data is not, thus there will only be one instance of the data fixture for each instance of the shape fixture. I would also parameterize data. Then, still having the data fixture depend upon shape, you'll get the product you desire:
import pytest
fixture_params = (1, 2, 3)
#pytest.fixture(params=fixture_params)
def shape(request):
return request.param
#pytest.fixture(params=fixture_params)
def data(request, shape):
print(request.param)
return request.param
def test_resize(data, shape):
print(data, shape)
assert 0 and 'assert to show prints'

Related

How do I minimize the code needed to perform matrix row operations in a python jupyter notebook? (using SymPy)

Here is my code so far (edited screenshots into code cells)
from sympy import *
import copy
init_printing()
A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
A
#Functions for row operations
def rowSwap(A, i, j):
B = A.elementary_row_op(op='n<->m',row1=i-1, row2=j-1)
return B
def rowMultiply(A, i , c):
B = A.elementary_row_op(op='n->kn',row=i-1, k=c)
return B
def rowAddSubtract(A, i, c, j):
B = A.elementary_row_op(op='n->n+km',row=i-1, k=c, row2=j-1) #use negative symbol to subtract
return B
I am creating a matrix row operation calculator using sympy in a jupyter notebook, for linear algebra students. The catch is, the students have to enter the row operations themselves. I've created functions that they can use to perform row operations, however, I need assistance in editing them so students can write less code. Specifically, I would like them to be able to just type:
rowSwap(A, 1, 3)
and have the resulting matrix be displayed and saved, as opposed to entering:
A = rowSwap(A, 1, 3)
A
*The above two line way of doing it is the only way I've been able to get the result matrix to actually save so more steps can be done (i.e. performing operations to get to rref).
My attempt so far at a solution looks like this:
def rowSwap(A, i, j):
A = A.elementary_row_op(op='n<->m',row1=i-1, row2=j-1)
return A
def rowMultiply(A, i, c):
A = A.elementary_row_op(op='n->kn',row=i-1, k=c)
return A
def rowAddSubtract(A, i, c, j):
A = A.elementary_row_op(op='n->n+km',row=i-1, k=c, row2=j-1)
return A
When calling a function it would look like this:
rowSwap(A, 1, 2)
rowMultiply(A, 1, 10)
Yes I was able to minimize the code needed to perform a single operation, however it is functionally useless since it does not actually save and update the matrix A. As when I call the rowMultiply function, it performs on the original matrix defined at the top, not the result shown of the cell just before it.
I come from a c++ background so dealing with objects in python is a bit foreign at the moment.
Any assistance would be much appreciated.

Can I use side_effect in my mocking to provide an indefinite number of values?

So I can use an iterable with side_effect in python mock to produce changing values returned by my calls to the mock:
some_mock.side_effect = [1, 2, 3]
return_value provides the same value every time
some_mock.return_value = 8
Is there a way I can use one or both of these methods so that a mock produces some scheduled values to begin and then an infinite response of one particular value when the first set is exhausted? i.e.:
[1, 2, 3, 8, 8, 8, 8, 8, 8, etc. etc etc.]
There is no specific build-in feature that does that, but you can achieve this by adding a side effect that does this.
In the cases I can think of, it would be sufficient to just add some highest needed number of values instead of an infinite number, and use the side_effect version that takes a list:
side_effect = [1, 2, 3] + [8] * 100
my_mock.side_effect = side_effect
If you really need that infinite number of responses, you can use the other version of side_effect instead that uses a function object instead of a list. You need some generator function that creates your infinite list, and iterate over that in your function, remembering the current position. Here is an example implementation for that (using a class to avoid global variables):
from itertools import repeat
class SideEffect:
def __init__(self):
self.it = self.generator() # holds the current iterator
#staticmethod
def generator():
yield from range(1, 4) # yields 1, 2, 3
yield from repeat(8) # yields 8 infinitely
def side_effect(self, *args, **kwargs):
return next(self.it)
...
my_mock.side_effect = SideEffect().side_effect
This should have the wanted effect.

Python - create a class as index for 2-dimension numpy array

I have some code of index class TwoDimIndex. I wanna use it to indexes of numpy 2-dimension array like arr[idx]. Below is code of that class.
import numpy as np
class TwoDimIndex:
def __init__(self, i1, i2):
self.i1 = i1
self.i2 = i2
pass
def __index__(self):
return self.i1, self.i2
# return np.array([self.i1, self.i2]) - Tried this too
def __eq__(self, other):
return self.i1 == other
def __int__(self):
return self.i1
def __hash__(self):
return hash(self.__int__())
# Don't edit code of this function please
def useful_func():
idx = TwoDimIndex(1, 1) # Can edit create instance
arr_two_dim = np.array([[0, 1], [2, 3]])
print(idx.__index__() == (1, 1))
print(arr_two_dim[1, 1], arr_two_dim[idx.__index__()]) # Success
print(arr_two_dim[idx]) # Crash here
# But I want this code work!!!
useful_func()
Class TwoDimIndex is use to be index, for example arr[TwoDimIndex()].
All code for Jupyter Notebook and Python 3.8. But I get error when execute this code.
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
Is there any ways to make an instance of a class TwoDimIndex an numpy 2-d array index?
I find short solution with inherit from tuple.
class TwoDimIndex(tuple):
def __init__(self, tup):
self.tup = tuple(tup)
pass
def __index__(self):
return self.tup
def useful_func():
idx = TwoDimIndex([1, 1]) # Edit at create class
arr_two_dim = np.array([[0, 1], [2, 3]])
print(idx.__index__() == (1, 1))
print(arr_two_dim[1, 1], arr_two_dim[idx.__index__()]) # Success
print(arr_two_dim[idx]) # Success now
useful_func()
Not sure it is right way but it work.

How to have one method reference another method from within the same class?

I am trying to better understand classes. Suppose I have a class that handles data modifications, such as copying data and resampling data. In my case, the function resample requires the function duplicate since I want to modify a copy of the original data rather than the original data itself.
I've seen a similar question addressed here, though I was unable to apply those solutions to my case. I'm wondering if things like #classmethod are required because of posts like this.
import numpy as np
import random
class DataMods():
def __init__(self, data):
self.data = data
def duplicate(self):
if isinstance(self.data, np.ndarray):
res = np.copy(self.data)
else:
res = self.data[:]
return res
def resample(self, nshuffles=0, struct=np.array):
# resample copy of data instead of data
data_dup = self.duplicate(self.data) # this is the problematic line
size = len(data_dup)
if isinstance(nshuffles, int):
if nshuffles > 1:
for idx in range(nshuffles - 1):
res = struct(random.sample(list(data_dup), size))
else:
raise ValueError("nshuffles ...")
return struct(random.sample(list(res), size))
aa = np.array([1, 2, 3, 4, 5])
a2 = DataMods(data=aa).resample(nshuffles=5)
print(a2)
>> TypeError: duplicate() takes 1 positional argument but 2 were given
If I change the problematic line to:
data_dup = self.duplicate(data)
Then I get a different error:
NameError: name 'data' is not defined
I've tried a few different variations, but all were unsuccessful. What am I not understanding correctly?

Is it possible to adapt this approach of using dictionaries to find/evaluate one of many functions and its corresponding args?

Suppose one has several separate functions to evaluate some given data. Rather than use redundant if/else loops, one decides to use a dictionary key to find the particular function and its corresponding args. I feel like this is possible, but I can't figure out how to make this work. As a simplified example (that I hope to adapt for my case), consider the code below:
def func_one(x, a, b, c=0):
""" arbitrary function """
# c is initialized since it is necessary in func_two and has no effect in func_one
return a*x + b
def func_two(x, a, b, c):
""" arbitrary function """
return a*x**2 + b*x + c
def pick_function(key, x=5):
""" picks and evaluates arbitrary function by key """
if key != (1 or 2):
raise ValueError("key = 1 or 2")
## args = a, b, c
args_one = (1, 2, 3)
args_two = (4, 5, 3)
## function dictionary
func_dict = dict(zip([1, 2], [func_one, func_two]))
## args dictionary
args_dict = dict(zip([1, 2], [args_one, args_two]))
## apply function to args
func = func_dict[key]
args = args_dict[key]
## my original attempt >> return func(x, args)
return func(x, *args) ## << EDITED SOLUTION VIA COMMENTS BELOW
print(func_one(x=5, a=1, b=2, c=3)) # prints 7
But,
print(pick_function(1))
returns an error message
File "stack_overflow_example_question.py", line 17, in pick_function
return func(x, args)
TypeError: func_one() missing 1 required positional argument: 'b'
Clearly, not all of the args are being passed through with the dictionary. I've tried various combinations of adding/removing extra brackets and paranthesis from args_one and args_two (as defined in pick_function). Is this approach fruitful? Are there other convenient (in terms of readability and speed) approaches that do not require many if/else loops?
To fix your code with minimal changes, change return func(x, args) to return func(x, *args). I think this is what Anton vBR is suggesting in the comments.
However, I think your code could be further simplified by
using the * ("splat") and ** ("double-splat"?) positional/keyword argument unpacking operators like this:
def func_one(x, a, b, c=0):
""" arbitrary function """
# c is initialized since it is necessary in func_two and has no effect in func_one
return a*x + b
def func_two(x, a, b, c):
""" arbitrary function """
return a*x**2 + b*x + c
def func(key, *args, **kwargs):
funcmap = {1: func_one, 2: func_two}
return funcmap[key](*args, **kwargs)
def pick_function(key, x=5):
""" picks and evaluates arbitrary function by key """
argmap = {1: (1, 2, 3), 2: (4, 5, 3)}
return func(key, x, *argmap[key])
print(func_one(x=5, a=1, b=2, c=3))
# 7
print(pick_function(1))
# 7
print(func(1, 5, 1, 2, 3))
# 7
print(func(1, b=2, a=1, c=3, x=5))
# 7
If I understand what you are asking, I don't know that you want to use 1, 2... as your dictionary keys anyway. You can pass the name of the function and the list of the args you want to use straight to a function and use it that way like:
def use_function(func, argList):
return (func(*argList))
print(use_function(func_one, [5, 1, 2, 3]))
or:
def use_function_2(func, argDict):
return (func(**argDict))
print(use_function_2(func_one, {'a':1, 'b':2,'x':5, 'c':3}))
And if you like you could still use a dictionary to hold numbers which correspond to functions as well. That would look like this:
def pick_function_2(key, x=5):
""" picks and evaluates arbitrary function by key """
if key != (1 or 2):
raise ValueError("key = 1 or 2")
## args = a, b, c
args_one = [1, 2, 3]
args_two = [4, 5, 3]
## function dictionary
func_dict = dict(zip([1, 2], [func_one, func_two]))
## args dictionary
args_dict = dict(zip([1, 2], [args_one, args_two]))
## apply function to args
func = func_dict[key]
args = args_dict[key]
return func(*([x] + args))
print(pick_function_2(1))
However, this is starting to get a bit confusing. I would make sure you take some time and just double check that this is actually what you want to do.
You are trying to mixing named arguments and unnamed arguments!
As rule of thumb, if you pass a dict to a function using ** notation, you can access the arguments using **kwargs value. However, if you pass a tuple to the funcion using * notation, you can access the arguments using *args value.
I edited the method in the following way:
def func_one(*args, **kwargs):
""" arbitrary function """
# c is initialized since it is necessary in func_two and has no effect in func_one
if args:
print("args: {}".format(args))
x, other_args, *_ = args
a, b , c = other_args
elif kwargs:
print("kwargs: {}".format(kwargs))
x, a , b , c = kwargs['x'], kwargs['a'], kwargs['b'], kwargs['c']
return a*x + b
So, in the first call you have:
print(func_one(x=5, a=1, b=2, c=3)) # prints 7
kwargs: {'b': 2, 'a': 1, 'x': 5, 'c': 3}
7
Because you are passing named arguments.
In the second execution, you'll have:
print(pick_function(1))
args: (5, (1, 2, 3))
7
I know you wanted to find a solution without if/else, but you have to discriminate between theese two cases.

Resources