Classes¶
A class is a programmer-defined data type. Python and its external packages contain many useful data types, but classes allow a programmer to create custom data types that are tailored to specific use-cases. Classes can contain functions, called methods as well as variables, called attributes. Classes are defined using the class
keyword.
In this lesson, we will create a simple class called circle
. Objects of this data type are intended to represent geometric circles. We will define our circle class in steps. At each step, our class will become slightly more complex.
Circle Class: Version 1¶
We will start will a very basic version that contains three attributes, r
, x
, and y
. The attribute r
is intended to store the radius of the circle, while x
and y
store the coordinates of the circle’s center point. Our class will also contain a single method, called area
. This will return the area of the circle, rounded to two decimal places.
# Version 1
class Circle:
r = 5
x = 0
y = 0
def area(self):
pi = 3.141592653589793
return round(pi * self.r**2, 2)
Notice the references to the parameter self
in the area
method. This will refer to the instance of circle
from which the area
method is called. We will say more about this in a moment.
Creating Instances of a Class¶
A class is simply a template. Defining a class does not create any instances of that type. We will now create an instance of our circle
class, storing it in a variable named c1
.
c1 = Circle()
print(type(c1))
<class '__main__.Circle'>
Notice that we can directly access the attributes of the class by following the class name with a period, and then the name of the attribute.
print('c1 radius:', c1.r)
print('c1 center: (', c1.x, ',', c1.y, ')', sep='')
c1 radius: 5
c1 center: (0,0)
We can use a similar format to call a method belonging to a class instance.
print('Area of c1:', c1.area())
Area of c1: 78.54
Notice that when we defined the area()
method, we specified that it was to accept one parameter, named self
. But when we called area, we apparently did not provide it with any arguments. In fact, we did. The class instance that the method is called from is always passed in as the first argument of the method. So, when we type c1.area()
, the circle
object c1
is plugged in for the self
parameter of area
, and then when we encounter the expression self.r
within this method, it is interpreted as c1.r
.
Every method within a class should have a self
parameter that appears first within the parameter list. And self
will always refer to the class instance from which the method was called.
Changing Attribute Values¶
Our circle c1
has the default radius of 5
. We can change this by directly changing the value of c1.r
.
c1.r = 7
print('Area of c1:', c1.area())
Area of c1: 153.94
Creating Multiple Instances¶
We can create as many instances of a class as we would like. Each such instance will be a separate object, with its own attributes and methods.
c2 = Circle()
c2.r = 3
print('c1 radius:', c1.r)
print('c2 radius:', c2.r)
c1 radius: 7
c2 radius: 3
Circle Class: Version 2¶
Notice that each of our circles starts out with a default radius of 5, and a center at (0,0). We can easily change these, but it would be helpful if we could specify the desired values when creating an instance of circle
. We will now update our circle class to allow us to do just that.
Constructors¶
A constructor is a method that is called when an instance of a class is created. It is intended to perform the initialization of the class object. In Python, constructors should always have the special name __init__()
. The constructor must accept self
as a parameter, but can also accept additional parameters to be used during initialization.
class Circle:
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
def area(self):
pi = 3.141592653589793
return pi * self.r**2
When a class object is created, the constructor is immediately called the object is passed to the constructor, along with any other arguments that were listed between the parentheses when the object was created. Below, we will create a circle with a center at (5,2), and a radius of 6.
c3 = Circle(5, 2, 6)
print('c3 radius:', c3.r)
print('c3 center: (', c3.x, ',', c3.y, ')', sep='')
c3 radius: 6
c3 center: (5,2)
This provides us with much more flexibility when creating instances of the circle
class.
Circle Class: Version 3¶
Let’s complete our circle class by adding four new methods: contains()
, intersect()
, copy()
, and __str__()
.
contains()
will check to see whether or not a provided point is inside the circle.intersect()
will check to see whether or not two circles intersect.copy()
will create and return a copy of the instance from which it was called.__str__()
is used to display information about the center and radius of the circle.
We will discuss each of these methods in more detail after providing the definition of the class.
class Circle:
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
def area(self):
pi = 3.141592653589793
return pi * self.r**2
def contains(self, x, y):
# Find distance between center and new point
dist = ( (self.x - x)**2 + (self.y - y)**2 )**0.5
# If that distance is less than r, return True. Otherwise, return False.
if dist < self.r:
return True
return False
def intersect(self, other):
# Find distance between two centers
dist = ( (self.x - other.x)**2 + (self.y - other.y)**2 )**0.5
# The circles intersect if the distance is between
# the sum and difference of the two radii.
if (dist >= abs(self.r - other.r)) and (dist <= self.r + other.r):
return True
return False
def copy(self):
new_circle = Circle(self.r, self.x, self.y)
return new_circle
def __str__(self):
out = f'Center: ({self.x}, {self.y})\n'
out += f'Radius: {self.r}'
return out
The contains() Method¶
The contains()
method accepts three parameters: self
, x
, and y
. The parameter self
is expected to be an instance of the Circle
class. The parameters x
and y
are expected to be the coordinates of a point. This method returns True
if the point provided lies inside the circle, and return False
otherwise. This is accomplished by calculating the distance between the center of the circle and the new point. The point lies inside the circle if and only if that distance is less than the radius.
Notice that the contains()
method accepts two parameters named x
and y
, and recall that each circle
object has attributes named x
and y
. Within the method, the attributes will be stored in self.x
and self.y
. These will be distinct from the values of the parameters x
and y
. These parameter values will be created in a local scope when the method is called, and will disappear when the function finishes executing. The attributes, on the other hand, will persist as part of the object itself.
Let’s now test this method.
c4 = Circle(16, 20, 4)
print(c4.contains(18, 21))
print(c4.contains(19, 24))
True
False
The intersect() Method¶
The intersect()
checks to see if two circles intersect. To accomplish this, the method first calculates the distance between the centers of the circles. If that distance is less than the sum of the circles’ radii and greater than the absolute value of the difference between the radii, then the circles intersect.
Notice that this method is required to accept two circle
objects as arguments. These are referred to as self
and other
within the function definition. The parameter self
will, as always, reference to the circle
instance from which the method was called. The object other
will be provided to the method by listing it between the parentheses.
c5 = Circle(x=0, y=0, r=4)
c6 = Circle(x=4, y=2, r=6)
c7 = Circle(x=5, y=4, r=1)
print('c5 and c6 intersect:', c5.intersect(c6))
print('c5 and c7 intersect:', c5.intersect(c7))
print('c6 and c7 intersect:', c6.intersect(c7))
c5 and c6 intersect: True
c5 and c7 intersect: False
c6 and c7 intersect: False
The copy() Method¶
As is the case with lists, when we set a variable equal to another variable containing a class instance, the new variable will refer to the currently existing instance rather than a copy of that instance. We will demonstrate this below.
c8 = c7
c8.r = 3
print('c7 radius:', c7.r)
print('c8 radius:', c8.r)
c7 radius: 3
c8 radius: 3
We can copy instances of a class by providing them with a copy()
method, as we have done with the Circle
class. This method creates and returns a new instance of the class with the same attribute values as the instance from which it was called. We will now demonstrate that this does, in fact, create a new instance of the class.
c9 = c8.copy()
c9.r = 9
print('c8 radius:', c8.r)
print('c9 radius:', c9.r)
c8 radius: 3
c9 radius: 9
The __str__()
Method¶
Let __init__()
, the __str__()
method is a special method that can belong to any class, and that performs a specific function. Generally speaking, the __str__()
method should return a string that contains some information about a particular instance of the class. This method is called whenever an instance of the class is passed to the print()
function. In that case, the string returned by the __str__()
method is what is actually displayed.
Notice that the __str__()
method for the Circle
is a string that states the center and radius of a Circle
instance. We will now call print on a Circle
object to confirm that this information is what is actually displayed.
print(c9)
Center: (3, 5)
Radius: 9
Application: Sensor Network¶
import numpy as np
import matplotlib.pyplot as plt
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-15-e0e1492b7973> in <module>
1 import numpy as np
----> 2 import matplotlib.pyplot as plt
ModuleNotFoundError: No module named 'matplotlib'
np.random.seed(1)
n = 40
x_array = np.random.uniform(0, 100, n)
y_array = np.random.uniform(0, 100, n)
r_array = np.random.uniform(10, 16, n)
plt.figure(figsize=[10,10])
ax = plt.gca()
plt.scatter(x_array, y_array, edgecolor='k', s=100)
for i in range(n):
ax.add_artist(plt.Circle((x_array[i],y_array[i]), r_array[i], color='cornflowerblue', alpha=0.2))
plt.show()
site_list = []
for i in range(n):
temp = Circle(x_array[i], y_array[i], r_array[i])
site_list.append(temp)
def count_sites(site_list, x, y):
site_count = 0
for i in range(len(site_list)):
if(site_list[i].contains(x, y)):
site_count += 1
return site_count
print(count_sites(site_list, 20, 82))
print(count_sites(site_list, 60, 50))
print(count_sites(site_list, 80, 90))