Mock a Python class __init__ partially? - python

I have a Python class with complicated initialization. I would like to mock the class initialization to avoid writing too much scaffolding code. I want to test its non-mocked method.
A simple example:
class Person:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def do_work(self):
return self.x + self.y
There's an answer which shows how to get it done and it works - https://stackoverflow.com/a/21771920/3346915.
Here's a passing test:
from unittest.mock import patch
with patch.object(Person, '__init__', lambda self: None):
person = Person()
person.x = 3
person.y = 4
assert person.do_work() == 7
I wonder, however, if it would be possible to pass x and y as part of the Person initialization to avoid assigning the fields after the construction to reduce the amount of code?
I wonder if this would be possible?
from unittest.mock import patch
with patch.object(Person, '__init__', lambda self, x, y: None):
person = Person(x=3, y=4)
assert person.do_work() == 7
This doesn't work of course because the x and y values are not assigned to the person instance.

lambdas do not support assignment, but you do not have to use lambda as third argument - normal (named) function will work too, so you can do:
class Person:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def do_work(self):
return self.x + self.y
from unittest.mock import patch
def newinit(self, x, y):
self.x = x
self.y = y
with patch.object(Person, '__init__', newinit):
person = Person(x=3, y=4)
assert person.do_work() == 7
(tested in Python 3.7.3)

Related

Function missing 1 required positional argument [duplicate]

This question already has an answer here:
Missing 1 required positional argument
(1 answer)
Closed last year.
I'm getting this error :
Traceback (most recent call last):
File "C:/Users/mali03/Desktop/Python/Practice/p2.py", line 18, in <module>
first.subtraction(1, 2)
TypeError: subtraction() missing 1 required positional argument: 'y'
Here is my calculator class
class calculator:
def __init__(self, x, y):
self.x = x
self.y = y
def addition(self, x, y):
return self.x + self.y
def subtraction(self, x, y):
if self.x > self.y:
return self.y - self.x
else:
return self.x - self.y
I then call subtraction with the following:
first = calculator
first.subtraction(1, 2)
Like stated previously, you don't have to include parameters in your addition or subtraction functions if you already gather that information in the __init__ function.
Like so:
class calculator:
def __init__(self, x, y):
self.x = x
self.y = y
def addition(self):
return self.x + self.y
def subtraction(self):
if self.x > self.y:
return self.y - self.x
else:
return self.x - self.y
first = calculator
print(first(5,10).addition())
Alternatively, if you do want to have x and y parameters in your addition and subtraction functions, you can adjust your code like so:
class calculator:
def addition(self, x, y):
return x + y
def subtraction(self, x, y):
if x > y:
return y - x
else:
return x - y
first = calculator
print(first().addition(5, 10))
Where parameters of individual functions are used instead to the parameters given to the class object.
Either ways work, it depends on how you want to use the class.
Alternatively you could do:
class calculator():
def addition(self, x, y):
return x + y
def subtraction(self, x, y):
if x > y:
return y - x
else:
return x - y
first = calculator()
print(first.subtraction(1, 2))
Also not entirely sure if x > y: was your intention or if you really wanted if x < y:
You don't need to specify x and y in subtraction or addition:
class calculator:
def __init__(self, x, y):
self.x = x
self.y = y
def addition(self):
return self.x + self.y
def subtraction(self):
if self.x > self.y:
return self.y - self.x
else:
return self.x - self.y
self will cover retrieving x and y for you, since you are referencing those instance variables. Otherwise, you will need to specify them on call:
# Yields 2-1 rather than 4-3
result = calculator(1,2).subtraction(3,4)
You do, however, need to specify them when instantiating your class
myinst = calculator(1,2)
first = calculator makes first refer to the class. But first = calculator(1, 2) makes first an object of the class calculator. This initializes self for all other functions called on first. This also sets the values for self.x and self.y because __init__ is called as soon as object is created. Hence, when first = calculator(x, y) is used, self has a value along with self.x and self.y, and when parentheses are not used, they do not have a value. That gives a missing argument error.
Next, as others have already referred, once __init__ is called, x and y are initialized for the object. And so, you don't need them while declaring other functions. self.x and self.y can directly be used there which will now refer to 1 and 2 respectively in this case.
Hope it helps.

How can i use a function inside of another function

I am currently playing around with classes and functions since i am not familiar with python and i would like to know how i can get addy(self, addx) to call addx.
class test:
def __init__(self, x):
self.x = x
def addx(self):
y = self.x + 10
return y
def addy(self, addx):
z = addx() + 10
return z
one = test(1)
print(one.addy())
line 15, in print(one.addy()) TypeError: addy() missing 1
required positional argument: 'addx' Process finished with exit code 1
You need to call self from within a class method.
self.addx()
Also the addx parameter on this line shouldn't be there:
def addy(self, addx):
I think this is what you are going for:
class test:
def __init__(self, x):
self.x = x
def addx(self):
y = self.x + 10
return y
def addy(self):
z = self.addx() + 10
return z
one = test(1)
print(one.addy())
You've overcomplicated things by wrapping it in a class. Take it out and it'll work (mostly) the way you expect.
def add10(x):
return x+10
def add20(x):
return add10(add10(x))
Since you've wrapped it in the class you've complicated the namespace. It's no longer called addx or addy, so using those names throws a NameError. You have to use the qualified name instead.
class FooBar():
def __init__(self):
self.x = 10
def addx(self):
return self.x + 10 # Note the `self.` before the attribute...
def addy(self):
return self.addx() + 10 # ...and also before the method name.
Methods are always passed their owning object as a first argument when called, which is why we've got def addx(self): but then call with self.addx()
If you are attempting to relate addx in the signature of addy to the method addx, you can pass the string name of the method and use getattr:
class Test:
def __init__(self, x):
self.x = x
def addx(self):
y = self.x + 10
return y
def addy(self, func):
z = getattr(self, func)() + 10
return z
s = Test(3)
print(s.addy('addx'))

Which is the better way to inheritance?

Is there any difference between this:
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
def __init__(self, x, y):
Vehicle.__init__(self, x, y)
class Scooter(Vehicle):
def __init__(self, x, y):
Vehicle.__init__(self, x, y)
and this:
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
pass
class Scooter(Vehicle):
pass
Because without def __init__ in child classes I got the same thing, I mean __init__ doesn't provide any effect.
You should't do either of them. The best way to do it is using super.
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
def __init__(self, x, y):
super(Car, self).__init__(x, y)
# super().__init__(x, y) # for python3
Check this blog post by Raymond Hettinger (core python contributor) on why you should be using super
You need __init__ method when you want to do child specific initialisation. Assume your child classes require another argument to be passed to the constructor and is unique to that class, in that case __init__ method is really required.
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
def __init__(self, x, y, z):
Vehicle.__init__(self, x, y)
self.z = z
class Scooter(Vehicle):
def __init__(self, x, y, z):
Vehicle.__init__(self, x, y)
self.z = z
I think this would be best explained with an example.
Now take this scenario:
>Vehicle ---> Car,Bike,Boat,Aeroplane,Train
>[All are vehicles right]
>Things they have in common would be (say for ex.) **Price** and **Color**
However things they won't have in common would be?
>**Wheels**. The total number of wheels may differ.
>
> Car-4 Bike-2 Boat-0 Aeroplane-(**Not sure**) Train-(**Many I
guess**?)
But you get the point right? So When I want to just have a Vehicle object I don't want (or I can't tell the number of wheels) In that case I can initialize only with just price and color
However when I know the specific type of Vehicle say Car now I can __init__ it with number of wheels. Now this is where object specific initializations play a major role.
A full example code of the above sample:
class Vehicle():
def __init__(self, x, y):
self.color = y
self.price = x
def Horn(self):
print("Pommm...Pommmmm!!")
class Car(Vehicle):
def __init__(self, x, y,wheel):
Vehicle.__init__(self, x, y)
self.wheel = "Four Wheels man: 4"
class Scooter(Vehicle):
def __init__(self, x, y,wheel):
Vehicle.__init__(self, x, y)
self.wheel = "Just Two man : 2"
VehObj = Vehicle("5000$","Black")
VehObj.Horn()
print(VehObj.color,VehObj.price)
#However note this
carObj = Car("5000$","Black",4)
print(carObj.color,carObj.price,carObj.wheel)
#Look at this
sObj = Scooter("5000$","Black",2)
print(sObj.color,sObj.price,sObj.wheel)
Output:
Pommm...Pommmmm!!
Black 5000$
Black 5000$ Four Wheels man: 4
Black 5000$ Just Two man : 2
Hope that cleared you up.
If you don't provide an __init__ method in the child classes, they will just use the __init__ method defined in their parent class (aka inheritance). In the former case, you are overriding the __init__ method for the child classes but you are simply calling the __init__ method of the parent class. So if you don't do that (like the later case) it will be the same. The later case automatically inherits the __init__ method.
Other ways to write the same thing would be:
class Car(Vehicle): #This is the best way to do it though
def __init__(self, x, y):
super()__init__(x, y)
Or
class Car(Vehicle):
def __init__(self, x, y):
self.x = x
self.y = y
TLDR; They are equivalent.
Calling the init method of super class is optional if you don't wont to edit the __init__ method of superclass.
but if you want to edit the superclass method you need custom init
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
def __init__(self, x, y, z):
Vehicle.__init__(self, x, y)
self.z = z

python local variables vs self

What's the difference between self.x+self.y and x+y in the code below?
class m2:
x, y = 4, 5
def add(self, x, y):
return self.x + self.y
def add2(self, x, y):
return x + y
>>> x = m2()
>>> print "x.add(self.x + self.y = )", x.add(1, 2)
x.add(self.x + self.y = ) 9
>>> print "x.add2(x + y = )", x.add2(1, 2)
x.add2(x + y = ) 3
Why does self.x + self.y return 9 vs x + y returns 3?
In add you are calling the class variables and ignoring the method arguments x and y.
class m2:
# these variables are class variables and are accessed via self.x and self.y
x, y = 4, 5
def add(self, x, y):
return self.x + self.y # refers to 4 and 5
def add2(self, x, y):
return x + y # refers to input arguments x and y, in your case 1 and 2
When defining x and y in the class scope it makes them class variables. They are part of the the class m2 and you don't even need to create an instance of m2 to access them.
print m2.x, m2.y
>> 4, 5
However, you are also able to access them via an instance as if they were instance variables like this:
m = m2()
print m.x, m.y
>> 4, 5
The reason behind this is that the interpreter will look for instance variables with names self.x and self.y, and if they are not found it will default to class variables.
Read more about class attributes in the python documentation.
The difference is that when you use self you refer to the member of the instance of your class
When you use X and Y dirrectly you refer to the parameter that you use in your function
This is a simplification of your class
class m2:
x_member1, y_member2 = 4, 5
def add(self, x_parameter1, y_parameter2 ):
return self.x_member1+ self.y_member2
def add2(self, x_parameter1, y_parameter2 ):
return x_parameter1 + y_parameter2
When a class method is called, the first argument (named self by convention) is set to the class instance. When the method accesses attributes of self, it is accessing those attributes in the class instance, and their values persist in that instance.
On the other hand, if a class method accesses bare variables, those variables are strictly local to those methods and their values do not persist across calls to class methods of that instance.
class m2:
x, y = 4, 5 #This are class attributes
def add(self, x, y ):
return self.x + self.y # This are instance variables
def add2(self, x, y ):
return x + y # This are local variables
Class variables are common to each instance of the class. Instance variables are only avaible to that instance. And local variables are only avaible in the scope of the function.
In add, when you do self.x it's refering to the class variable x cause it's also part of the instance. In add2 it's refering to local variables
The same results could be achieved if those methods were class methods or static methods (With proper adjustments)
Class method:
class m2:
x, y = 4, 5
#classmethod
def add(cls, x, y):
return cls.c + cls.y #Here you're calling class attributes
#classmethod
def add2(cls, x, y):
return x + y
Results:
>>> m.add(1,2)
9
>>> m.add2(1,2)
3
Static method:
class m2:
x, y = 4, 5
#staticmethod
def add(x, y):
return m2.c + m2.y #Here you need to call the class attributes through the class name
#staticmethod
def add2(x, y):
return x + y
Results:
>>> m2.add(1,2)
9
>>> m2.add2(1,2)
3
x and y will be local by default. The self.x and self.y are persisted in that instance, x and y will only be there locally.
class Dog():
def __init__(self):
x = "local"
self.y = "instance"
d = Dog()
print(d.y)
#=> instance
print(d.x)
#=> AttributeError: Dog instance has no attribute 'y'

How to use property()

I'm having trouble on how to implement property to protect attributes.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def set_x(self, x):
if '_x' in dir(self):
raise NotImplementedError("Cannot change x coordinate")
else:
self._x = x
def get_x(self):
return self._x
#I beleive my mistake is here. I'm not sure if I'm implementing this correctly
x = property(get_x, set_x, None, None)
So I want to prevent any user from changing the x-coordinate. My question is, how do I get python to redirect the user to the set_x() and get_x() methods? I've tried running this code in terminal and whenever I apply the following, the point changes.
p = point(3, 4)
p.x = 5 #x is now 5
You only need this much:
class Point:
def __init__(self, x, y):
self._x = x
self.y = y
def get_x(self):
return self._x
x = property(get_x)
You can set the hidden field self._x in your init, then you don't need a setter for x at all. And have get_x return self._x rather than self.x so it doesn't try and call itself.
You can use the #property decorator to do this even more succinctly.
class Point:
def __init__(self, x, y):
self._x = x
self.y = y
#property
def x(self):
return self._x
The following code works on both python2.x and python3.x:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def set_x(self, x):
if '_x' in dir(self):
raise NotImplementedError("Cannot change x coordinate")
else:
self._x = x
def get_x(self):
return self._x
x = property(get_x, set_x, None, None)
p = Point(2, 3)
print(p.x) # 2
p.x = 6 # NotImplementedError
Pretty much all I did was inherit from object (to get it to work on python2.x) and use the name Point rather than point (which would have been a NameError before).
There are other things you can do to clean it up a bit (e.g. khelwood's suggestion of just writing the getter -- or DSM's suggestion of using hasattr instead of '_x' in dir(self)).
Note, if you really just want a type that takes an x and y arguments that you want to be immutable -- Maybe you should consider using a colledctions.namedtuple
from collections import namedtuple
Point = namedtuple('Point', 'x,y')
p = Point(2, 3)
p.x # 2
p.y # 3
p.x = 6 # AttributeError: can't set attribute

Categories

Resources