0% found this document useful (0 votes)
11 views

Classes and OOP

Uploaded by

Khalil Hafiz
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views

Classes and OOP

Uploaded by

Khalil Hafiz
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 10

1.

Defining classes
A class in Python is effectively a data type. All the data types built into Python are classes. You
define a class with the class statement.

# the below code will give an error, it's just for explanation
class MyClass:
body

By convention, class identifiers are in CapCase—that is, the first letter of each component word
is capitalized. After you define the class, you can create a new object of the class type (an
instance of the class) by calling the class name as a function:
instance = MyClass()

1.1 Using a class instance as a structure or record


Class instances can be used as structures or records.
The data fields of an instance don’t need to be declared ahead of time.

The following short example defines a class called Circle, creates a Circle instance, assigns a
value to the radius field of the circle, and then uses that field to calculate the circumference of
the circle:

>>> class Circle:


pass

>>> my_circle = Circle()


>>> my_circle.radius = 5
>>> print(2 * 3.14 * my_circle.radius)
31.4

The fields of an instance are accessed and assigned to by using dot notation.

You can initialize fields of an instance automatically by including an __init__ initialization


method in the class body. This function is run every time an instance of the class is created,
with that new instance as its first argument, self .

The __init__ method is similar to a constructor in Java, but it doesn’t really construct anything;
it initializes fields of the class. Also unlike those in Java and C++, Python classes may only
have one __init__ method. This example creates circles with a radius of 1 by default:
1. By convention, self is always the name of the first argument of __init__ . self is set to the
newly created circle instance when __init__ is run
2. Next, the code uses the class definition. You first create a Circle instance object
3. The next line makes use of the fact that the radius field is already initialized.
4. You can also overwrite the radius field;
5. as a result, the last line prints a different result from the previous print statement

2. Instance variables
Instance variables are the most basic OOP feature. Looking at the Circle class defined above,
radius is an instance variable of Circle instances.

Each instance of the Circle class has its own copy of radius, the value stored in its copy might
be different from the values stored in the radius variable in other instances.

In Python, instance variables can be created by assigning to a field of a class instance:


instance.variable = value e.g. my_circle.radius = 8.5 . If the variable doesn't already exist,
it's created automatically.

2.1 Exercise: Create a Rectangle class with the


appropriate instance variables
3. Methods
A method is a function associated with a particular class.

Methods are called with the dot notation (like with variables), except that we add parentheses at
the end and pass whatever arguments the function requires e.g. instance.method() ,
instance.method2(3, 4) .
We are going to add an area method to our Circle class:

>>> > class Circle:


... def __init__(self):
... self.radius = 1
... def area(self):
... return self.radius * self.radius * 3.14159
...

>>> c = Circle()
>>> c.radius = 3
>>> print(c.area())
28.27431

The above way of invoking methods instance.methodname() is known as bound method


invocation. The same result can be obtained with the following code: Circle.area(c)

>>> print(Circle.area(c))
28.27431

This syntax is known as unbound method invocation. The method is invoked with the class
name ( Circle ) dot the method name area and the first argument is the instance c . This
syntax is not commonly used.

Like functions, methods can be invoked with arguments if the method definition accepts them.
We are going to add an argument to the __init__ method so that we can initialize our circles
with a particular radius.

class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return self.radius * self.radius * 3.14159

NB: self.radius is the instance variable while radius is a parameter to the __init__
function. In Python OOP, if a variable is not prefixed with the self keyword, that usually means
it refers to a local variable in the method and not an instance variable.

Using the above class, we can create circles of any radius with one call on the Circle class
e.g. c = Circle(5) creates a circle with radius 5.

All standard Python function features - default argument values, extra arguments ( *args ),
keyword arguments ( **kwargs ) and so forth - can be used with methods.
The first line of the __init__ could have been defined as:
def __init__(self, radius=1)

Then calls to circle would work with or without an extra argument; Circle() would return a
circle of radius 1, and Circle(3) would return a circle of radius 3.

3.1 Exercise:
Update the code for the Rectangle class so that you can set the dimensions when an instance
is created, just as for the Circle class above. Also, add an area() and perimeter() method.

4. Class variables
A class variable is a variable associated with a class, not an instance of a class, and is
accessible by all instances of the class.

A class variable might be used to keep track of some class-level information, such as how many
instances of the class have been created at any point.

A class variable is created by an assignment in the class body, not in the __init__ function.
After it has been created, it can be seen by all instances of the class.

We can use a class variable to make a value for pi accessible to all instances of the Circle
class:

class Circle:
pi = 3.14159
def __init__(self, radius):
self.radius = radius

def area(self):
return self.radius * self.radius * Circle.pi

With the definition entered, you can type:

>>> Circle.pi
3.14159
>>> Circle.pi = 4
>>> Circle.pi
4
>>> Circle.pi = 3.14159
>>> Circle.pi
3.14159
Class variables can also be accessed through instance methods in any of the following ways:

Using the class's name like in the area method defined above
Using the __class__ attribute of the instance i.e. the return statement in the area method
above can be replaced with: return self.radius * self.radius * self.__class__.pi .
This avoids hardcoding the class name.
Using the instance (self). The return statement can also be return self.radius *
self.radius * self.pi . This method has some drawbacks that I didn't think necessary to
include here.

5. Static methods and class methods


Python classes can also have methods that correspond explicitly to static methods in a
language such as Java. In addition, Python has class methods, which are a bit more advanced.

5.1 Static methods


Static methods can be invoked even though no instance of that class has been created (they
can also be called using a class instance). To create a static method, use the @staticmethod
decorator.

Type the below code in a file if you haven't been typing it yet:

"""circle module: contains the Circle class."""


class Circle:
"""Circle class"""
all_circles = []
pi = 3.14159

def __init__(self, r=1):


"""Create a Circle with the given radius"""
self.radius = r
# add the newly initialized instance to the class's list of all circles
self.__class__.all_circles.append(self)

def area(self):
"""determine the area of the Circle"""
return self.__class__.pi * self.radius * self.radius

@staticmethod
def total_area():
"""Static method to total the areas of all Circles """
total = 0
for c in Circle.all_circles:
total = total + c.area()

return total

Now interactively type the following:

Notice the use of documentation strings (docstrings). This help to explain the use of modules,
classes or even functions. We can access the docstring of the circle module with the __doc__
special attribute e.g. circle.__doc__

>>> circle.__doc__
'circle module: contains the Circle class.'
>>> circle.Circle.__doc__
'Circle class'
>>> circle.Circle.area.__doc__
'determine the area of the Circle'

5.2 Class methods


Class methods, like static methods, can be invoked before an object has been instantiated.

Class methods are (implicitly) passed the class they belong to as their first parameter, below
shows how the total_area from above will look as a class method

Create a new file, circle_cm.py and paste the code from the above circle module into it
Replace the total_area function definition in the circle_cm.py with this one:
By using a class method instead of a static method, you don't have to hardcode the class name
into total_area .

5.3 Exercise
Write a class method similar to total_area() that returns the total circumference of all circles

6 Inheritance
Let's take the case of a program that allows us to draw shapes (circles, rectangles, squares,
etc.) on particular positions on the screen.

To be able to do this, we need to store the position (x and y coordinates) of each instance of the
shape. We can do this by adding two new instance variables to our classes: x and y.
But this approach is tedious and redundant if we have to do it for many other shape classes.

So instead we create a super class called Shape that will initialize these position attributes and
each shape will inherit from this superclass.

class Shape:
def __init__(self, x, y)
self.x = x
self.y = y

class Square(Shape): # square inherits from shape


def __init__(self, side=1, x=0, y=0): # default values for x and y
super().__init__(x, y) # call the __init__ method of Shape (parent
class)
self.side = side

class Circle(Shape):
def __init__(self, r=1, x=0, y=0):
super().__init__(x, y)
self.radius = r

There are (generally) two requirements in using an inherited class in Python.

The first requirement is specifying the parent classes, in parentheses, immediately after
the name of the class being defined.
The second and more subtle element is the necessity to explicitly call the __init__
method of inherited classes. Otherwise, in the example, instances of Circle and Square
wouldn’t have their x and y instance variables set.
Inheritance also comes into effect when you attempt to use a method defined in the parent
class (superclass).
To see this, define another method in the Shape class called move which moves a shape by a
given displacement. It modifies the x and y coordinates of the shape.
Add the following method definition to the Shape class:

After defining this method, we can use it in the child classes as follows:

>>> c = Circle(1)
>>> c.move(3, 4)
>>> c.x
3
>>> c.y
4

7. Private variables and private methods


A private variable or private method is one that can't be seen outside the methods of the class
in which it's defined.

They are useful because:

They enhance security and reliability by selectively denying access to parts of an object's
implementation
They prevent name clashes that can arise from the use of inheritance.

In python, any method or instance variable whose name begins - but doesn't end - with a
double underscore ( __ ) is private, everything else isn't private.

Consider the following class:

class Mine:
def __init__(self):
self.x = 2
self.__y = 3
def print_y(self):
print(self.__y)

Using this definition, create an instance of the class:


>>> m = Mine()

x isn’t a private variable, so it’s directly accessible:

>>> print(m.x)
2

__y is a private variable. Trying to access it directly raises an error:

>>> print(m.__y)
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError: 'Mine' object has no attribute '__y'

The print_y method isn’t private, and because it’s in the Mine class, it can access __y and print
it:

>>> m.print_y()
3

8. Using @property for more flexible instance


variables
The property allows us to implement getters and setters for our classes. But unlike traditional
getters and setters, the property methods are accessed more like attributes than as methods.

To create a property, you use the property decorator with a method that has the property's
name:

class Temperature:
def __init__(self):
self._temp_fahr = 0

@property
def temp(self):
return (self._temp_fahr - 32) * 5 / 9
Without a setter, the above property is read-only, trying to assign a value to it raises an
AttributeError:

>>> t = Temperature()
>>> t.temp
-17.77777777777778
>>> t.temp = 3
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[13], line 1
----> 1 t.temp = 3
AttributeError: property 'temp' of 'Temperature' object has no setter
In [14]:

To make the property writable, you need to add a setter to the class:

@temp.setter
def temp(self, new_temp):
self._temp_fahr = new_temp * 9 / 5 + 32

Now you can use standard dot notation to both get and set the property temp.

Notice that the name of the method remains the same, but the decorator changes to the
property name (temp, in this case), plus .setter indicates that a setter for the temp property is
being defined:

>>> t = Temperature()
>>> t._temp_fahr
0
>>> t.temp
-17.77777777777778
>>> t.temp = 34
>>> t._temp_fahr
93.2
>>> t.temp
34.0

You might also like