12. System Programming in Python
The goal of this seminar is to practice in using Python modules that manage facilities provided by the operation system.
Using the os and sys modules to run programs from Python scripts.
Read documentation on the os.path.realpath(path), os.system(command), sys.executable, and sys.argv methods.
- Study the Python program below.
15-57_dorun.py:
15-58_torun.py
- Run the program with python3 and see the result:
python3 15-57_dorun.py 15-58_torun.py qwe ert ert
- Run the program and pass it an invalid program to run. See the output:
python3 15-57_dorun.py nothing_torun.py qwe ert ert
Task 01: Modify the 15-57_dorun.py program so that it checks whether the program to run exists before trying to run it.
Use the os.path.exists method to check whether the program exists.
If the file does not exist, print the "No filename" message to sys.stderr.
Save the modified code to the 16-12_dochkrun.py file.
Task 02: Study documentation on the subprocess module.
Rewrite the 16-12_dochkrun.py file to run the program using the subprocess.run function.
Save the resulting program to the 16-21_dosubrun.py file.
Using pipes to replace shell pipelines.
The following shell command: output=$(dmesg | grep sda) can be implemented in Python as follows:
- Execute shell and Python and see the results.
Task 03: On the basis of the previous example, write a Python program that pipelines two commands specified in the command line. Both commands can have any number of arguments.
Save the program to the 16-43_pipecmd.py file.
Hint: You need to separate one program with arguments from another program with a special separator character. For example, it can be @.
- The command line can look like this:
andrewt@comp-core-i7-3615qm-0dbf32 ~ $ python3 16-43_pipecmd.py date -u @ hexdump -C 00000000 d0 92 d1 81 20 d0 bc d0 b0 d1 8f 20 33 31 20 32 |.... ...... 31 2| 00000010 31 3a 31 33 3a 30 38 20 55 54 43 20 32 30 32 30 |1:13:08 UTC 2020| 00000020 0a |.| 00000021
Using the multiprocessing module to run processes in Python.
The example below runs a process using the Process class.
16-50_multiex.py:
1 from multiprocessing import Process 2 import os 3 4 def info(title): 5 print(title) 6 print('module name:', __name__) 7 print('parent process:', os.getppid()) 8 print('process id:', os.getpid()) 9 10 def f(name): 11 info('function f') 12 print('hello', name) 13 14 if __name__ == '__main__': 15 info('main line') 16 p = Process(target=f, args=('bob',)) 17 p.start() 18 p.join()
Note info() here is just a function printing some system info
Task 04: Modify the 16-50_multiex.py program. Pass the f function running in another process the additional argument wait, which specifies a delay in seconds. It must be a random value from the range [1..5]. In the f function, print the wait argument together with name. Then delay execution for the number of seconds specified in wait.
Use the time.sleep method to delay execution.
Use the random.randrange to generate a random value.
Save the resulting program in the 16-58_multiex2.py file.
Exchange objects between processes using pipes.
- Here is an example:
1 from multiprocessing import Process, Pipe 2 3 def f(conn): 4 conn.send([42, None, 'hello']) 5 conn.close() 6 7 if __name__ == '__main__': 8 parent_conn, child_conn = Pipe() 9 p = Process(target=f, args=(child_conn,)) 10 p.start() 11 print(parent_conn.recv()) # prints "[42, None, 'hello']" 12 p.join()
Note «Pipe» here looks more like socket. It's bidirectional!
- Here is an example:
Task 05: Modify the 16-58_multiex2.py program to
call f() with the only f(conn) argument
send name from parent to child via Pipe
send the f"Hello, {name}!" message from the child to the parent process. Print it in the parent process.
Save the resulting program to the 17-07_multiexchat.py file.
Task 06: Modify the 16-58_multiex2.py program to run multiple processes and wait for them to stop. Use any value (e. g. current number of the process) instead of name argument.
- Number of processes must be specified as a command line argument (default value is 5).
Save the resulting program to the 17-11_multiexmany.py file.
Hint:
First, all the Process()-es, storing them in a list
Next, start() all the processes from the list
An then, join() all the processes
See the running processes with ps:
andrewt@comp-core-i7-3615qm-0dbf32 ~ $ python3 17-11_multiexmany.py & [1] 2844 0 4 4 3 1 2 3 2 2 2 andrewt@comp-core-i7-3615qm-0dbf32 ~ $ andrewt@comp-core-i7-3615qm-0dbf32 ~ $ ps -ef | grep python andrewt 2844 2515 0 01:53 pts/0 00:00:00 python3 17-11_multiexmany.py andrewt 2845 2844 0 01:53 pts/0 00:00:00 python3 17-11_multiexmany.py andrewt 2846 2844 0 01:53 pts/0 00:00:00 python3 17-11_multiexmany.py andrewt 2847 2844 0 01:53 pts/0 00:00:00 python3 17-11_multiexmany.py andrewt 2848 2844 0 01:53 pts/0 00:00:00 python3 17-11_multiexmany.py andrewt 2849 2844 0 01:53 pts/0 00:00:00 python3 17-11_multiexmany.py andrewt 2851 2515 0 01:53 pts/0 00:00:00 grep --color=auto python andrewt@comp-core-i7-3615qm-0dbf32 ~ $ hello 1 2 hello 2 2 hello 3 2 hello 4 3 hello 0 4 [1]+ Завершён python3 17-11_multiexmany.py
Using pools for parallelizing tasks. The tasks will be distributed among the specified number of worker processes.
- Here is an example:
Task 07: Modify the 16-58_multiex2.py program to run multiple tasks using a pool.
- Number of tasks must be specified as a command-line argument (default value is 5). The size of pool is fixed and equals 6.
The f() function must return the wait value multiplied by 10.
Print the aggregated result returned by the Pool.map method.
Save the resulting program to the 17-17_multiexpool.py file.
H/W
Finish all unfinished programs. Create the 12_HighLevelLanguages folder at the sugon server. Put all programs there.
Home tasks
Task 03
Task 04
Task 05
Task 06
Task 07
Bonus task
Bonus task (if done) should reside in 13_Bonus subdirectory on sugon server. Put there all the files related to your solution, including the prigram itself and 1-million digits pi file it used.
Never use float numbers if you want to achieve precision!
- Preliminaries:
Take look on Chudnovsky Pi calculation algorithm.
$$ \frac{(640320)^{3/2}}{12\pi}=\frac{426880\sqrt{10005}}{\pi} = \sum_{k=0}^{\infty} \frac{(6k)! (545140134k + 13591409)}{(3k)!(k!)^3 (-262537412640768000)^{k}} $$
Learn Python decimal data type.
How to control precision with getcontext().prec = precision (see example at the very start of documentation)
Donwload 1-million precise $$\pi$$ representation (e. g. form here)
- Remove all non-numeric characters from it
- Actually we need no more than 40000 digits for ordinary CPU
(still no bonus ☺) Write a python program PiChud.py N that iterates over Chudnovsky formula N times and prints out how many exact digits from real $$\pi$$ it has calculated:
george@inspiron:~/src> python3 PiChud.py 4 57 george@inspiron:~/src> python3 PiChud.py 40 568 george@inspiron:~/src> python3 PiChud.py 400 5674
This program calculated 75, 568 and 5674 exact $$\pi$$ digits on 4, 40 and 400 iterations of Chudnovsky formula respectively. Your results and time may differ.
Use 40000 digits Decimal precision
- Make sure you're useng only integers and Decimals and no floats
(bonus) Use miultiprocessing to speed up calculations about N times, when N is the number of cores on your computer. Call the resulting program PiChudP.py
- N can be actually lesser than number of cores. E. g. on 12-core CPU under Linux:
george@inspiron:~/src> time python3 PiChud.py 800 11347 python3 PiChud.py 800 19,94s user 0,01s system 99% cpu 19,952 total george@inspiron:~/src> time python3 PiChudP.py 800 11347 python3 PiChudP.py 800 24,00s user 0,11s system 1002% cpu 2,406 total
Parallel version took more userspace CPU time than single-process (24.00s over 19.94s)
- Parallel version worked 8 times faster than single-process (2.406s over 19.952s)
- N can be actually lesser than number of cores. E. g. on 12-core CPU under Linux:
(more bonuses). Rewrite the program (call it PiChudPA.py) to work N seconds exactly and then stop calculating. Print digits per CPU core statistic (use os.cpu_count()):
george@inspiron:~/src> python3 PiChudPA.py 60 33626 digits, 2802.17 per core
Teaser: this is my solution of bonus task № 2. Note you probably will finish with slightly lager code