mock python instance method with argument or call original - python

Given any class with an instance method foo that depends on an instance variable, e.g.
class A:
def __init__(self, x: int):
self.x = x
def foo(self, y: int):
return self.x * y
def test_foo(mocker):
a = A(1)
y = -10
assert a.foo(y) < 0
mocker.patch.object(a, "foo", return_value=a.x * abs(y))
assert a.foo(y) > 0
The question is how to mock A(1).foo(y) only for a test value of interest, e.g. y < 0, otherwise return the original call to A(1).foo(y)? In this example above, the y value is already known, but for this question, please assume that it is unknown (deep in 3rd-party code, say) and it must be inspected from the call to foo(). It seems like python mocks apply to all calls to A(1).foo(y) without any options to only apply a mock for specific values of y by inspecting the method call args.
For example, using pytest-mock, the test above works but it has no options to inspect the method call, check the args to the method call and only inject a mock when the args match some values of interest and otherwise call the original method.
Is the solution to create a subclass of A that can inspect args and return a mock value or call super, then use a mock to inject that subclass in place of A? For example, building on the above example code:
class B(A):
def foo(self, y: int):
if y < 0:
return self.x * abs(y)
else:
super().foo(y)
def test_b_foo(mocker):
a = A(1)
y = -10
assert a.foo(y) < 0
mocker.patch.object(A, "foo", side_effect=B(a.x).foo)
assert a.foo(y) > 0
This works OK, but it assumes that A is already initialized and it uses the instance a to initialize B. How could the mock substitute calls to A(i) with calls to B(i) so that the test actually calls B(i).foo(y)? What happens when the mocks for a specific argument to A(i).foo(y) for some specific y should apply to the A.foo method no matter how or when it is initialized? I'm looking for something like the ruby mocks that apply a mock return value only when the patched object (method) is called with some specific arguments. It seems like some kind of spy might be able to dynamically inject a return value when it detects a method call with specific arguments (to short-circuit the original method), but otherwise call the original method. I guess it would be something like:
m = mock.patch.object(A, "foo")
m.called_with(y < 0).return_value = mock_specific_to_args
# otherwise m.foo(10) calls the original method

You're almost there with your B.foo idea, but it doesn't have to be a class and can just be a lambda that checks the logic (assuming you saved the original A.foo). I'm not totally clear on how all the Mock constructor args work, so I just use patch()'s new parameter, like this:
orig_a_foo = A.foo
with patch.object(A, 'foo', new=lambda self, y: self.x * abs(y) if y < 0 else orig_a_foo(self, y)):
assert a.foo(-10) == 10

Check the value of y to decide whether to mock A.foo or not.
def test_foo(mocker):
a = A(1)
y = -10
assert a.foo(y) < 0
if y < 0:
mocker.patch.object(a, "foo", return_value=a.x * abs(y))
assert a.foo(y) > 0

Related

python class attributes as standard input in a class method

I don't seam to be able to do this but is would make sense that you could.
So mybe I just made a mistake.
class Foobar:
def __init__(self):
self.myatr = 0
def add(self, someinput=self.myatr): # <-- someinput=self.myatr???
return someinput += 1
but you get the error
NameError: name 'self' is not defined
But it would be logicl if the this was the way it worket
f = Foobar()
f.add() # returns 1
f.add(1) # returns 2
Instance methods are functions bound to class attributes, defined when the class is defined, before any instance exists. Similarly, the default value is set once, at definition time, not on-demand when the method is called without an explicit argument.
As such, you need a sentinel (typically None) which signals that no argument was passed.
def add(self, someinput=None):
if someinput is None:
someinput = self.myatr
return someinput + 1
Default arguments are evaluated at function definition. Moreover, the names of the arguments defined earlier (like self in your function) aren't available during function definition. So when you refer to self.myattr, there's no self yet.
For example, consider this function:
>>> def test(thing=print('hello')):
... ...
...
hello
>>>
The expression print('hello') was evaluated right when the function was defined, and it won't be re-evaluated when you call test.
Also, return someinput += 1 is an error too because assignment is not an expression.
Furthermore, integers are always copied, so if you do this:
def test(x):
x += 1
return x
a = 6
test(a)
a will still be equal to six, since the call test(a) copied a.

Python thinks I'm passing 3 arguments although I'm only passing 2

I've written a basic Vector class for dealing with vectors. Calculating their magnitude and whatnot. I have two overloaded constructors one taking two arguments x and y and one taking a tuple (x,y).
When I initialize a variable it gives me the error:
TypeError: __init__() takes 2 positional arguments but 3 were given.
What am I missing?
class Vector:
x = 0.0
y = 0.0
def __init__(self, x, y):
self.x = x
self.y = y
def __init__(self, coordinates):
self.x = coordinates[0]
self.y = coordinates[1]
v1 = Vector(1,3)
print(v1.x)
Python doesn't support overloading, so you overwrite the first __init__ method with the second one. The second one takes only 1 argument: coordinates. That Python writes 3 arguments is because the instance (self) is passed implicitly.
Remove the second __init__ method and it will work correctly.
If you like multiple constructors you could for example implement the additional ones as classmethods:
class Vector:
x = 0.0
y = 0.0
def __init__(self, x, y):
self.x = x
self.y = y
#classmethod
def from_list(cls, coordinates):
return cls(coordinates[0], coordinates[1])
# you could also use unpacking:
# return cls(*coordinates)
Test:
>>> v1 = Vector(1,3)
>>> print(v1.x)
1
>>> v2 = Vector.from_list([2, 1])
>>> print(v2.x)
2
Python doesn't do overloaded constructors - its a dynamic language and has no preknowledge of type that would allow this to happen. Your code defines an __init__ then replaces it completely with a second __init__. If you want to do something like this, you could define a factory method with the alternate signature and call that.
You do not have overloaded constructors: the second method definition overwrites the first. Thus, your only surviving constructor takes only two arguments. You supplied 3 -- self is implied.

How does "in" checks for membership?

I have a multiple instances of a class. I consider two classes equal, when a certain attribute matches.
All instances are in an array list = [a, b, c]. I now create a new instance of said class d. When I do d in list it ofc outputs false.
My question is: How is membership checked when using in? Is it normal comparison (which means I can use __eq__ in my class to implement the equality of classes)? If not: How can I achieve that in matches if a certain attribute of a class equals?
class Foo:
def __init__(self, x):
self.x = x
def __eq__(self, other):
if isinstance(other, Foo):
return self.x == other.x
a = [1,2,3,Foo(4),Foo(5)]
Foo(5) in a
>>>True
Foo(3) in a
>>>False
From the docs:
For user-defined classes which define the __contains__() method, x in y is true if and only if y.__contains__(x) is true.
For user-defined classes which do not define __contains__() but do define __iter__(), x in y is true if some value z with x == z is produced while iterating over y. If an exception is raised during the iteration, it is as if in raised that exception.
Lastly, the old-style iteration protocol is tried: if a class defines __getitem__(), x in y is true if and only if there is a non-negative integer index i such that x == y[i], and all lower integer indices do not raise IndexError exception. (If any other exception is raised, it is as if in raised that exception).
Behavior of in is based on the __contains__() method. Let us see with an example:
class X():
def __contains__(self, m):
print 'Hello'
Now when you do in on X()m you can see 'Hello' printed
>>> x = X()
>>> 1 in x
Hello
False
As per the __contains__() document:
For objects that don’t define __contains__(), the membership test first tries iteration via __iter__(), then the old sequence iteration protocol via __getitem__(), see this section in the language reference.

Possible to use more than one argument on __getitem__?

I am trying to use
__getitem__(self, x, y):
on my Matrix class, but it seems to me it doesn't work (I still don't know very well to use python).
I'm calling it like this:
print matrix[0,0]
Is it possible at all to use more than one argument? Thanks. Maybe I can use only one argument but pass it as a tuple?
__getitem__ only accepts one argument (other than self), so you get passed a tuple.
You can do this:
class matrix:
def __getitem__(self, pos):
x,y = pos
return "fetching %s, %s" % (x, y)
m = matrix()
print m[1,2]
outputs
fetching 1, 2
See the documentation for object.__getitem__ for more information.
Indeed, when you execute bla[x,y], you're calling type(bla).__getitem__(bla, (x, y)) -- Python automatically forms the tuple for you and passes it on to __getitem__ as the second argument (the first one being its self). There's no good way[1] to express that __getitem__ wants more arguments, but also no need to.
[1] In Python 2.* you can actually give __getitem__ an auto-unpacking signature which will raise ValueError or TypeError when you're indexing with too many or too few indices...:
>>> class X(object):
... def __getitem__(self, (x, y)): return x, y
...
>>> x = X()
>>> x[23, 45]
(23, 45)
Whether that's "a good way" is moot... it's been deprecated in Python 3 so you can infer that Guido didn't consider it good upon long reflection;-). Doing your own unpacking (of a single argument in the signature) is no big deal and lets you provide clearer errors (and uniform ones, rather than ones of different types for the very similar error of indexing such an instance with 1 vs, say, 3 indices;-).
No, __getitem__ just takes one argument (in addition to self). In the case of matrix[0, 0], the argument is the tuple (0, 0).
You can directly call __getitem__ instead of using brackets.
Example:
class Foo():
def __init__(self):
self.a = [5, 7, 9]
def __getitem__(self, i, plus_one=False):
if plus_one:
i += 1
return self.a[I]
foo = Foo()
foo[0] # 5
foo.__getitem__(0) # 5
foo.__getitem__(0, True) # 7
I learned today that you can pass double index to your object that implements getitem, as the following snippet illustrates:
class MyClass:
def __init__(self):
self.data = [[1]]
def __getitem__(self, index):
return self.data[index]
c = MyClass()
print(c[0][0])

What does python3 do with the methods passed to the "key" argument of sorted()?

I have a question about how python treats the methods passed to sorted(). Consider the following small script:
#!/usr/bin/env python3
import random
class SortClass:
def __init__(self):
self.x = random.choice(range(10))
self.y = random.choice(range(10))
def getX(self):
return self.x
def getY(self):
return self.y
if __name__ == "__main__":
sortList = [SortClass() for i in range(10)]
sortedList = sorted(sortList, key = SortClass.getX)
for object in sortedList:
print("X:", object.getX(),"Y:",object.getY())
Which gives output similar to the following:
X: 1 Y: 5
X: 1 Y: 6
X: 1 Y: 5
X: 2 Y: 8
X: 2 Y: 1
X: 3 Y: 6
X: 4 Y: 2
X: 5 Y: 4
X: 6 Y: 2
X: 8 Y: 0
This script sorts the SortClass objects according to the value of each instance's x field. Notice, however, that the "key" argument of sorted points to SortClass.getX, not to any particular instance of SortClass. I'm a bit confused as to how python actually uses the method passed as "key". Does calling getX() like this work because the objects passed to it are of the same type as the "self" argument? Is this a safe use of the "key" argument?
Methods on classes are just functions.
class MyClass(object):
... def my_method(self): pass
...
>>> MyClass.my_method
<function my_method at 0x661c38>
When you fetch a method from an instance of a class, Python uses some magic (called descriptors) to return a bound method. Bound methods automatically insert the instance as the first argument when they are called.
>>> MyClass().my_method
<bound method MyClass.my_method of <__main__.myClass object at 0x6e2498>>
However, as you have noticed, you can also directly call the function with an instance as the first argument: MyClass.my_method(MyClass())
That's what happening with sorted(). Python calls the function SortedClass.getx with the item in the list which happens to be an instance of SortedClass.
(By the way, there are better ways to do what you are doing. First of all, don't use methods to expose attributes like getX. Use properties or plain attributes. Also, look at operator.itemgetter and operator.methodcaller.)
+1 for Benjamin's answer.
Also, read the Sorting HowTo if you want to master sorting in Python: http://docs.python.org/howto/sorting.html

Categories