The Python Class – Syntax and Semantics

The “man walks into a bar” example illustrates many aspects of the Python class structure, but obviously omits many details, especially formal ones that can be so important to a programmer. A class looks like a function in that there is a keyword, class, and a name and a colon, followed by and indented region of code. Everything in that indented region belongs to the class, and cannot be used from outside without using the class name or the name of a variable that is an instance of the class.

The method _ init__ is used to initialize any variables that belong to the class. It is what we called a constructor above. Any variables that belong to the class must be accessed through either an instance (from outside of the class) or by using the name self (from within the class). self.name refers to a variable that was defined inside of the class, whereas simply using name refers to a variable local to a method. When __init__ is called, a set of parameters can be passed and used to initialize variables in the class. If the first parameter is self, it means that the method can access class-local variables, otherwise it cannot. Normally, self is passed to__ init__ or it cannot initialize things. Any variable initialized within__ init__ and prefixed by self is a class-local variable. Any method that is passed self as a parameter can define a new class-local variable, but it makes sense to initialize all of them in one place it that’s possible.

A simple example of a class, initialization, and a method is as follows:

class person:

def init (self, name): self.name = name

def introduce (self):

print (“Hi, my name is “, self.name)

me = person(“Jim”)

me.introduce()

This class has two methods, __init__() and introduce(). After the class is defined, a variable named me is defined and is given a new instance of the person class having the name “Jim.” Then this variable is used to access the introduce method, which prints the introduction message “Hi, my name is Jim.” A second instance could be created and assigned to a second variable named you using

you = person (“Mike”)

and the method call

you.introduce()

would result in the message “Hi, my name is Mike.” Any number of instances can be created, and some many have the same name as others – they are still distinct instances.

A new class-local variable can be created by any method. In introduce(), for example, a new local named introductions can be created simply by assigning a value to it.

def introduce (self):

print (“Hi, my name is “, self.name)

self.introductions = True

This variable is True if the method introductions has been called. The main program can access this variable directly. If the main program becomes

me = person(“Jim”)

me.introduce()

print (me.introductions)

then the program will generate the output

Hi, my name is Jim

True

This is the essential information needed to define and use a class in Python.

1. Really Simple Class

A common example of a basic class is a point, a place on a plane specified by x and y coordinate. The beginning of this class is

class point:

def init (self, x, y):

self.x = x

self.y = y

This simply represents the data associated with a mathematical point. What more does it need? Well, two points have a distance between them. A distance method could be added to the point:

def distance (self, p):

d = (self.x-p.x)*(self.x-p.x) + (self.y-p.y)* (self.y-p.y)

return sqrt(d)

If a traditional function were to be used to compute distance, it would be written similarly but not identically. It would take two points as parameters:

def distance (p1, p2):

d = (p1.x-p2.x)*(p1.x-p2.x)  + (p1.y-p2.y)*  (p1.y-p2.y)

return sqrt(d)

The distance method uses one of the points as a preferred parameter, in a sense. The distance between points p1 and p2 would be calculated as

d = p1.distance(p2) or d = p2.distance(p1)

using the distance method, but as

d = distance (p1, p2)

if the function was used. To a degree, the difference is a philosophical one. Is distance some property that a point has from another point (the method), or is it something that is a thing that is calculated for two things (the function). After a while, it is possible to see the methods and data of a class as belonging to the object, and as somehow being properties of it. That’s what makes a class a type definition.

Many object-oriented languages offer the concept of accessor methods. Some languages do not allow variables that belong to a class to be used directly, or allow specific controls on access to them. The truth is that having the ability to find the value of variables and to modify them is generally a bad idea. If the only place that a class local variable can be modified is within the class then that limits the places where that can occur, and allows more control over what is possible. Preventing errors in programs is partly a matter of restricting actions to a small region and of knowing exactly what is going on at all times.

Similarly, if some object outside of a class has access to the local variables of that class, then it promotes a dependency on a specific implementation. One of the advantages of an object-oriented implementation is that the interface to the class is fixed and independent of the way that class is implemented. It may seem obvious that a point object has an x, y position and that those would be real num­bers, but the point class is the simplest class, and taking advantage of how a class is coded it not always beneficial.

All that an accessor method does is return a value of important to a user of a class. The x and y positions are variables local to the class, and many would agree that they should have an accessor method:

def getx (self):

return self.x

def gety (self):

return self.y

Rewriting the distance() method to use accessor methods changes it only slightly:

def distance (self, p):

d = (self.x-p.getx())*(self.x-p.getx()) + (self.y-p.gety())* (self.y-p.gety())

return sqrt(d)

Methods called mutators or setters are used to modify the value of a variable in a class. They may do more than that, such as checking ranges and types, and tracking modifications.

def setx (self, x):

self.x = x

def sety (self, y):

self.y = y

There are other methods that could be added to even this simple class just in case they were needed, such as to draw the point, to return a string that describes the object, to rotate about the origin or some other point, or to call a destructor method when the object is no longer needed. Until it is known what the class will be used for, there may not be any value for this effort, but if a class is being provided for general utility, like the Python string, as much functionality would be provided as the programmer’s imagination could invent. A draw method could simply print the coordinates, and could be useful for debugging:

def draw (self):

print (“(“, self.x, “,”, self.y, “)   “)

Using this class involves creating instances and using the provided methods, and that should be all. A triangle consists of three points. A triangle class could be defined as follows:

class triangle:

def init (self, p0, p1, p2):

self.vO = p0

self.vl = pi

self.v2 = p2

self.x = (p0.getx()+pi.getx()+p2.getx())/3 self.y = (p0.gety()+pi.gety()+p2.gety())/3

def set vertices (self, p0, pi, p2):

self.vO = p0

self.vi = pi

self.v2 = p2

def get vertices (self):

return ( (self.v0, self.v1, self.v2) )

def getx (self):

return self.x

def gety (self):

return self.y

The (x, y) value of a triangle is its center, or the average value of the x and the y coordinates of the vertices. These are the basic methods. A triangle is likely to be drawn somehow, and the next chapter will explain how to do that. However, without knowing the details, a triangle is a set of lines drawn between the vertices and so might be done that way. As it is, using text only, it will print its vertices:

def draw (self):

print (“Triangle:”)

self.v0.draw()

self.v1.draw()

self.v2.draw()

The triangle can be moved to a new position. A change in the x and y loca­tion specifies the change, and it is done by changing the coordinates of each of the vertices:

def move (self, dx, dy)

coord = p0.getx()

p0.setx(coord+dx)

coord = p0.gety()

p0.sety(coord+dy)

coord = p1.getx()

p1.setx(coord+dx)

coord = p0.gety()

p1.sety(coord+dy)

coord = p2.getx()

p2.setx(coord+dx)

coord = p2.gety()

p2.sety(coord+dy)

self.x  = self.x + dx

self.y  = self.y + dy

In this way of expressing things, it is clear that moving the triangle is a matter of changing the coordinates of the vertices. If each point had a move() method, then it would be clearer: moving a triangle is a matter of moving each of the vertices:

def move (self, dx, dy):

p0.move(dx, dy)

p1.move(dx, dy)

p2.move(dx, dy)

self.x = self.x + dx

self.y = self.y + dy

Which of these two move() methods seems the best description of what is happening? The more complex are the classes, the more value there is in making an effort to design them to effectively communicate their behaviors and to make things easier to expand and modify. It is also plain that the move() method for a point is simpler than that for a triangle. That fact is invisible from outside the class, and it is not relevant.

2. Encapsulation

In the example of the point class, there is no need for an accessor method because the variables can be accessed from outside the class, in spite of the argu­ments that have been given for more controlled use of these variables. A careful programmer would want to ensure the integrity of classes by forcing the variables to remain protected in some way, and Python allows this while not requiring it.

The variables x and y are accessible and modifiable from outside because of how they are named. Any variable name in a class that begins with an underscore character (‘_’) cannot be modified by code that does not belong to the class. Such a variable is said to be protected. A variable name that begins with two under­score characters cannot be modified or even examined from outside of the class, and is said to be private. All other variables are public. This applies to method names too, so the method      __init__() that is the usually constructor is private.

Rewriting the point class to make the internal variables private would be done like this:

class point:

def init (self, x, y):

self. x = x

self. y = y

def getx (self):

return self. x

def gety (self):

return self. y

def setx (self, x):

self. x = x

def sety (self, y):

self. yy = y

def distance (self, p):

d = (self. x-p.getx())*(self. x-p.getx()) +

(self. y-p.gety())* (self. y-p.gety())

return sqrt(d)

def move(self, dx, dy):

self.  x = self. x + dx

self.  y = self. y + dy

def draw (self):

print (“(“, self.__ x, “,”, self.__ y, “)  “)

Now the internal variables x and y cannot be modified or even have their values examined unless explicitly allowed by a method.

 

Source: Parker James R. (2021), Python: An Introduction to Programming, Mercury Learning and Information; Second edition.

Leave a Reply

Your email address will not be published. Required fields are marked *