python3 - easiest way to share variable from parent to child using scripts - python-3.x

Using script parent.py I would like to set variable parvar and execute child.py and have parvar printed. I am having a hard time wrapping my head around the easiest way to accomplish this. It seems like I could use os.fork() as data present in the parent are presented to the child, but I cannot get it to work. Reading through examples of using multiprocessing I cannot find examples that show sharing data across two different scripts like this.
This is what I have so far:
parent.py
#!/usr/bin/env python3
import subprocess, os
parvar = 'parent var'
pid = os.fork()
if pid == 0:
print('child pid is running')
subprocess.call(['python3', 'child.py'])
exit()
child.py
#!/usr/bin/env python3
childvar = 'child var'
print('this is child var: ', childvar)
print(parvar)
Which returns a NameError:
$ ./parent.py
child pid is running
$ this is child var: child var
Traceback (most recent call last):
File "child.py", line 4, in <module>
print(parvar)
NameError: name 'parvar' is not defined
I think I understand why that is not working. The subprocess call replaces the existing process - spawning a new one. Because that was not forked, whatever I made available to my child PID is now inaccessible to that 3rd process.
Can someone help me with a simple example of getting the above to work?

This is not an answer (not yet at least) but figured I would post this here since it seems to move things in the right direction.
Using the mmap example from this page:
https://blog.schmichael.com/2011/05/15/sharing-python-data-between-processes-using-mmap/
I have (I believe correctly) re-factored it for Python 3 (3.6.8):
a.py
#!/usr/bin/env python3
import ctypes
import mmap
import os
import struct
def main():
# Create new empty file to back memory map on disk
fd = os.open('/tmp/mmaptest', os.O_CREAT | os.O_TRUNC | os.O_RDWR)
# Zero out the file to insure it's the right size
assert os.write(fd, b'\x00' * mmap.PAGESIZE) == mmap.PAGESIZE
# Create the mmap instace with the following params:
# fd: File descriptor which backs the mapping or -1 for anonymous mapping
# length: Must in multiples of PAGESIZE (usually 4 KB)
# flags: MAP_SHARED means other processes can share this mmap
# prot: PROT_WRITE means this process can write to this mmap
buf = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE)
# Now create an int in the memory mapping
i = ctypes.c_int.from_buffer(buf)
# Set a value
i.value = 10
# And manipulate it for kicks
i.value += 1
assert i.value == 11
# Before we create a new value, we need to find the offset of the next free
# memory address within the mmap
offset = struct.calcsize(i._type_)
# The offset should be uninitialized ('\x00')
assert buf[offset] == 0
# Now ceate a string containing 'foo' by first creating a c_char array
s_type = ctypes.c_char * len('foo')
# Now create the ctypes instance
s = s_type.from_buffer(buf, offset)
# And finally set it
s.raw = b'foo'
print('First 10 bytes of memory mapping: %r' % buf[:10])
input('Now run b.py and press ENTER')
print
print('Changing i')
i.value *= i.value
print('Changing s')
s.raw = b'bar'
new_i = input('Enter a new value for i: ')
i.value = int(new_i)
if __name__ == '__main__':
main()
b.py
#!/usr/bin/env python3
import mmap
import os
import struct
import time
def main():
# Open the file for reading
fd = os.open('/tmp/mmaptest', os.O_RDONLY)
# Memory map the file
buf = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_READ)
i = None
s = None
while 1:
new_i, = struct.unpack('i', buf[:4])
new_s, = struct.unpack('3s', buf[4:7])
if i != new_i or s != new_s:
print('i: %s => %d' % (i, new_i))
print('s: %s => %s' % (s, new_s))
print('Press Ctrl-C to exit')
i = new_i
s = new_s
time.sleep(1)
if __name__ == '__main__':
main()
Execution Example
terminal 1
$ ./a.py
First 10 bytes of memory mapping: b'\x0b\x00\x00\x00foo\x00\x00\x00'
Now run b.py and press ENTER
Changing i
Changing s
Enter a new value for i: 87
terminal 2
$ ./b.py
i: None => 11
s: None => b'foo'
Press Ctrl-C to exit
i: 11 => 121
s: b'foo' => b'bar'
Press Ctrl-C to exit
i: 121 => 87
s: b'bar' => b'bar'
Press Ctrl-C to exit

Related

Python3: how to use module 'Import click' & Parsing command line

I am a hobby radio amateur [G6SGA] not a programmer but I do try. :)
using python3. I am trying to do the following and really can't get my head around - argparse and ended up trying to use 'Import click'. Still can't get my head around so here I am. Any all (polite) :) suggestions welcome.
I wish to ---
cmd line> python3 scratch.py [no options supplied]
output> "Your defaults were used and are:9600 and '/dev/ttyAMA0' "
or
cmd line> python3 scratch.py 115200 '/dev/ttyABC123'
output> "Your input values were used and are: 115200 and '/dev/ttyAMA0'"
so a command line that will take [or NOT] argument/s. store the argument to a variable in the code for future use.
This some of what I have tried: Yes I accept it's a mess
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
# ========================
# Include standard modules
# import click
# baud default = 9600
# port default = "/dev/ttyAMA0"
import click
#click.command()
# #click.option('--baud', required = False, default = 9600, help = 'baud rate defaults to: 9600')
# #click.option('--port', required = False, default = '/dev/ttyAMA0', help = 'the port to use defaults to: /dev/ttyAMA0')
#click.option('--item', type=(str, int))
def putitem(item):
click.echo('name=%s id=%d' % item)
def communications():
""" This checks the baud rate and port to use
either the command line supplied item or items.
Or uses the default values
abaud = 9600 # default baud rate
b=abaud
aport = "/dev/ttyAMA0"
p=aport
print(f"abaud = {b} and aport = {p}")
"""
# now I wish to check if there were supplied values
# on the command line
# print(f"Baud supplied {click.option.} port supplied {port}" )
if __name__ == '__main__':
putitem() # communications()
The code I have used to work this all out is below, I hope it helps somebody. Any better ways or mistakes please advise.
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
import click
from typing import Tuple
# Command Line test string: python scratch_2.py -u bbc.co.uk aaa 9600 bbb ccc
myuri = "" # This is a placeholder for a GLOBAL variable -- take care!
list1 = [] # This is a placeholder for a GLOBAL variable -- take care!
#click.command(name="myLauncher", context_settings={"ignore_unknown_options": True})
#click.option('--uri', '-u', type=click.STRING, default=False, help ="URI for the server")
#click.argument('unprocessed_args', nargs = -1, type = click.UNPROCESSED)
def main(uri: str, unprocessed_args: Tuple[str, ...]) -> None:
# ==================== Checking the command line structure and obtaining variables
global myuri # define the use of a GLOBAL variable in this function
temp = list((str(j) for i in {unprocessed_args: Tuple} for j in i)) # sort out the command line arguments
res = len(temp)
# printing result
print("")
for e in range(0 ,res): # list each of the command line elements not including any uri
print("First check: An input line Tuple element number: " + str(e) +": " + str(temp[e])) # elements base 0
# ==================== deal with any URI supplied -- or NOT
if uri is False: #if --uri or -u is not supplied
print("No uri supplied\n")
print("The input line tuple list elements count: " + str(res))
# set a defaul GLOBAL value of myuri if it is not supplied
myuri = "https://192.168.0.90:6691/" #
else:
print("\nThe input line tuple list elements count : " + str(res) + " and we got a uri")
myuri = uri # set the GLOBAL value of myuri if the uri is
print(f"which is: {uri}, and therefore myuri also is: {myuri}") # a temp print to prove the values of the GLOBAL variable 'myuri'
# ==============================================================================================
# Testing choice of baud rate on command line
db_list = {
'4800': 'TEST48',
'9600': 'TEST96',
'19200': 'TEST19',
'38400': 'TEST38',
'57600': 'TEST57',
'115200': 'TEST11',
}
# Print databases ----- db_list ----- listed in dictionary
print("\nDatabases:")
for e in range(0 ,res) :
""" list each of the command line elements not including any uri
print("Second Check: An input line Tuple element number: " + str(e) +": " + str(temp[e]))
elements base 0 """
if str(temp[e]) in db_list.keys() :
print(f"The index of db contains {str(temp[e])}, The index refers to: {db_list[str(temp[e])]}")
if __name__ == "__main__":
# pylint: disable=no-value-for-parameter, unexpected-keyword-arg
main()

multiprocessing.Manager().dict() can't update second level value

i have a dict x ,format like: x_dic = {0:{'length':2,'current':0}},but when i use Manager().dict() to pass x_dic to child process, i found the value in 'current' can't update by child process.
method 1:
dic[i]['current'] += 1
method 2:
current_val = dic[i]['current']
current_val += 1
dic[i]['current'] = current_val
if __name__ == '__main__':
# set config of logger
print("{}:{}:{}".format(time.localtime().tm_hour,
time.localtime().tm_min, time.localtime().tm_sec))
print(os.getpid())
# set parameter
lock = multiprocessing.Lock()
pool = multiprocessing.Pool(processes=2, initializer=start_process)
# set test dic
testdic = multiprocessing.Manager().dict()
x = {0:{'length':2,'current':0}}
testdic.update(x)
# before multi
print('now value testdic',dict(testdic))
# running
partialmulti = partial(multi_core, testdic=testdic)
for i, _ in enumerate(pool.imap_unordered(partialmulti,[0,0,0])):
print('finish process: ',i)
pool.close()
pool.join()
# after multiprocessing
print('after multi',dict(testdic))
pool.terminate()
You can try with multiprocessing.Process()
import multiprocessing as mp
m=mp.Manager()
x_dict=m.dict({0:{'length':2,'current':0}})
procs=[]
no_of_processes=2
for i in range(no_of_processes):
p=mp.Process(target=func_name, args=(x_dict,)) #func_name takes one argument as x_dict and does all the manupulations as required
p.start()
procs.append(p) #Just creating a pointer to the current process and storing in a list
for proc in procs:
proc.join()
print(x_dict)
when pass a python dict to Manager().dict(), python dict as the second level won't be changed. The solution is to pass anther Manager().dict() as the second level. For example:
valuedic = multiprocessing.Manager().dict()
valuedic.update({'length':0,'current':1})
x = {0:valuedic}
testdic.update(x)
then the valuedic will be successfully changed after multiprocessing.

Defining a file descriptor in python3 - to work with pyudev/evdev

I'm currently trying to detect the connexion of a bluetooth button on a raspberry pi 3 (that part works) and once connected, detect when the button is pressed (that part doesn't work).
I've started with the code provided by evdev and tried to tweak it for my use (see hereunder), but I cannot manage to create the correct file descriptor to use with select (if I correctly understood what's happening).
import functools
import pyudev
import evdev
from select import select
context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='bluetooth')
monitor.start()
fds = {monitor.fileno(): monitor}
finalizers = []
while True:
r, w, x = select(fds, [], [])
if monitor.fileno() in r:
r.remove(monitor.fileno())
for udev in iter(functools.partial(monitor.poll, 0), None):
devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
for device in devices:
if device.name.strip() == 'AB Shutter3':
if udev.action == u'add':
print('Device added: %s' % udev)
fds[dev.fd] = device #This here breaks. dev.fd undefined.
break
if udev.action == u'remove':
print('Device removed: %s' % udev)
def helper():
global fds
fds = {monitor.fileno(): monitor}
finalizers.append(helper)
break
for fd in r:
dev = fds[fd]
for event in dev.read():
print(event)
for i in range(len(finalizers)):
finalizers.pop()()
The problem is that when I try to add the device, dev.fd is not defined. I've tried to define it, but I've got no idea how to define a file descriptor. What should I do ?
Device added: Device('/sys/devices/platform/soc/3f201000.serial/tty/ttyAMA0/hci0/hci0:64')
Traceback (most recent call last):
File "dev_status.py", line 27, in <module>
fds = {dev.fd:device} #This here breaks. dev.fd undefined.
NameError: name 'dev' is not defined
Other information : Raspberry Pi 3 running Raspbian Strech & Python 3.5.3
Also, this is my first question on Stack Overflow, so if anything's missing or could be more detailed, feel free to mention it.
Thanks,
Pôm'
OK, I've managed to find a solution. Here it is, if it can help anybody.
#!/usr/bin/env python3
import functools
import pyudev
import evdev
from select import select
context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='bluetooth')
monitor.start()
fds = {monitor.fileno(): monitor}
time = 0.0
udevices = context.list_devices(subsystem='bluetooth')
link_up = False
for udevice in udevices :
if udevice.device_type == 'link' and udevice.is_initialized :
link_up = True
print('yiha')
evdevices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
if len(evdevices) > 0:
for evdevice in evdevices:
if evdevice.name.strip() == 'AB Shutter3' and link_up:
print('Device existing: %s' % udevice)
fds[evdevice.fileno()] = evdevice
while True:
r, w, x = select(fds, [], [])
if monitor.fileno() in r:
for udevice in iter(functools.partial(monitor.poll, 0), None):
evdevices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
for evdevice in evdevices:
if evdevice.name.strip() == 'AB Shutter3':
if udevice.action == u'add':
print('Device added: %s' % udevice)
fds[evdevice.fileno()] = evdevice
break
if udevice.action == u'remove':
print('Device removed: %s' % udevice)
fds.pop(evdevice.fileno(),None)
break
if evdevice.fileno() in r:
dev = fds[evdevice.fileno()]
for event in dev.read():
if event.type == evdev.ecodes.EV_KEY:
data = evdev.categorize(event)
if data.keystate == 1 and data.event.timestamp() - time > 0.05 :
if data.scancode == 115:
print("Big button")
elif data.scancode == 28:
print("Small button")
time = data.event.timestamp()
I'm pretty sure I will look at this in horror in a few months' time, but for the moment, it does the job.

Pass an array as command line argument to the script

I'd like to experiment codes from command line, so import argv form sys.
from sys import argv
def binary_search(array, item):
low = 0
high = len(array) - 1
while low <= high:
mid = (low + high) // 2 # round down if not an even
guess = array[mid]
if guess == item:
return mid
if guess > item:
high = mid - 1
else:
low = mid + 1
return None
def main():
script, array, item = argv
binary_search(array, item)
When run it on the command line:
$ python3 binary_search.py [1, 2, 3] 8
Traceback (most recent call last): File "binary_search.py", line 94, in <module>
main() File "binary_search.py", line 89, in main
script, array, item = argvValueError: too many values to unpack (expected 3)
I tested and found that arguments passed from command line are treated as str by argv.
How can pass an array as argument?
There are a couple different ways you can do this...
using re
Using regular expressions may be one of the easiest ways of handling this.
from sys import argv
import re
def binary_search(array, item):
low = 0
high = len(array) - 1
while low <= high:
mid = (low + high) // 2 # round down if not an even
guess = array[mid]
if guess == item:
return mid
if guess > item:
high = mid - 1
else:
low = mid + 1
return None
def main():
array = re.findall(r"[\w]+", argv[1])
array = [int(i) for i in array]
item = int(argv[2])
binary_search(array,item)
if __name__== "__main__":
main()
using exec()
You can also use exec() which may be more risky and complicated. Here's a simple example:
from sys import argv
command = 'mylist = {0}'.format(argv[1])
exec(command)
for item in mylist:
print(item)
example output:
C:\path>py foo.py [1,2,3]
1
2
3
The arguments on the command line are strings, they're not parsed like literals in the program.
argv construct the strings automatically to a list from command line arguments (as separated by spaces), in short,
sys.argv is a list.
Additionally, module argparse helps
The argparse module makes it easy to write user-friendly command-line interfaces. The program defines what arguments it requires, and argparse will figure out how to parse those out of sys.argv. The argparse module also automatically generates help and usage messages and issues errors when users give the program invalid arguments.

python3 multiprocessing.Process approach fails

I saw somewhere a hint on how to process a large dataset (say lines of text) faster with the multiprocessing module, something like:
... (form batch_set = nump batches [= lists of lines to process], batch_set
is a list of lists of strings (batches))
nump = len(batch_set)
output = mp.Queue()
processes = [mp.Process(target=proc_lines, args=(i, output, batch_set[i])) for i in range(nump)]
for p in processes:
p.start()
for p in processes:
p.join()
results = sorted([output.get() for p in processes])
... (do something with the processed outputs, ex print them in order,
given that each proc_lines function returns a couple (i, out_batch))
However, when i run the code with a small number of lines/batch it works fine
[ex: './code.py -x 4:10' for nump=4 and numb=10 (lines/batch)] while after a
certain number of lines/batch is hangs [ex: './code.py -x 4:4000'] and when i
interrupt it i see a traceback hint about a _wait_for_tstate_lock and the system
threading library. It seems that the code does not reach the last shown code
line above...
I provide the code below, in case somebody needs it to answer why this is
happening and how to fix it.
#!/usr/bin/env python3
import sys
import multiprocessing as mp
def fabl(numb, nump):
'''
Form And Batch Lines: form nump[roc] groups of numb[atch] indexed lines
'<idx> my line here' with indexes from 1 to (nump x numb).
'''
ret = []
idx = 1
for _ in range(nump):
cb = []
for _ in range(numb):
cb.append('%07d my line here' % idx)
idx += 1
ret.append(cb)
return ret
def proc_lines(i, output, rows_in):
ret = []
for row in rows_in:
row = row[0:8] + 'some other stuff\n' # replacement for the post-idx part
ret.append(row)
output.put((i,ret))
return
def mp_proc(batch_set):
'given the batch, disperse it to the number of processes and ret the results'
nump = len(batch_set)
output = mp.Queue()
processes = [mp.Process(target=proc_lines, args=(i, output, batch_set[i])) for i in range(nump)]
for p in processes:
p.start()
for p in processes:
p.join()
print('waiting for procs to complete...')
results = sorted([output.get() for p in processes])
return results
def write_set(proc_batch_set, fout):
'write p[rocessed]batch_set'
for _, out_batch in proc_batch_set:
for row in out_batch:
fout.write(row)
return
def main():
args = sys.argv
if len(args) < 2:
print('''
run with args: -x [ NumProc:BatchSize ]
( ex: '-x' | '-x 4:10' (default values) | '-x 4:4000' (hangs...) )
''')
sys.exit(0)
numb = 10 # suppose we need this number of lines/batch : BatchSize
nump = 4 # number of processes to use. : NumProcs
if len(args) > 2 and ':' in args[2]: # use another np:bs
nump, numb = map(int, args[2].split(':'))
batch_set = fabl(numb, nump) # proc-batch made in here: nump (groups) x numb (lines)
proc_batch_set = mp_proc(batch_set)
with open('out-min', 'wt') as fout:
write_set(proc_batch_set, fout)
return
if __name__ == '__main__':
main()
The Queue have a certain capacity and can get full if you do not empty it while the Process are running. This does not block the execution of your processes but you won't be able to join the Process if the put did not complete.
So I would just modify the mp_proc function such that:
def mp_proc(batch_set):
'given the batch, disperse it to the number of processes and ret the results'
n_process = len(batch_set)
output = mp.Queue()
processes = [mp.Process(target=proc_lines, args=(i, output, batch_set[i]))
for i in range(process)]
for p in processes:
p.start()
# Empty the queue while the processes are running so there is no
# issue with uncomplete `put` operations.
results = sorted([output.get() for p in processes])
# Join the process to make sure everything finished correctly
for p in processes:
p.join()
return results

Resources