[go: up one dir, main page]

0% found this document useful (0 votes)
2 views30 pages

Scientific Python 2025-02-25

The document covers Object-Oriented Programming (OOP) in Python, emphasizing concepts like classes, instances, inheritance, and operator overloading. It also introduces lambda functions for creating anonymous functions and discusses parallel processing tools in Python, including forking and threading. Finally, it outlines classwork assignments related to string validation, polygon classes, and parallel task processing.

Uploaded by

Maiky Khorani
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)
2 views30 pages

Scientific Python 2025-02-25

The document covers Object-Oriented Programming (OOP) in Python, emphasizing concepts like classes, instances, inheritance, and operator overloading. It also introduces lambda functions for creating anonymous functions and discusses parallel processing tools in Python, including forking and threading. Finally, it outlines classwork assignments related to string validation, polygon classes, and parallel task processing.

Uploaded by

Maiky Khorani
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/ 30

Lesson 5.

OOP, Function extras, Lambda,


Parallel system tools
M. MAIKEY ZAKI BIA
Object Oriented
Programming
● In Python, OOP is entirely optional
● Strategic mode - OOP (doing long-term product
development)
● Tactical mode (where time is in very short supply)
● Inheritance
○ “inherit” properties from the general category
○ common properties need to be implemented only once
● Composition
○ Modular programming - build up your programs as
composition of components
OOP
● Classes are Python program units
● Classes define new namespaces
● Classes have three critical distinctions, when it comes to
building new objects:
○ Multiple instances
■ Every time we call a class, we generate a new
object with a distinct namespace.
○ Customization via inheritance
■ We can extend a class by redefining its attributes
outside
○ Operator overloading
■ Operators can be redifined, as well
OOP
Classes and Instances

● Classes: Serve as instance factories. Their attributes provide


behavior — data and functions — that is inherited by all the
instances generated from them

● Instances: Represent the concrete items in a program’s


domain. Their attributes record data that varies per specific
object
OOP
Class tree

● Two supeclasses at
the top (C2 and C3)

● A (sub)class (C1) at
the middle

● Two instances (I1


and I2) at the
bottom
OOP
Method Calls

● Methods are functions attached to classes as attributes


● If “I2.w” reference is a function call, that means “call the C3.w
function to process I2”
● Whenever we call a function attached to a class in this
fashion, an instance of the class is always implied:
○ I2.w()
○ C3.w(I2)
OOP
Coding Class Trees

● Each class statement generates a new class object


● Each time a class is called, it generates a new instance
object
● Instances are automatically linked to the classes from which
they are created
● Classes are automatically linked to their superclasses
according to the way we list them in parentheses in a class
header line; the left-to-right order there gives the order in the
tree
OOP
An example

class C2: ... # Make superclass objects


class C3: ...
class C1(C2, C3): # Make and link class C1
def setname(self, who): # Assign name: C1.setname
self.name = who # Self is either I1 or I2

I1 = C1() # Make two instances


I2 = C1()
I1.setname('bob') # Sets I1.name to 'bob'
I2.setname('sue') # Sets I2.name to 'sue'
print(I1.name) # Prints 'bob'
OOP
Operator/function Overloading (through an example)

class C2: ... # Make superclass objects


class C3: ...

class C1(C2, C3):


def __init__(self, who): # Set name when constructed
self.name = who # Self is either I1 or I2

I1 = C1('bob') # Sets I1.name to 'bob'


I2 = C1('sue') # Sets I2.name to 'sue'
print(I1.name) # Prints 'bob'
OOP
Polymorphism and classes

class Employee: # General superclass


def computeSalary(self): ... # Default behaviors
def giveRaise(self): ...
def promote(self): ...
def retire(self): ...

class Engineer(Employee): # Specialized subclass


def computeSalary(self): ... # Something custom here

bob = Employee() # Default behavior


sue = Employee() # Default behavior
tom = Engineer() # Custom salary calculator
Anonymous Functions:
lambda
● Creates a function to be called later, but it returns the
function instead of assigning it to a name
● Syntax:
lambda argument1, argument2,... argumentN
: expression using arguments
● lambda is an expression, not a statement:
○ Can appear in places where a def is not allowed
○ Functions can be referenced by name but must be
created elsewhere
● lambda’s body is a single expression, not a block of
statements
○ lambda is designed for coding simple functions
Anonymous Functions:
lambda
Syntax

>>> def func(x, y, z):


return x + y + z
>>> func(2, 3, 4)
9

>>> f = lambda x, y, z: x + y + z
>>> f(2, 3, 4)
9

Default values

>>> x = (lambda a="fee", b="fie", c="foe": a + b + c)


>>> x("wee")
'weefiefoe'
Anonymous Functions:
lambda
Examples

L = [lambda x: x ** 2, # Inline function definition


lambda x: x ** 3,
lambda x: x ** 4] # A list of three callable functions
for f in L:
print(f(2)) # Prints 4, 8, 16
print(L[0](3)) # Prints 9

>>> key = 'got'


>>> {'already': (lambda: 2 + 2),
'got': (lambda: 2 * 4),
'one': (lambda: 2 ** 6)}[key]()
8
Parallel System Tools
● The key goal is the CPU utilization
● Parallel processing means running multiple programs, at the
“same time”
● Solution to be responsive to users regardless of the amount
of work they must perform behind the scenes
● Two fundamental ways in Python:
Process forks and Spawned threads
● Both rely on underlying operating system services to run bits
of Python code in parallel
● Procedurally, they are very different in terms of interface,
portability, and communication
Parallel System Tools
Forking
● Traditional way, a fundamental part of the Unix tool set
● When a program calls the fork routine, the operating
system makes a new copy of that program and its
process in memory and starts running that copy
● Original: parent, Copy: child
● In Python to start a new, parallel process, call the
os.fork built-in function
Parallel System Tools
● os.fork returns a different value in each copy:
○ 0 in the child process
○ the process ID of the new child in the parent
● Programs usually test the return of os.fork() to detect
a child process, to run different tasks in the child
● Forking does not work on Windows
Parallel System Tools
Example of os.fork() #1 An output

import os fork in process: 3902


started new thread: 3903
def id(): fork in process: 3903
print('i am a good child:\t', os.getpid()) i am a good child: 3903

while True: fork in process: 3902


newpid = os.fork() started new thread: 3904
print('fork in process:\t', os.getpid()) fork in process: 3904
i am a good child: 3904
if newpid == 0:
id() fork in process: 3902
os._exit(0) started new thread: 3905
else: fork in process: 3905
print('started new thread:\t', newpid) i am a good child: 3905
q
if input() == 'q':
break
Parallel System Tools
Example of os.fork() #2

import os, time

def counter(count): # run in new process


for i in range(count):
time.sleep(1) # simulate real work
print('PID:', os.getpid(), 'TASK:', i)

for i in range(5):
pid = os.fork()
if pid != 0:
print('Process', pid, 'spawned') # in parent: continue
else:
counter(5) # else in child/new process
os._exit(0) # run function and exit

print('Main process exiting.') # parent need not wait


Parallel System Tools
fork/exec Combination

● Can be used to start independently running programs


that are completely different from the parent
● There are different variants of exec functions:
○ os.execl(path, arg0, arg1, ...)
○ os.execle(path, arg0, arg1, ..., env)
○ os.execlp(file, arg0, arg1, ...)
○ os.execlpe(file, arg0, arg1, ..., env)
○ os.execv(path, args)
○ os.execve(path, args, env)
● arg0 is passed to the new program as its own name (will
seem to be ignored)
Parallel System Tools
Example of fork and exec An output

import os
parm = 0
Child is 6338
while True:
I'm child: 1
parm += 1
pid = os.fork()
if pid == 0: Child is 6339
# overlay program I'm child: 2
os.execlp('python3', 'n', 'child.py', str(parm))
# shouldn't return Child is 6340
assert False, 'error starting program' I'm child: 3

else:
Child is 6341
print('Child is', pid)
if input() == 'q': break I'm child: 4

child.py: Child is 6342


I'm child: 5
#!/usr/bin/python3 q
import sys
print("I'm child:", sys.argv[1])
Parallel System Tools
threads
● Run a call to a function (or other callable objects) in
parallel with the rest of the program
● Threas are running whitin the same single process
● Advantages:
○ Performance: less expensive than a fork
○ Simplicity: simplier to program
○ Shared global memory: communications (btw. be carful with it…)
○ Portability: also supported by Ms Window$
● Disadvantages:
○ Global Interpreter Lock (GIL): all threads must acquire a single
shared lock when they are ready to run, and each thread may be
swapped out
○ Cannot be distributed across multiple CPUs on a multi-CPU computer
Parallel System Tools
Example of using _thread #1 An output

import os, _thread


Parent PID: 8225
def child(tid):
print('Hello from thread', tid, 'PID:', Hello from thread 1 PID: 8225
os.getpid())
Hello from thread 2 PID: 8225
def parent():
print('Parent PID:', os.getpid(), '\n') Hello from thread 3 PID: 8225
i = 0
while True: Hello from thread 4 PID: 8225
i += 1
_thread.start_new_thread(child, (i,)) Hello from thread 5 PID: 8225

if input() == 'q': break Hello from thread 6 PID: 8225


q
parent()
Parallel System Tools
Example of using _thread #1 An output

import _thread as thread, time [1] => 0


[2] => 0
def counter(myId, count): [0] => 0
for i in range(count): [1] => 1
time.sleep(1) [0] => 1
print('[%s] => %s' % (myId, i)) [2] => 1
[1] => 2
for i in range(3): [0] => 2
thread.start_new_thread(counter, (i, 3)) [2] => 2
Main thread exiting.
time.sleep(6)
print('Main thread exiting.')
Parallel System Tools
threads: shared objects and names

● Objects and namespaces in a process that span the life of


threads are shared by all spawned threads
● That means: if one Python thread changes a global scope
variable, the change can be seen by every other thread in the
process, main or child
● Programmer must be careful to avoid changing global objects and
names at the same time
● To be robust, threaded programs need to control access to
shared global items
● Python’s _thread module comes with its own easy-to-use tools for
synchronizing access to objects shared among threads
Parallel System Tools
Example of using _thread and shared objects An output

import _thread as thread, time [1] => 0


[2] => 0
def counter(myId, count): [0] => 0
for i in range(count): [1] => 1
time.sleep(1) [0] => 1
mutex.acquire() [2] => 1
print('[%s] => %s' % (myId, i)) [1] => 2
mutex.release() [0] => 2
[2] => 2
mutex = thread.allocate_lock() Main thread exiting.
for i in range(5):
thread.start_new_thread(counter, (i, 5))

time.sleep(6)
print('Main thread exiting.')
Parallel System Tools
Example of using _thread and mutexes An output

import _thread as thread ID: 0 count: 0


ID: 0 count: 1
threads = 3
stdoutmutex = thread.allocate_lock() ID: 0 count: 2
exitmutexes = [thread.allocate_lock() for i in range(threads)] ID: 0 count: 3
ID: 0 count: 4
def counter(ID, count):
ID: 2 count: 0
for i in range(count):
stdoutmutex.acquire() ID: 2 count: 1
print('ID:', ID, 'count:', i) ID: 2 count: 2
stdoutmutex.release() ID: 2 count: 3
exitmutexes[ID].acquire()
ID: 2 count: 4
for i in range(threads): ID: 1 count: 0
thread.start_new_thread(counter, (i, 5)) ID: 1 count: 1
ID: 1 count: 2
for mutex in exitmutexes:
ID: 1 count: 3
while not mutex.locked(): pass
print('Main thread exiting.') ID: 1 count: 4
Main thread exiting.
Classwork
● Go to the site: BOLOGNA ASSIGNMENT

● Create a folder with your name and bologna ID’s, like:


Aya-B10….

● Create and upload your solution for the excercies

● From now, only really working solutions will be


rewarded. Submission deadline is next week from the
same day of the lecture, when the class takes place
Classwork
Implement one exercise per file. Also write some lines at
the end of the file, which will instantiate the classes, and
test the functionality.

1. Create a class, which checks string for parenthese


validity. Example: input: “()” -> True, input: “([]{[]}) ->
True, input: “[([)]]” -> False
2. Create a class “polygon” which has attributes
“distinct” and “area” and create dummy member
functions to calculate them. Create classes
“rectangle” and “circle” by customizing “polygon”
using inheritance, and override/implement calculation
methods.
Classwork
3. First, create a list filled with 10 pcs of random floats
from the interval [0.5-1.0] -- this will be our task
queue.

The task will be to sleep for so many seconds, as an


item of the list. Start 4 threads to process the ‘tasks’
parallel. Use forking or threads -- it’s up on you.

After processing the whole queue, print the sum of


the items, and the time needed to process them
parallel.
Homework
1. Try out the commands, statements learned today
2. Finish the exercises you could not resolve during the
lesson

You might also like