Introduction to Objects
Classes are one of the most important parts of the Python language and also
a major component of object-oriented programming. Some books put off
classes until later chapters, but because nearly every component of Python
is an object, we will take a look at them up right away. And don’t skip
ahead, because we’ll be using them in every single chapter that follows!
Almost every component of Python is an object.
Objects hold data and have methods to access and change that data.
For example, strings, lists, tuples, sets, and dictionaries are all objects, as
are complex numbers. They all have functions associated with them called
methods that enable you to get and change that data.
Click here to view code image
list1 = [5, 20, 15, 6, 123] # create a list
x = list1.pop() # remove the last item, x =123
This is how you manipulate some common Python objects. But how do you
make your own objects?
Classes enable you to create new objects.
At first, a class might seem a bit like a function. But the important
difference is that a class can have many different instances, each containing
different data. Classes can contain a number of functions, and they each
operate on the data associated with that instance of the class. Each instance
of a class is commonly called an object , and each function is usually
referred to as a method.
Very often, you use classes to represent real-world concepts such as stores,
customers, and banks. You create objects by first defining a class that
describes that object. Instead of dealing with cute Dog classes, let’s instead
get right to work with a useful class that describes an employee. Our
Employee class here contains the employee’s name, salary, benefits, and ID
number.
Click here to view code image
class Employee():
def init(self, frname, lname, salary):
self.idnum: int #place holder
self.frname = frname #save name
self.lname = lname
self._salary = salary # and salary
self.benefits = 1000 # and benefits
def getSalary(self): # get the salary
return self._salary
Note that the values for each employee are set in the init method, which
gets called automatically when we create each instance of the Employee
class. The self prefix is used to show that you want to access the variables
inside that class instance. Other instances of the class can have different
values for those same variables. Within a class, you access all the variables
(and all the methods) using a self prefix.
The Class init Method
When you create an instance of a class, you simply create a variable and
pass in any arguments:
Click here to view code image
fred = Employee('Fred', 'Smythe', 1200)
sam = Employee('Sam', 'Snerd', 1300)
The variables fred and sam are instances of the Employee class, with specific
values for the name, salary, and so forth. We can, of course, create any
number of such instances of the Employee class, one for each person
employed.
There can be many instances of a class, each holding different values.
Each instance can also be called an object.
The functions inside a class are called methods.
Variables Inside a Class
Our Employee class contains variables for first name, last name, salary,
benefits, and an ID number. We use the getSalary method, but why not just
access it directly? In many similar languages, variables inside a class are
private or hidden, so you need an accessor method to retrieve those values.
However, Python lets you do whatever you want to do, and you really don’t
need to use getSalary or a property. Instead, you can just write
print(fred._salary)
and get the data directly. So why bother with accessor functions? It is partly
to emphasize that the variables inside the class are private and the
implementation might change, but the accessor function would remain the
same. And, in some cases, the value that the accessor function returns might
have to be calculated at that moment.
There is a Python convention that you name these private variables with a
leading underscore, to emphasize that you aren’t meant to access them
directly. This makes them harder to type accidentally. Many development
environments, such as PyCharm, don’t even suggest the existence of such
variables when you type
fred.
and look at the possible variables and methods that come up: The ones with
a leading underscore are not shown. You can still access them directly if
you insist; this is just a convention, not a syntactic requirement.
Collections of Classes
So now, let’s consider where we are going to keep all those Employee
classes. You might keep them in a database, but within a program, using
some sort of collection seems like a good idea. We’ll define an Employee
class that keeps the employees in a dictionary, each with its own ID number
as a key. It looks like this:
Click here to view code image
Contains a dictionary of Employees, keyed by ID
number
class Employees:
def init(self):
self.empDict = {} # employee dictionary
self.index = 101 # starting ID number
def addEmployee(self, emp):
emp.idnum = self.index # set its ID
self.index += 1 # go on to next ID
self.empDict.update({emp.idnum: emp}) # add
In the above Employee class, we create an empty dictionary and a starting ID
number. Every time we add an employee to the class, it increments that
index value.
We create the classes in some simple outer class called HR:
Click here to view code image
This creates a small group Employees
class HR():
def init(self):
self.empdata = Employees()
self.empdata.addEmployee(
Employee( 'Sarah' , 'Smythe' , 2000 ))
self.empdata.addEmployee(
Employee( 'Billy' , 'Bob' , 1000 ))
self.empdata.addEmployee(
Employee( 'Edward' , 'Elgar' , 2200 ))
def listEmployees(self):
dict = self.empdata.empDict
for key in dict:
empl= dict[key] # get the instance
and print it out
print (empl.frname, empl.lname,
empl.salary)
You can keep class instances inside other classes.
Inheritance
Inheritance is the other powerful tool introduced in object-oriented
programming. Not only can you create different instances of a class, as we
have seen above, but you can create derived classes. These new classes
have all the properties of the parent class plus anything additional that you
write. Note, however, that your init method must call the parent
class’s init method.
In a real company, we might have other kinds of employees as well,
including temporary workers who are paid (the same or less) but do not get
benefits. Instead of creating a completely new class, we can derive a new
TempEmployee class from the Employee class. The new class has all the same
methods, so you don’t have to write that code over again. You just have to
write the parts that are new.
Click here to view code image
Temp employees get no benefits
class TempEmployee(Employee):
def init(self, frname,lname, idnum):
super().init(frname, lname, idnum)
self.benefits = 0
Derived Classes Created with Revised Methods
One neat trick you can use in object-oriented programming is to create a
derived class in which one or more methods do something slightly different.
This is called polymorphism , which is a $50 word for changing the shape of
that new class.
For example, we can create another class from that called Intern. Interns do
not get benefits and have a low salary cap. So, we write a new derived class
whose setSalary method checks the salary to make sure it isn’t above the
cap. (Of course, we don’t really think this is a good idea economically.)
Click here to view code image
Interns get no benefits and a smaller salary
class Intern(TempEmployee):
def init(self, frname, lname, sal):
super().init(frname, lname, sal)
self.setSalary(sal) # cap salary
limit intern salary
def setSalary(self, val):
if val > 500 :
self._salary = 500
else:
self._salary = val
Multiple Inheritance
Unlike Java and C# (but like C++), Python enables you to create classes
that inherit from more than one base class. This might seem confusing at
first, but most people create a class hierarchy and then note that some of
these classes might have a method or two in common with other sorts of
classes. We will see later, in Chapter 21, “The Command Pattern,” that we
use the Command class this way from time to time.
Suppose that a few of our employees are good public speakers. We could
create a separate class indicating that they can be invited to give talks, and
can be rewarded for doing so.
Click here to view code image
class representing public speakers
class Speaker():
def inviteTalk(self):
pass
def giveTalk(self):
pass
This example leaves out the implementation details for now, but we can
then create a new Employee-derived class that is also derived from Speaker:
Click here to view code image
class PublicEmployee(Employee, Speaker):
def init(self, frname, lname, salary):
super().init(frname, lname, salary)
Now we can create a set of employees in each of these classes:
Click here to view code image
class HR():
def init(self):
self.empdata = Employees()
self.empdata.addEmployee(
Employee('Sarah', 'Smythe', 2000 ))
self.empdata.addEmployee(
PublicEmployee('Fran', 'Alien', 3000 ))
self.empdata.addEmployee(
TempEmployee('Billy', 'Bob', 1000 ))
self.empdata.addEmployee(
Intern('Arnold', 'Stang', 800 ))
def listEmployees(self):
dict = self.empdata.empDict
for key in dict:
empl= dict[key]
print (empl.frname, empl.lname,
empl.getSalary())
Note that although three of these are derived classes, they are still Employee
objects, and you can list them out as shown above.
Derived classes let you create related classes with different properties
or computations.
Drawing a Rectangle and a Square
Let’s illustrate one last example of inheritance by using the functions of the
Canvas class to draw a rectangle and a square. Canvas is one of several
visual objects in the tkinter library that we’ll deal with in the following
chapter. But, for now, let’s just use the Canvas create_rectangle method to
draw a rectangle.
The create_rectangle method has the arguments (x1, y1, x2, y2), but we
wanted to create a method that uses the arguments (x, y, w, h) so that the
conversion is buried inside the Rectangle class:
Click here to view code image
Rectangle draws on canvas
class Rectangle():
def init(self, canvas):
self.canvas = canvas # copy canvas ref
def draw(self, x, y, w, h): # draw the rect
canvas rectangle uses x1,y1, x2,y2
self.canvas.create_rectangle(x, y, x+w, y+h)
This results in Figure 1.1.
Figure 1.1 Drawing a rectangle on a Canvas
But suppose that we want to draw a square instead. We can derive a class
from Rectangle that draws squares very easily:
Click here to view code image
Square derived from Rectangle
class Square(Rectangle):
def init(self, canvas):
super().init(canvas)
def draw(self, x, y, w):
super().draw( x, y, w, w) # draw a square
Note that we simply pass the width of the square into Rectangle twice: once
as the width and once as the height.
Click here to view code image
def main():
root = Tk() # the graphics library
canvas = Canvas(root) # create a Canvas inst
rect1 = Rectangle(canvas) # and a Rectangle
rect1.draw( 30 , 10 , 120 , 80 ) # draw a rectangle
square = Square(canvas) # create a Square
square.draw( 200 , 50 , 60 ) # and draw a square
Figure 1.2 shows the result.
Figure 1.2 A rectangle and a square derived from that rectangle
Visibility of Variables
There are four levels of visibility of variables in a Python program:
Global variables (ill-advised)
Class variables inside a class
Instance variables within class code
Local variables only within a function (invisible elsewhere)
Consider the beginning of this simple program:
Click here to view code image
""" demonstrate variable access """
badidea = 2.77 # global variable
class ShowData():
localidea = 3.56 # class variable
def init(self):
self.instvar = 5.55 # instance variable
The global variable badidea can be accessed by any function in any class
and, worse, can be changed by any part of the program. People sometimes
use global variables for constants, but using a class variable is more
controlled and less prone to error.
In the previous example, localidea is a variable at the top of a class but is
not part of any method in the class. Members of the class and members of
other classes can access it by using the class name and variable name:
print(ShowData.localidea)
They can also change it, which probably is not a good practice.
Instance variables are unique to each instance of a class and are created by
prefixing self to the variable name.
Click here to view code image
def _init(self):
self._instvar = 5.55 # instance variable
By creating a variable _instvar we are signaling that this variable should
not be accessed outside the class. Various development environments will
warn you if you try to access it by
print (ShowData._instvar)
The usual way to get at these instance variables is to use getter and setter
methods:
Click here to view code image
return the instance variable
def getInstvar(self):
return self._instvar
set the value
def setInstvar(self, x):
self._instvar = x
Properties
You can also use the property decorator to fetch and store instance
variables:
Click here to view code image
getters and setters can protect
use of instance variables
@property
def instvar(self):
return self._instvar
@instvar.setter
def instvar(self, val):
self._instvar = val
These decorators enable you to access or change that instance variable
using methods that might protect the actual values if a value goes out of
range.
Click here to view code image
print(sd.instvar) # uses getter
sd.instvar = 123 # uses setter to change
Local Variables
Variables inside a function within a class exist only within that function. For
example, both x and i are local only within that simple function and cannot
be accessed outside of it:
Click here to view code image
def addnums(self):
x = 0 # i and x are local
for i in range(0, 5):
x += i
return x
Types in Python
Variables in Python are typed dynamically at runtime, rather than because
the types are declared in advance. Python deduces the type from the values
you assign to the variable, which can sometimes lead to runtime problems
when the types conflict. This approach is called duck typing, based on the
old maxim, “If it looks like a duck and quacks like a duck, it’s a duck.”
In version 3.8, Python added type hints to tell the static type checking what
to expect. Static type checking is not part of Python itself, but most
development environments, such as PyCharm, carry it out automatically
and highlight probable errors.
You can declare the type of every argument and return the type as follows:
Click here to view code image
class Summer():
def addNums(self, x: float, y: float) ->float:
return x + y
Even more impressive, you can have two or more functions that have the
same name but different arguments:
Click here to view code image
def addNums(self, f: float, s: str)->float:
fsum = f + float(s)
return fsum
And Python will call the correct function, based on the arguments, whether
you put in two floats or a float and a string:
Click here to view code image
sumr = Summer()
print(sumr.addNums(12.0, 2.3))
print(sumr.addNums(22.3, "13.5"))
then printing out:
14.3
35.8
This is called polymorphism , meaning essentially the capability to take
different forms. Here, this means that you can have several methods with
the same name but different arguments. Then you can call the method you
need based on the arguments you select. This feature is commonly used all
through Python.
However, if you make a call to addNums(str, str), you will find that
PyCharm and other type checkers flag this as an error because there is no
such method. You then get the following error message:
Unexpected types (str, str)
Summary
This chapter covered all the basics of object-oriented programming, so
here’s a summary:
You create classes by using the class keyword followed by a
capitalized class name.
Classes contain data, and each instance of a class can hold different
data. This is called encapsulation.
You can create classes derived from other classes. In parentheses after
the class name, you indicate the name of the class the new one is
derived from. This is called inheritance.
You can create a derived class whose methods are in some way
different than those of the base class. This is called polymorphism.
You can also create classes that contain other classes. We’ll see an
example in the following chapter.
Programs on GitHub
Note that you can find all the examples on GitHub at
jameswcooper/pythonpatterns.
BasicHR.py: Contains Employees without derived classes
HRclasses.py: Includes two derived classes
Speaker.py: Includes the Speaker class
Rectangle.py: Draws squares and rectangles
Addnumstype.py: Shows a polymorphic function call
In case you are unfamiliar with GitHub, it is a free software repository for
sharing code that anyone can use. To get started, go to GitHub.com and
click Sign Up. You will need to create a user ID and a password and submit
an email address for verification. Then you can search for any code
repository (such as jameswcooper) and download the code you want. There
is a complete manual available at that website as well.
After you have signed in, you can also access the examples directly at
https://github.com/jwcnmr/jameswcooper/tree/main/Pythonpatterns.
'James Cooper - Python Programming with D' 카테고리의 다른 글
The Factory Pattern (0) | 2023.03.23 |
---|---|
What Are Design Patterns? (0) | 2023.03.23 |
Visual Programming of Tables of Data (0) | 2023.03.23 |
Visual Programming in Python (0) | 2023.03.23 |
Index (0) | 2023.03.23 |
댓글