Classes
Published:
This lesson covers An Informal Introduction to Python 3.10.5, https://docs.python.org/3/tutorial/introduction.html
Introduction
bundling data and functionality together
Creating new class creates new type of object - allow to create new instances
Each class instance can have:
- attributes attached to its main class
- methods, to modify its state, defined by the class
Python
- class mechanism adds classes with a minimum of new syntax and semantics
- provide all the standard features of Object Oriented Programming
- class inheritance mechanism allows multiple base classes
- derived class can override any methods of its base class or classes,
- method can call the method of a base class with the same name.
- Objects can contain arbitrary amounts and kinds of data.
- classes are created at runtime and can be modified further after creation
Normally Class members are public
Method function is declared with an explicit first argument representing the object, which is provided implicitly by the call.
A Word About Names and Objects
Python Scopes and Namespaces
Scopes and Namespaces Example
def scope_test():
def do_local():
spam = "local spam" # no chnage in scope
def do_nonlocal():
nonlocal spam # Changed binding to scope_test
spam = "nonlocal spam"
def do_global():
global spam # now module level binding
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
# "test spam"
do_nonlocal()
print("After nonlocal assignment:", spam)
# nonlocal spam
do_global()
print("After global assignment:", spam)
# global spam
scope_test()
print("In global scope:", spam) # global spam
A First Look at Classes
Class Definition Syntax
class ClassName:
<statement-1>
.
.
.
<statement-N>
Class Objects
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
# Valid Attribute References i and f
print(MyClass.i) # returns integer i
print(MyClass.f) # returns function object
# __doc__ also a valid attribute
print(MyClass.__doc__) # returns doc string
x = MyClass() # instantiation # empty object
x.i = 123
print(x.i) # 123
print(x.f()) # hello world
class MyClass:
"""A simple example class"""
def __init__(self):
self.data = []
def f(self):
return 'hello world'
# Valid Attribute References i and f
print(MyClass.f) # returns function object
# __doc__ also a valid attribute
print(MyClass.__doc__) # returns doc string
x = MyClass() # initialize data with __init__
print(x.data)
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5) # init with arguments
print(x.r, x.i)
Instance Objects
- There are two kinds of valid attribute names: data attributes and methods.
- Data attributes need not be declared; like local variables, they spring into existence when they are first assigned to
class MyClass:
"""A simple example class"""
def __init__(self):
self.data = []
def f(self):
return 'hello world'
x = MyClass()
# Data attributes need not be declared
# like local variables, they spring into existence
# when they are first assigned to.
#print(x.counter) # Error
x.counter = 1
print(x.counter)
del x.counter
#print(x.counter) # Error
Method Objects
x.f()
xf = x.f
print(xf())
Class and Instance Variables
class Dog:
# class variable shared by all instances
kind = 'canine'
def __init__(self, name):
# instance variable unique to each instance
self.name = name
d = Dog('Fido')
e = Dog('Buddy')
print(d.kind) # 'canine' # shared by all dogs
print(e.kind) # 'canine' # shared by all dogs
print(d.name) # 'Fido' # unique to d
print(e.name) # 'Buddy' # unique to e
# mistaken use of a class variable
class Dog:
tricks = []
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
# unexpectedly shared by all dogs
print(d.tricks) # ['roll over', 'play dead']
# Correct Design
class Dog:
def __init__(self, name):
self.tricks = []
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_trick("roll over")
e.add_trick("play dead")
# unexpectedly shared by all dogs
print(d.tricks) # ['roll over']
print(e.tricks) # ['play dead']
Random Remarks
- If the same attribute name occurs in both an instance and in a class, then attribute lookup prioritizes the instance
class Warehouse:
purpose = 'storage'
region = 'west'
w1 = Warehouse()
print(w1.purpose, w1.region) # storage west
w2 = Warehouse()
w2.region = 'east'
print(w2.purpose, w2.region) # storage east
w3 = Warehouse()
w3.price_per_hour = 1
print(w3.purpose, w3.region, w3.price_per_hour) # storage west 1
# Function defined outside the class
def f1(self, x, y):
return min(x, y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
# f, g, h are all attributes of class C
x = C()
print(x.g()) # hello world
print(x.h()) # hello world
print(x.f(2, 3)) # 2
# Method may call other methods using self
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
b = Bag()
b.add("C Book")
b.addtwice("Python Book")
print(b.data) # ['C Book', 'Python Book', 'Python Book']
Inheritance
# when baseclass is from same scope
class DerivedClassName(BaseClassName):
<statement-1>
...
<statement-N>
# when baseclass is from other module
class DerivedClassName(modname.BaseClassName):
<statement-1>
...
<statement-N>
Multiple Inheritance
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
...
<statement-N>
- depth-first, left-to-right
- DerivedClassName, then Base1, then Base2, then Base3
Private Variables
Odds and Ends
- Creating structured datatype as in C
# Empty class for C-like structured data type
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
print(john.name)
Iterators
# most container objects can be looped using for
for element in [1, 2, 3]: # List
print(element)
for element in (1, 2, 3): # Tuple
print(element)
for key in {'one':1, 'two':2}: # Dictionary
print(key)
for char in "123": # String
print(char)
for line in open("data.txt"): # File
print(line, end='')
s = "abc"
it = iter(s)
print(next(it)) # a
print(next(it)) # b
print(next(it)) # c
#print(next(it)) # StopIteration Exception
s = [1, 2, 3]
it = iter(s)
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
#print(next(it)) # StopIteration Exception
class Reverse:
"""Iterator for looping a sequence backwards"""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index -= 1
return self.data[self.index]
#ret char at index position
rev = Reverse("spam")
# instance of Reverse with data spam index 4
print(rev)
iter(rev) # inplace modifies rev
print(rev) # Iterator
print(next(rev))
print(next(rev))
print(next(rev))
print(next(rev))
#print(next(it))
rev = Reverse("spam")
for char in rev:
print(char, end=" ")
Generators
- Generators are a simple and powerful tool for creating iterators.
- They are written like regular functions but use the yield statement whenever they want to return data.
- Each time next() is called on it, the generator resumes where it left off (it remembers all the data values and which statement was last executed)
def reverse(data):
for index in range(len(data)-1, -1, -1):
return data[index] # return last char and loop terminates
print("Hi") # Unreachable code
rev = reverse('golf')
print(rev)
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index] # return last char and waits
#print("Hi", end=" ") # will continue here
rev = reverse('golf')
print(rev) # generator instance due to yield
print(next(rev)) # f
print(next(rev)) # Hi l
print(next(rev)) # Hi o
print(next(rev)) # Hi g
#print(next(rev))
rev = reverse('golf')
for char in rev:
print(char, end=" ")