0% found this document useful (0 votes)
5 views50 pages

18 Oop1

The document provides an overview of Object-Oriented Programming (OOP) concepts, focusing on objects, classes, and methods in Python. It explains the differences between object-oriented and procedure-oriented programming, how to instantiate objects, and the use of constructors and attributes. Additionally, it includes practical examples such as turtle graphics and a custom 2D Point class to illustrate OOP principles.

Uploaded by

likevin1022
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views50 pages

18 Oop1

The document provides an overview of Object-Oriented Programming (OOP) concepts, focusing on objects, classes, and methods in Python. It explains the differences between object-oriented and procedure-oriented programming, how to instantiate objects, and the use of constructors and attributes. Additionally, it includes practical examples such as turtle graphics and a custom 2D Point class to illustrate OOP principles.

Uploaded by

likevin1022
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 50

Object-Oriented

Programming
Part 1
Prof. Pai H. Chou
National Tsing Hua University
Outline
• Objects
• Object-oriented vs. procedure-oriented
• Instantiation: copying vs. class
• Example with Turtle Graphics
• Class definition
• Constructor, methods
• instance attributes vs. class attributes
What is an Object?
• Informally,
• A general term for "things" in a program
• anything that an identifier can reference
• More formally,
• a unit of data that can "respond to messages"
• in other words, bundled data and program code!
Objects in Python
• Values of built-in data types
• 13, "hello", 22.5, ['a', 'b', 'c'], {'x', 'y', 'z'}, ...

• Why? because you can "send message" to them


• "send message" in Python is to call a method on an object
• e.g., ['a', 'b', 'c'].index('b') returns 1
=> .index('b') "sends a message" to the list object
=> "responds" by returning 1

• in Python, types (classes) are also objects!


• you can compare if type(L) == str
(str is the name of a class!)
Two ways of making an object
• by copying
• shallow copy vs. deep copy
• by instantiating from a class
• send a "construct" message to a class
• the class responds by instantiating an object
• a constructor is a special method that initializes
the newly created object
Copying object
• import copy
x = copy.copy(y) # shallow copy
x = copy.deepcopy(y) # deep copy
• Useful for mutable objects
• immutable objects (int, float, str) don't need to be
copied, because their values don't change!!!
• the source object is the "prototype"
• behaves like the original, but different identity
Class-based object instantiation
• Class: definition, "blueprint"
• member data and code (member function) that
operates on data
• Instance: object created according to class
term meaning
class definition for data (attributes) and code (methods)
instance an object created according to a class definition
instantiation creation of an instance (based on a class)
method a function defined in a class to operate on its data
Concept of data type
• In Python, a type is called a class
• Python provides built-in types
• simple types: int, float, complex
• str, list, tuple, set, dict, bytes
• Additional types can be defined
• defined by modules
• defined by users
Constructor call looks like
function call!
• Constructor name = name of the type
• e.g., s = set((1, 2, 3))
• looks like function call, but calls set constructor
=> creates an instance of set class, initialized
based on parameter (1, 2, 3) => {1, 2, 3}

• similarly, L = list('hello')
• calls constructor => ['h','e','l','l','o']
Procedure-Oriented vs.
Object-Oriented Programming
• Procedure- • Object-oriented
oriented: • Send messages to
• Call procedures (i.e., objects
functions) by passing • Python: invoke
objects as parameters methods
• e.g., • e.g.,
L = [2, 5, 1, 7] L = [2, 5, 1, 7]
M = sorted(L) L.sort()
sorted() is .sort() is
a function a method
Reasons for Object Oriented
• Higher-level abstraction
• "abstract data type" with some meaning (e.g., date,
time, window, button, ...)
• keep code organized
• list of methods = things you can do to the object
• Decouple implementation from interface
• allow multiple instances
• Instances do not interfere with each other
Example: turtle graphics
• classic way of drawing graphics using very
simple commands
• import turtle # import module
• t1 = turtle.Turtle() # instantiate Turtle
• t2 = turtle.Turtle() # instantiate another

• allow drawing on a 2-D canvas in Cartesian


coordinate
• can up/down, hide/show turtle, goto, backward,
forward, setheading, left, right, pencolor, dot
Methods of turtle graphics
purpose API
show/hide turtle t.showturtle() t.hideturtle()
pen up or down t.up() t.down()
turtle move by distance t.forward(dist) t.backward(d)
turtle turning #degrees t.left(deg) t.right(deg)
set turtle to coordinate & dir t.goto(x,y) t.setheading(deg)
get turtle coordinate & dir t.pos() t.heading()
set pen or fill color t.pencolor(c) t.fillcolor(c)
draw circle of diameter and color t.dot(dia,c)
bracket for filling polygon t.begin_fill() t.end_fill()
undo, clear what this turtle drew t.undo() t.clear()
print text t.write(s, font, align)
Example Turtle Graphics
>>> import turtle
>>> t1 = turtle.Turtle()
>>> t1.up()
>>> t1.goto(-100, 0); t1.down()
>>> t1.fillcolor('blue')
>>> t2 = turtle.Turtle()
>>> t2.up()
>>> t2.goto(100, 0); t2.down()
>>> t1.begin_fill()
>>> t2.fillcolor('red')
>>> t2.begin_fill()
>>> for i in range(3):
... t1.forward(50)
... t1.left(120)
... t2.backward(70)
... t2.right(120)
...
>>> t2.end_fill()
>>> t1.end_fill()
Defining your own class
• Purposes
• higher-level concepts
• separate representation from access format
• impose constraints on allowable values
• What is needed
• constructor, possibly with parameters
• methods and attributes
Example: 2D Point class
• Multiple ways to represent a point in 2D
• (x, y) in Cartesian coordinates, or complex #
• (r, θ) in polar coordinates
y
(x, y) = (4, 3) (r, θ) = (5, 36.9°)
3

x
4
Point as an Abstract data type
• (x, y) is just one way of representing a
point
• internally, it could also use (r, θ) => this should be
of no concern to the user!
• users just want a way to access it in the most
convenient way => a method could do
conversion
• Define a set of methods for Point
Example constructor for Point
class
• Constructor is called upon instantiation
• special name __init__(self, args...) within class
class Point:
def __init__(self, x, y): # two underscores before and after
self.x = x # save param x as attribute of self
self.y = y # save param y as attribute of self

• called as the ClassName(args) for instantiation


>>> p = Point(2, 3) # instantiate a Point with (2, 3)

• p = Point(2, 3)
Python calls Point.__init__(p, 2, 3) to initialize p
• formal param self refers to the instance being initialized (p)
Attributes of an object
• Also called "fields", "member data"
• Think of as variables local to object
class Point:
def __init__(self, x, y): # two underscores before and after
self.x = x
self.y = y

p = Point(2, 3)

• Constructor creates two attributes self.x and self.y


based on parameters x and y
• instance p can access attributes as p.x, p.y
Derived attribute
• Those computable from other attributes
(r, θ) = (5, 36.9°)
• e.g., given (x, y) can calculate (r, θ) 5
(4, 3)

• define r and theta as methods


import math
class Point:
def __init__(self, x, y):
self.x = x
>>> p = Point(4, 3)
self.y = y
>>> p.r()
def r(self):
5.0
return math.sqrt(self.x**2 \
>>> p.theta()
+ self.y**2)
36.86989764584402
# = math.hypot(self.x, self.y)
def theta(self):
return math.atan(self.y / self.x)\
* 180 / math.pi
Object literal
• When typing in interactive mode,
>>> p = Point(2, 3) # instantiate a Point with (2, 3)
>>> p
<__main__.Point object at 0x10f5c5400>

• not very helpful...


• Would be nice if it could show a "literal"!
• Solution: __repr__ special method
__repr__ special method
• called by shell to get string to display
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point({self.x}, {self.y})'
• it can be any string that is meaningful
• most likely, looks like the constructor call!!
• So now Python shell can display it
>>> p = Point(2, 3) # instantiate a Point with (2, 3)
>>> p
Point(2, 3)
>>>
Function operating on points
• example: move a point by dx, dy
>>> def proc_move_by(p, dx, dy):
... p.x += dx
... p.y += dy
...
>>> q = Point(2, 3)
>>> proc_move_by(q, 1, -2)
>>> q
Point(3, 1)
• formal parameter p references the same Point
object as q does
Method: member function
• Method is invoked on an instance
!• Python requires the first parameter of
method to be self
method (inside class)
class Point:
def __init__(self, x, y):
function (outside class) self.x = x
self.y = y
def proc_move_by(p, dx, dy): def move_by(self, dx, dy):
p.x += dx self.x += dx
p.y += dy self.y += dy

>>> q = Point(2, 3) >>> q = Point(2, 3)


>>> proc_move_by(q, 1, -2) >>> q.move_by(1, -2)
>>> q >>> q
Point(3, 1) Point(3, 1)
Methods for Point class
• Mutation
• move_to(x, y) # absolute coordinate
• move_by(dx, dy) # relative displacement
• Derived attributes
• radius()
• area_as_circle() # if treated as radius
• area_as_rectangle # if treated as width and height
implementation for Point
methods
import math • All methods have self
class Point:
def __init__(self, x, y): ... as first argument
def __repr__(self): ...
def r(self): ... • methods can access all
def theta(self): ...
attributes of its own class
def move_to(self, x, y): (self's or another
self.x = x
self.y = y instance's)
def move_by(self, dx, dy):
self.x += dx • methods can call other
self.y += dy
radius = r
methods
def area_of_circle(self):
return math.pi * self.r() **2 • radius = r declares
def area_of_rectangle(self):
return self.x * self.y
radius to be an alias for
r
@property for derived attributes
import math
class Point:
def __init__(self, x, y): ... • Want to just name
def __repr__(self): ... derived attributes without
@property
def r(self): ... the calling () syntax
@property
def theta(self): ... • e.g., want to say p.radius
instead of p.radius()
def move_to(self, x, y):
self.x = x • Solution: use @property
self.y = y
def move_by(self, dx, dy): decorator in front of
self.x += dx
self.y += dy
those methods
radius = r
@property
def area_of_circle(self): >>> q = Point(4, 3)
return math.pi * self.r **2 • >>> q.r
@property 5.0
def area_of_rectangle(self):
return self.x * self.y
Advantages of @property
decoration
• uniform syntax like regular attributes
• object.attr , instead of () without passing arg
• can make attribute read-only access!
• otherwise, other code can make data inconsistent
>>> q = Point(4, 3)
>>> q.radius
5.0
>>> q.radius = 20
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Name spaces of class and
instance
• Class
• contains its own symbol table
• Look at Point.__dict__ or dir(Point)
• class can also have its own attributes
• Instance
• each instance has own symbol table, separate from
class
• constructor enters symbols into the instance's name
space by self.x = value
Example name spaces of class
and instance
>>> p = Point(2, 3)
>>> p.move_by
<bound method Point.move_by of <__main__.Point object at
0x10a862ac8>>
>>> dir(Point)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',...
'__subclasshook__', '__weakref__', 'move_by', ...]
>>> p.__dict__
{'x': 2, 'y': 3}
>>> p.__class__
<class '__main__.Point'>
>>> p.w = 'hello' # entered into the instance's namespace
>>> Point.z = 'abc' # entered into the class's namespace
>>> p.z # instance can look into class's namespace
'abc'
>>> p.__dict__ # even though 'z' is not in instance's space
{'x': 2, 'y': 3}
Example use of Class Attributes
• Suppose we want to assign a unique serial
number to each point that we create
• need to keep a "global variable", but want it to be
associated with the class
• solution: class attribute
class Point: >>> p = Point(2, 3)
count = 0 # class attribute >>> q = Point(4, 5)
def __init__(self, x, y): >>> p.serial # p has serial no.0
self.x = x 0
self.y = y >>> q.serial # q has serial no.1
self.serial = Point.count 1
Point.count += 1 >>>
Class-specific Constraints on
Value
• A class may limit allowed values of attributes
• example: ADT for date and time
• possible attributes: year, month, day, hour, minute, second
• day-of-week may be a derived attribute!
• day-of-week is limited to Sun, Mon, ... Sat
• month is limited to 1-12
• day is limited to 1-28, 29, 30, or 31, depending on the year or
month!!
• Q: How to enforce such constraints?
Ans: by methods
Attribute getting vs. setting
• "get" = read an attribute's value
• e.g., print(dt.hour) # read dt's hour attribute
• "set" = assign a new value to an attribute
• e.g., dt.hour = 7 # set hour to 7 o'clock
• Q: How to ensure setting attribute is valid?
• A: Use "setter" method, and then decorate
using @property
Example class: DateTime
• Suppose we want a constructor
• >>> dt = DateTime(2019, 6, 30, 5, 20, 32)
to construct the date and time for
June 30, 2019 at 5:20:32 am
• is this a valid date and time? if not, need
to raise an exception
• >>> dt = DateTime(2019, 6, 31, 5, 20, 32)
ValueError: day 31 out of range
Example: constructor for
DateTime
• Original, unchecked • Replace with "setter"
methods
class DateTime: class DateTime:
def __init__(self,year,month, \ def __init__(self,year,month,\
day, hour, minute, second): day, hour, minute, second):
self._year = year self.set_year(year)
self._month = month self.set_month(month)
self._day = day self.set_day(day)
self._hour = hour self.set_hour(hour)
self._minute = minute self.set_minute(minute)
self._second = second self.set_second(second)

• use _ to protect
• setter method checks
attribute from outside before actually setting
access
How to write setter methods
def check_range(field_name, field_value, L, U):
if not (L <= field_value <= U):
raise ValueError(f'{field_name} must be {L}..{U}')

class DateTime:
def __init__(self, year, month, day, hour, minute, second):
...
def set_year(self, year):
if type(year) != int: raise TypeError('year must be int')
self._year = year
def set_month(self, month):
check_range('month', month, 1, 12)
self._month = month
def set_day(self, day):
if self._month in {1,3,5,7,8,10,12}:check_range('day',day,1,31)
elif self._month in {4, 6, 9, 11}: check_range('day',day,1,30)
elif leap(self._year): check_range('day',day,1,29)
else: check_range('day',day,1,28)
self._day = day
def set_hour(self, hour):
check_range('hour', hour, 0, 23)
self._hour = hour
Boundary cases
• set_day() checks month and year => okay
• set_month() and set_year() are incomplete
• suppose month=3, day=31, then set_month(4)
=> April does not have 31 days, should raise ValueError
due to existing day setting!
• similarly, month=2, day=29, leap year, then
set_year(2001) non-leap year => cannot have 29 days!
Should also raise ValueError due to 2/29
• These extra checks need to be added to set_month() and
set_year() access methods
Attribute access through
__dict__
• obj.attr • obj.__dict__['attr']
• dt._month • dt.__dict__['_month']

• dt.__dict__['_'+'month']
>>> dt = DateTime(2019, 6, 30, 5, 20, 32)
>>> dt._month
6
>>> dt.__dict__['_month']
6
>>> dt.__dict__['_month'] = 12
>>> dt._month
12

Q: Why access attribute through __dict__?


A: Because you can compute the key (attribute name)!
e.g., change from 'month' to '_month'
Conversion to a helper setter
class DateTime:
...
def check_and_set(self, field_name, field_value, L, U):
if not (L <= field_value <= U):
raise ValueError(f'{field_name} must be {L}..{U}')
self.__dict__['_'+field_name] = field_value
def set_year(self, year):
if type(year) != int: raise TypeError('year must be int')
self._year = year
def set_month(self, month):
self.check_and_set('month', month, 1, 12)
def set_day(self, day):
if self._month in {1, 3, 5, 7, 8, 10, 12}:
self.check_and_set('day', day, 1, 31)
elif self._month in {4, 6, 9, 11}:
self.check_and_set('day', day, 1, 30)
elif leap(self._year):
self.check_and_set('day', day, 1, 29)
else:
self.check_and_set('day', day, 1, 28)
def set_hour(self, hour):
self.check_and_set('hour', hour, 0, 23)
Example: getter and setter
class DateTime:
def __init__(self, year, month, day, hour, minute, second):
...
def get_month(self):
return self._month
def set_month(self, month):
self.check_and_set('month', month, 1, 12)

• call getter/setter method to make changes


>>> dt = DateTime(2019, 6, 30, 5, 20, 32)
>>> dt.get_month()
5
>>> dt.set_month(8)
>>> dt.get_month()
8
>>> dt.set_month(13)
ValueError: day 31 out of range
How to package getter/setter as
attribute access?
• getter method • attribute syntax
>>> dt.get_month() >>> dt.month
5 5

• setter method
>>> dt.set_month(8) >>> dt.month = 8

• Solution: property
class DateTime:
...
def get_month(self):
...
def set_month(self, month):
...
month = property(lambda self: self.get_month(), \
lambda self, v: self.set_month(v))
instance method, class method,
and static method
• instance method (default)
• implicit self as first parameter

• class method (@classmethod)


• implicit cls as first parameter

• static method (@staticmethod)


• no implicit first parameter; just like any other
function but just scoped inside class
Class methods
• a method that is invoked on the class
rather than on the instance
• first argument is the class (cls), rather than the
instance (self)
• Why? same reason as class attributes
• most likely, want a method to protect access to a
class attribute
Example: DateTime year range
• Previously, no limit on year, but may want
user to set their own
• e.g., limit valid year from -5000 to +4000
• want getter/setter to view/set new range
class DateTime:
year_from = -5000
year_to = +4000
def __init__(self, year, month, day, hour, minute, second):
...
def set_year(self, year):
self.check_and_set('year', year, \
self.year_from, self.year_to) # class attr
def set_year_range(self, lower, upper):
! self.year_from, self.year_to = lower, upper this won't
work!
Solution: class method
• @classmethod >>> dt = DateTime(2019,6,30,5,20,32)
>>> dt.year_from
-5000
• cls instead of self >>> dt.set_year_range(-8000, +6000)
>>> dt.year_from
-8000
• can call it from either >>> DateTime.year_from
instance or class! -8000
>>> DateTime.set_year_range(-100,3000)
>>> dt.year_to
class DateTime: 3000
year_from = -5000
year_to = +4000
...
def set_year(self, year):
self.check_and_set('year', year, \
self.year_from, self.year_to) # class attr
@classmethod
def set_year_range(cls, lower, upper):
cls.year_from, cls.year_to = lower, upper
Static methods
• a method really just a function that is
scoped in the class
• there is no implicit argument(cls or self), unlike
class method or instance method
• Why? it is just a function, not a method
• most likely, want to provide some functions that
are logically associated with the class but is
independent of the instance or the class
Example: leap function in
DateTime class
• leap(year) is leap-year test
• What is the best way to structure it?
(1) top-level function? (2) instance method (3)
class method (4) static method?
class DateTime:
def __init__(self, year, month, day, hour, minute, second):
...
def set_day(self, day):
if self._month in {1, 3, 5, 7, 8, 10, 12}:
self.check_and_set('day', day, 1, 31) looks like a top-
elif self._month in {4, 6, 9, 11}:
self.check_and_set('day', day, 1, 30)
level function, but
elif leap(self._year): could be considered
self.check_and_set('day', day, 1,2 9)
else:
for a static method
self.check_and_set('day', day, 1, 28)
Rewrite leap as static method in
DateTime class
• can call leap() from either class or instance
class DateTime:
def __init__(self, year, month, day, hour, minute, second):
...
@staticmethod
def leap(year): # note: no self, no cls!!!
return (year % 400 == 0) or \
((year % 4) == 0) and (year % 100 != 0)
def set_day(self, day):
if self._month in {1, 3, 5, 7, 8, 10, 12}:
self.check_and_set('day', day, 1, 31)
elif self._month in {4, 6, 9, 11}: >>> dt = DateTime(...)
self.check_and_set('day', day, 1, 30) >>> dt.leap(2000)
elif self.leap(self._year): True
self.check_and_set('day', day, 1,2 9) >>> dt.leap(2001)
else: False
self.check_and_set('day', day, 1, 28) >>> DateTime.leap(2000)
True
>>> DateTime.leap(2001)
False
Example: leap function in four
possible places
defined as calling form Pro Con
def leap(year): leap(year) concise, no need name
defined
return ... for qualified name pollution
as class DateTime:
class DateTime: self.leap(year) defined and called self is passed
instance def leap(self,y): like any other but not used
method ... method
class DateTime: self.leap(y) defined similar to cls is passed
class @classmethod method but not used
method def leap(cls,y):
...
class DateTime: self.leap(y) no name pollution,
static @staticmethod does not pass cls or
method def leap(y): self unnecessarily
return ..
Summary of Part 1 OOP
• Object-oriented programming
• Write code to defined data+code bundle
• Send message to object; one way is method call
• Ways of creating objects
• Duplication of prototype vs. instantiation of class
• Class-based OOP
• Constructor, method definition, attribute
• Value enforcement through get/set, property syntax

You might also like