Python 3.9 - Unitesting method in class - python

Begginer level question, I am quite new and wanted to learn using unit test in my code. I've watched some tutorials how to make unittests, but when comes to practice it on your own code I start wonder how to make it properly to avoid learn bad code habits.
I have a class Geometry that will be inherited by other class, that class use imported custom object (namedtuple "Point") and list from json file with configuration but this will be provided by other part of code. Here are my questions:
Does my unittests should only check class methods create_geometry and calculate_drag_surfaces or also all mentioned above and instance creation with init method ?
When I create unitest of method that affect instance property, like create_geometry method do, how asserts should look like ? I should check value of changed instance property or there is a way to test it "in-place" without new instance creation ?
How should I make unittest for protected or hidden methods, I mean is there any difference there ?
If you will find any issues in my code I'm open to hear any suggestions I don't have any commercial experience and want to learn as much as I can. Below I presented code I want to test with unittest.
from point import Point
class Geometry:
"""
Geometry class - object that will keep geometry dependent information necessary for
graphic render and physic calculations
"""
def __init__(self, geometry_points_cords, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry_points = [] # list of named tuples with co-ords on flat surface
self.__create_geometry(geometry_points_cords)
self.x_drag_surface = None
self.y_drag_surface = None
self.__calculate_drag_surfaces()
def __create_geometry(self, geometry_points_cords):
"""
Method that will convert provided geometry points into namedtuples that describe
geometry on x/y plane
:param geometry_points_cords:
:return:
"""
for geometry_cords in geometry_points_cords:
self.geometry_points.append(Point(geometry_cords[0], geometry_cords[1]))
def __calculate_drag_surfaces(self):
"""
Method that will calculate drag surfaces in each axis base on geometry
:return:
"""
x_cords = []
y_cords = []
for single_point in self.geometry_points:
x_cords.append(single_point.x)
y_cords.append(single_point.y)
self.x_drag_surface = (max(x_cords) - min(x_cords))**2
self.y_drag_surface = (max(y_cords) - min(y_cords))**2

Is the interface the two fields x_drag_surface and y_drag_surface? Then you should primarily test that those get the proper values.
geometry = Geometry(some_coordinates)
assert geometry.x_drag_surface = correct_x_drag_surface
assert geometry.y_drag_surface = correct_y_drag_surface
As the code is written now you can not test __create_geometry and __calculate_drag_surfaces separately since they will both be run by the constructor. You can extract them from the class, though, and make them testable:
def make_points(coordinates):
"""
Method that will convert provided geometry points into namedtuples that describr geometry on x/y plane
:param geometry_points_cords:
:return:
"""
return [ Point(x, y) for (x, y) in coordinates]
def calculate_drag_surfaces(points):
"""
Method that will calculate drag surfaces in each axis base on geometry
:return:
"""
x_coords = list(map(lambda p: p.x, points))
y_coords = list(map(lambda p: p.y, points))
x_drag_surface = (max(x_coords) - min(x_coords))**2
y_drag_surface = (max(y_coords) - min(y_coords))**2
return x_drag_surface, y_drag_surface
class Geometry:
"""
Geometry class - object that will keep geometry dependent information necessary for
graphic render and physic calculations
"""
def __init__(self, coordinates, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry_points = make_points(coordinates) # list of named tuples with co-ords on flat surface
self.x_drag_surface, self.y_drag_surface = calculate_drag_surfaces(self.geometry_points)

Related

What is a clean, Pythonic way of setting multiple members in a constuctor from one method in Python?

I have a triangle class with vertices, area, and contours. What is the Pythonic way of setting multiple members in a constructor using One method, if needing to set three members in a Constructor at once?
I'm using dictionary method below, however it seems clunky. Wondering if this is appropriate or any optimal method?
This question is a variation of this question, What is a clean, Pythonic way to have multiple constructors in Python?
class TriangleData():
vertices_key: str = 'vertices'
triangle_area_key: str = 'triangle_area'
contours_key: str = 'contours'
def __init__(self, contour_data: np.ndarray):
self.contour_data = contour_data
data = self.get_triangle_data()
self.vertices: np.ndarray = data[self.vertices_key]
self.triangle_area: float = data[self.triangle_area_key]
self.poly_dp_contours: np.ndarray = data[self.contours_key]
self.color = self.get_color()
def get_triangle_data(self):
# Run calculations from original contour dataetc etc
.....
return {
self.vertices_key: vertices_final,
self.triangle_area_key: triangle_area,
self.contours_key: contours
}
This boils down to stylistic preferences. I personally don't have qualms with having some of the attributes defined outside of __init__(...), and so would opt for something like:
class TriangleData():
def __init__(self, contour_data: np.ndarray):
self.get_triangle_data(contour_data)
def get_triangle_data(self, contour_data):
# calculations
self.vertices = vertices_final
self.triangle_area = triangle_area
self.contours = contours
and disable any linter warnings that didn't like it. You could also opt for something like:
class TriangleData():
def __init__(self, contour_data: np.ndarray):
self.vertices, self.triangle_area, self.contours = self.get_triangle_data(contour_data)
def get_triangle_data(self, contour_data):
# calculations
return vertices_final, triangle_area, contours
which I think should satify the linter as-is.
There is a wide spectrum of approaches and libraries to eliminate redundancy of a plain __init__(). Please take a look at the dataclasses, attrs, NamedTuple and Pydantic.
UPD: I'm agree with Randy: there's nothing wrong about setting attributes outside of __init__.
Beside of that, you could update instance dict directly. This would be better in case when the final set of fields should be computed dynamically.
class TriangleData():
def __init__(self, contour_data: np.ndarray):
self.contour_data = contour_data
data = self.get_triangle_data()
# That's strange, that you return that field from `get` method
# with different name, than you need to bind. Could we avoid that?
# data['poly_dp_contours'] = data.pop('contorous')
self.set_triangle_data(**data)
def set_triangle_data(self, vertices, triangle, contorous):
self.vertices = vertices
self.triangle = triangle
self.contorous = contorous
# or even like below
def set_triangle_data(self, **data):
valid_keys = ['vertices', 'triangle', 'contorous', <...>]
vars(self).update({k: data[k] for k in valid_keys if k in data})
def get_triangle_data(self):
# Run calculations from original contour dataetc etc
.....
return {
self.vertices_key: vertices_final,
self.triangle_area_key: triangle_area,
self.contours_key: contours,
}
To follow the pattern of setting/initializing variables in the constructor, from Instance variables in methods outside the constructor (Python) -- why and how? (see answer from BrianOakley and others)
You can declare your object members as follows, and them set them in a method afterwards.
def __init__(self, contour_data: np.ndarray):
self.contour_data = contour_data
self.vertices: np.ndarray = np.asarray([])
self.triangle_area: float = float()
self.contour_final: np.ndarray = np.asarray([])
self.set_triangle_data()
def set_triangle_data(self):
# Run calculations from original contour data etc
.....
self.vertices = vertices_final,
self.triangle_area = triangle_area
self.contour_final = contours

Using Python Factory Idioms

Below is a pattern from :https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Factory.html . My question is this, is this still the best idiom/pattern to do generic object creation in Python 3.x? I can't seem to find much on this topic. The code is below:
class Shape(object):
# Create based on class name:
def factory(type):
#return eval(type + "()")
if type == "Circle": return Circle()
if type == "Square": return Square()
assert 0, "Bad shape creation: " + type
factory = staticmethod(factory)
class Circle(Shape):
def draw(self): print("Circle.draw")
def erase(self): print("Circle.erase")
class Square(Shape):
def draw(self): print("Square.draw")
def erase(self): print("Square.erase")
# Generate shape name strings:
def shapeNameGen(n):
types = Shape.__subclasses__()
for i in range(n):
yield random.choice(types).__name__
shapes = \
[ Shape.factory(i) for i in shapeNameGen(7)]
for shape in shapes:
shape.draw()
shape.erase()
You can also create a factory by using the __class__ method as well I've noticed, but I'm unsure of the best way to use this.
I could be missing something, but I don't like this pattern.
You already have factories for Circle and Square - Circle and Square. :)
The code in your question unnecessarily hardcodes the class names in factory and then goes through some extra hoops by getting the names of the subclasses of Shape and then calling factory with those names.
A more direct way to generate the shapes list is
types = Shape.__subclasses__()
shapes = [random.choice(types)() for _ in range(7)]
I have a situation where I could have multiple geometries being given. This is an over simplified example, but I am getting JSON response of various geometries, and instead of having to write multiple if statements multiple times, I thought using a Factory could reduce the issue: so Shape(JSON) -> Circle or Shape(JSON) - Square
This does not justify the factory as it is coded here. You could have a simple dictionary like
classes = {'Circle': Circle, 'Square': Square, ...}
or possibly create it dynamically with
classes = {cls.__name__:cls for cls in Shape.__subclasses__()}
and then call classes[some_string]() for the instantiation. You can even dynamically instantiate a class by string name using getattr.

How to create a fenced field for the drunken walk

I bought an Introduction to Python book by John V. Guttag and I am trying to teach myself python. It is going decent for the most part, but I have a question that is not talked about in the book. The latest part talked about the druken walk simulation. In the simulation it uses a field class to create the field for the drunk to walk in. He then creates another kind of field using inheritance. I was wondering what it would take to make a fenced in field that would restrict the drunk from going on the edge and then the drunk having to turn around. Here is the field code:
class Field(object):
def __init__(self):
self.drunks = {}
def addDrunk(self, drunk, loc):
if drunk in self.drunks:
raise ValueError('Duplicate drunk')
else:
self.drunks[drunk] = loc
def moveDrunk(self, drunk):
if drunk not in self.drunks:
raise ValueError('Drunk not in field')
xDist, yDist = drunk.takeStep()
currentLocation = self.drunks[drunk]
#use move method of Location to get new location
self.drunks[drunk] = currentLocation.move(xDist, yDist)
def getLoc(self, drunk):
if drunk not in self.drunks:
raise ValueError('Drunk not in field')
return self.drunks[drunk]
and here is the other field he made using inheritance:
class oddField(Field):
def __init__(self, numHoles, xRange, yRange):
Field.__init__(self)
self.wormholes = {}
for w in range(numHoles):
x = random.randint(-xRange, xRange)
y = random.randint(-yRange, yRange)
newX = random.randint(-xRange, xRange)
newY = random.randint(-yRange, yRange)
newLoc = Location(newX, newY)
self.wormholes[(x, y)] = newLoc
def moveDrunk(self, drunk):
Field.moveDrunk(self, drunk)
x = self.drunks[drunk].getX()
y = self.drunks[drunk].getY()
if (x, y) in self.wormholes:
self.drunks[drunk] = self.wormholes[(x, y)]
The odd field uses wormwholes to move the drunk which is pretty cool. I am still new to python so I am curious how this would work.
Simply override the logic of the moveDrunk() method, such that the drunk remains within the fence if the coordinates are outside of your desired Cartesian space, and also override the initialization to provide that restricted Cartesian space. Consider the following pseudocode:
class stephenDaedalus(Field):
def init(self, cartesianSpace):
Field.init(self)
self.fence = cartesianSpace
...
def moveDrunk(self):
'''Note where our drunk is located, as he may do something impossible'''
lastX = self.drunks[drunk].getX()
lastY = self.drunks[drunk].getY()
Field.moveDrunk(self, drunk)
x = self.drunks[drunk].getX()
y = self.drunks[drunk].getY()
'''check that our drunk is still within parameters'''
if (x, y) in self.cartesianSpace.points:
self.drunks[drunk] = currentLocation.move(x, y)
'''and if he is not, he will stumble back to the old manifold'''
else:
self.drunks[drunk] = currentLocation.move(lastX, lastY)
You'll have to implement a CartesianField class, but if you think about this mathematically, you'd like to accept maybe a list of points and then fill another list with the integer points within the field delimited by the list. An interesting challenge for the new programmer. Consider using Python's rectangle class to save yourself the Euclidean headache:
https://wiki.python.org/moin/PointsAndRectangles
Assuming your question is about how the method moveDrunk in the OddField subclass works.
A subclass extends its superclass (this applies to most if not all OO languages) the OddField class here extends the Field class in that it adds a property called wormholes, and also overrides the superclass' moveDrunk method meaning it gives it a new implementation.
The subclass now has all the members of its superclass but with one new property and also a more customised implementation of moveDrunk that is more relevant to the subclass.
So we know the subclass OddField can act like a normal field but it has extra behaviour that comes from the implementation of OddField. So if we call the moveDrunk method on our OddField it will implement the new behaviour instead of the behaviour in our superclass.
But, if your question is about how wormholes work, I'm sorry but i can't help you there. ;)

In Python, is the method to load objects part of the class?

I want to read a text file, manipulate the fields a bit, and load them into instance variables for an object. Each row of the text would be stored in one object, so reading the whole file should return a list of objects.
Here's an example of the file:
L26 [coords]704:271[/coords] (1500)
L23 [coords]681:241[/coords] (400)
L20 [coords]709:229[/coords] (100)
And here's part of the current class definition:
class Poi(object):
'''Points of Interest have a location, level and points'''
def __init__(self, level, coords, points):
self.level = level
self.coordinates = coords
self.points = points
I'm new to this, and probably overthinking it by a lot, but it seems like the method to read and write the list of Pois should be part of the Poi class. Is there a correct way to do that, or is the right answer to have a separate function like this one?
def load_poi_txt(source_file, source_dir):
poi_list = []
pass
return poi_list
Both are correct, depending on what you want. Here's the method skeleton:
class Poi(object):
...
#classmethod
def load_from_txt(cls, source_file, source_dir):
res = []
while (still more to find):
# find level, coords, and points
res.append(cls(level, coords, points))
return res
Note how it uses cls, which is the class the method is defined on. In this case it is Poi, but it could just as easily be a subclass of Poi defined later without needing to change the method itself.

What's the Pythonic way to initialize, set and get my custom object's attributes, by name?

I'm quite new to Python and I need to make declare my own data structure, I'm a bit confused on how to do this though. I currently have:
class Particle:
def __init__(self, mass, position, velocity, force):
self.mass = mass
self.position, self.velocity, self.force = position, velocity, force
def __getitem__(self, mass):
return self.mass
def __getitem__(self, position):
return self.position
def __getitem__(self, velocity):
return self.velocity
def __getitem__(self, force):
return self.force
This isn't working, however, when I try to define an instance of the class with:
p1 = Particle(mass, position, velocity, force)
Every value just ends up as a (0.0, 0.0) (which is the value for velocity and force).
Could someone explain where I'm going wrong, all I need from the data structure is to be able to pull the data out of it, nothing else. (edit: actually, sorry, I will have to change them a bit later on)
Thanks
First off, you should understand that __getitem__ is syntactic sugar. It's nice to have, but if you don't need it, don't use it. __getitem__ and __setitem__ are basically if you want to be able to access items from your object using bracket notation like:
p= Particle(foo)
bar = p[0]
if you don't need to this, don't worry about it.
Now, onto everything else. It looks like you've got the main characteristics you want your object to carry around in your __init__ definition, which is fine. Now you need to actually bind those values onto your object using self:
class Particle:
def __init__(self, mass, position, velocity, force):
self.mass = mass
self.position = position
self.velocity = velocity
self.force = force
That's really it. You can now access these values using dot notation, like so:
mass,pos,vel,f = 0,0,0,0 # just for readability
p = Particle(mass,pos,vel,f)
print p.mass, p.position, p.velocity, p.force
One of the nice things we get out of this is that if we ask python what p is, it will tell you that it is an instance of the Particle type, like so:
in [1]: p
out[1]: <__main__.Particle instance at 0x03E1fE68>
In theory, when you work with objects like this you want there to be a "layer of abstraction" between the user and the data such that they don't access or manipulate the data directly. To do this, you create functions (like you tried to do with __getitem__) to mediate interactions between the user and the data through class methods. This is nice, but often not necessary.
In your simpler case, to update the values of these attributes, you can just do it directly the same way we accessed them, with dot notation:
in [2]: p.mass
out[2]: 0
in [3]: p.mass = 2
in [4]: p.mass
out[4]: 2
You might have figured this out already, but there's nothing magical about the __init__ function, or even the class definition (where you would/should generally be defining most of your class's attributes and methods). Certain kinds of objects are pretty permissive about allowing you to add attributes whenever/wherever you want. This can be convenient, but it's generally very hacky and not good practice. I'm not suggesting that you do this, just showing you that it's possible.
in [5]: p.newattr ='foobar!'
in [6]: p.newattr
out[6]: 'foobar!'
Weird right? If this makes your skin crawl... well, maybe it should. But it is possible, and who am I to say what you can and can't do. So that's a taste of how classes work.
class Particle:
def __init__(self, mass, position, velocity, force):
self.mass = mass
self.position = position
self.velocity = velocity
self.force = force
particle = Particle(1, 2, 3, 4)
print(particle.mass) # 1
If you want to pretend your class has properties, you can use the #property decorator:
class Particle:
def __init__(self, mass, position, velocity, force):
self.mass = mass
self.position = position
self.velocity = velocity
self.force = force
#property
def acceleration(self):
return self.force / self.mass
particle = Particle(2, 3, 3, 8)
print(particle.acceleration) # 4.0
Seems like collections.namedtuple is what you're after:
from collections import namedtuple
Particle = namedtuple('Particle', 'mass position velocity force')
p = Particle(1, 2, 3, 4)
print p.velocity
you can just put this class definition ahead before you use it. If you want to declare it, check this site: http://www.diveintopython.net/getting_to_know_python/declaring_functions.html
By the way, your question is similar to this post: Is it possible to forward-declare a function in Python? and also this post: Is it possible to use functions before declaring their body in python?
If you just need to store some attribute values (similar to a C-language struct), you can just do:
class myContainer(object):
pass # Do nothing
myContainerObj = myContainer()
myContainerObj.storedAttrib = 5
print myContainerObj.storedAttrib
In Python 3.7+ there is the data class library. This library will allow you to create your own class to hold data quickly using a decorator, #dataclass.
The #dataclass decorator allows you to quickly define and add functionality to a class you intend to mostly be used to hold data.
A data class for your problem might be implemented as below. I've included type hints and default values which you might also find helpful.
from dataclasses import dataclass
#dataclass
class Particle:
mass: float
position: float
velocity: float = 0.0
force: float = 0.0
Here is a useful article which explains how to use data classes in Python 3.7+ and some other features.

Categories