Array of Structs in Python - python-3.x

I cannot use multiprocessing, I need shared memory among entirely separate python processes on Windows using python 3. I've figured out how to do this using mmap, and it works great...when I use simple primitive types. However, I need to pass around more complex information. I've found the ctypes.Structure and it seems to be exactly what I need.
I want to create an array of ctypes.Structure and update an individual element within that array, write it back to memory as well as read an individual element.
import ctypes
import mmap
class Person(ctypes.Structure):
_fields_ = [
('name', ctypes.c_wchar * 10),
('age', ctypes.c_int)
]
if __name__ == '__main__':
num_people = 5
person = Person()
people = Person * num_people
mm_file = mmap.mmap(-1, ctypes.sizeof(people), access=mmap.ACCESS_WRITE, tagname="shmem")

Your people is not an array yet, it's still a class. In order to have your array, you need to initialize the class using from_buffer(), just like you were doing before with c_int:
PeopleArray = Person * num_people
mm_file = mmap.mmap(-1, ctypes.sizeof(PeopleArray), ...)
people = PeopleArray.from_buffer(mm_file)
people[0].name = 'foo'
people[0].age = 27
people[1].name = 'bar'
people[1].age = 42
...

Related

Class attributes/methods without defining them Python?

I am seeing the code shown below for the 1st time, which I have never learned or seen anywhere else before. I have only seen Class attributes or Instance attributes. How does this one below work? Can we just add any class attributes/methods like this way?
class Globals:
pass
g = Globals()
g.tasks = []
g.diff_list = []
g.pdf_list = []
g.tstamp = None
g.terminated = False
g.num_task_retries = 4
Thank you.
Unless you specify otherwise using __slots__, Python classes are basically just wrappers over dictionaries. They can be given arbitrary attributes at any time. To prevent this, you specify __slots__ on the class, which limits the attributes that can be added, and has performance benefits as well:
class Globals:
__slots__ = ["a"]
g = Globals()
g.a = 1 # Fine
g.b = 2 # AttributeError: 'Globals' object has no attribute 'b'

How to allow a class attribute to be access externally without class name in Python?

I defined an enum class and would like to be able to use its attributes without need to access it through class name. I mean:
class MyEnum:
ONE = 1
TWO = 2
...
if MyEnum.ONE == 1:
"Typic use"
if TWO == 2:
"My desire"
Is there a way to do this?
In my specific context, I'm calculating points externality of a window through Cohen–Sutherland algorithm, so I have the following code:
class Externality:
INSIDE = 0
LEFT = 1
RIGTH = 2
BELLOW = 4
ABOVE = 8
# And at some point:
((x, y), externality) = actual
if not Externality.INSIDE in externality:
del cohen_sutherland_list[0]
So, the needed of express Enum's name to access its items make the if statement (and the whole code) a little more verbose and redundant.
First things first: inherit from Enum.
Like everything in Python, all you need to do is assign the names:
from enum import Enum
class MyEnum(Enum):
ONE = 1
TWO = 2
ONE = MyEnum.ONE
TWO = MyEnum.TWO
That can get annoying fast, so a helper function would be nice:
def export_enum(enum_cls, env):
for member in enum_cls:
env[member.name] = member
and in use:
>>> export_enum(MyEnum, globals())
>>> TWO
<MyEnum.TWO: 2>
If you use aenum1 the export() function is already available, and can be used as a decorator:
from aenum import Enum, export
#export(globals())
class MyEnum(Enum):
ONE = 1
TWO = 2
1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

How to check what data pickled between processes

I have the following:
from struct import pack_into
from mmap import mmap
from multiprocessing import Pool
mem_label = "packed_ints"
total = 5 * 10**7
def create_mmap(size = total):
''' Seems only the Windows version of mmap accepts labels '''
is_this_pickled = mmap(-1, total * 4, mem_label)
def pack_into_mmap(idx_nums_tup):
idx, ints_to_pack = idx_nums_tup
pack_into(str(len(ints_to_pack)) + 'i', mmap(-1, total * 4, mem_label) , idx*4*total//2 , *ints_to_pack)
if __name__ == '__main__':
create_mmap()
ints_to_pack = range(total)
pool = Pool()
pool.map(pack_into_mmap, enumerate((ints_to_pack[:total//2], ints_to_pack[total//2:])))
I "hid" the initial mmap inside a function, but I would like to know for certain what is being pickled.
Can I monitor / tap into that information in Python?
I am not certain if there is an easy way to tell what information is pickled and what information is inherited when using multiprocessing.Pool. However, in your code example, I am confident that the is_this_pickled variable is in fact not pickled since it is never passed to the Pool object in any fashion. The underlying mmap object should be inherited by child processes.

Access Violation when using Ctypes to Interface with Fortran DLL

I have a set of dlls created from Fortran that I am running from python. I've successfully created a wrapper class and have been running the dlls fine for weeks.
Today I noticed an error in my input and changed it, but to my surprise this caused the following:
OSError: exception: access violation reading 0x705206C8
If seems that certain input values somehow cause me to try to access illegal data. I created the following MCVE and it does repeat the issue. Specifically an error is thrown when 338 < R_o < 361. Unfortunately I cannot publish the raw Fortran code, nor create an MCVE which replicates the problem and is sufficiently abstracted such that I could share it. All of the variables are either declared as integer or real(8) types in the Fortran code.
import ctypes
import os
DLL_PATH = "C:\Repos\CASS_PFM\dlls"
class wrapper:
def __init__(self,data):
self.data = data
self.DLL = ctypes.CDLL(os.path.join(DLL_PATH,"MyDLL.dll"))
self.fortran_subroutine = getattr(self.DLL,"MyFunction_".lower())
self.output = {}
def run(self):
out = (ctypes.c_longdouble * len(self.data))()
in_data = []
for item in self.data:
item.convert_to_ctypes()
in_data.append(ctypes.byref(item.c_val))
self.fortran_subroutine(*in_data, out)
for item in self.data:
self.output[item.name] = item.convert_to_python()
class FortranData:
def __init__(self,name,py_val,ctype,some_param=True):
self.name = name
self.py_val = py_val
self.ctype = ctype
self.some_param = some_param
def convert_to_ctypes(self):
ctype_converter = getattr(ctypes,self.ctype)
self.c_val = ctype_converter(self.py_val)
return self.c_val
def convert_to_python(self):
self.py_val = self.c_val.value
return self.py_val
def main():
R_o = 350
data = [
FortranData("R_o",R_o,'c_double',False),
FortranData("thick",57.15,'c_double',False),
FortranData("axial_c",100,'c_double',False),
FortranData("sigy",235.81,'c_double',False),
FortranData("sigu",619.17,'c_double',False),
FortranData("RO_alpha",1.49707,'c_double',False),
FortranData("RO_sigo",235.81,'c_double',False),
FortranData("RO_epso",0.001336,'c_double',False),
FortranData("RO_n",6.6,'c_double',False),
FortranData("Resist_Jic",116,'c_double',False),
FortranData("Resist_C",104.02,'c_double',False),
FortranData("Resist_m",0.28,'c_double',False),
FortranData("pressure",15.51375,'c_double',False),
FortranData("i_write",0,'c_int',False),
FortranData("if_flag_twc",0,'c_int',),
FortranData("i_twc_ll",0,'c_int',),
FortranData("i_twc_epfm",0,'c_int',),
FortranData("i_err_code",0,'c_int',),
FortranData("Axial_TWC_ratio",0,'c_double',),
FortranData("Axial_TWC_fail",0,'c_int',),
FortranData("c_max_ll",0,'c_double',),
FortranData("c_max_epfm",0,'c_double',)
]
obj = wrapper(data)
obj.run()
print(obj.output)
if __name__ == "__main__": main()
It's not just the R_o value either; there are some combinations of values that cause the same error (seemingly without rhyme or reason). Is there anything within the above Python that might lead to an access violation depending on the values passed to the DLL?
Python version is 3.7.2, 32-bit
I see 2 problems with the code (and a potential 3rd one):
argtypes (and restype) not being specified. Check [SO]: C function called from Python via ctypes returns incorrect value (#CristiFati's answer) for more details
This may be a consequence (or at least it's closely related to) the previous. I can only guess without the Fortran (or better: C) function prototype, but anyway there is certainly something wrong. I assume that for the input data things should be same as for the output data, so the function would take 2 arrays (same size), and the input one's elements would be void *s (since their type is not consistent). Then, you'd need something like (although I can't imagine how would Fortran know which element contains an int and which a double):
in_data (ctypes.c_void_p * len(self.data))()
for idx, item in enumerate(self.data):
item.convert_to_ctypes()
in_data[index] = ctypes.addressof(item.c_val)
Since you're on 032bit, you should also take calling convention into account (ctypes.CDLL vs ctypes.WinDLL)
But again, without the function prototype, everything is just a speculation.
Also, why "MyFunction_".lower() instead of "myfunction_"?

Access element of list by variable name

How can I access a list element using the name of the list?
I would like to allow a user to edit the code in determine a single variable to be inputted into a function. For example:
blah = [1,2]
blah2 = 5
toBeChanged = "blah2"
def foo():
print(blah)
def changeVariable():
globals()[toBeChanged] += 1
for time in range(5):
changeVariable()
simulate
This works for blah2 since it is a simple variable, however it will not work for blah[0] since it is part of a list. I've also tried placing my variables into a dictionary as other answers have suggested, but I still am unable to change list elements through a simple string.
Is there a way to do this that I am missing? Thanks!
Rather than using globals() and altering directly it would be much, much better to use a dictionary to store the variables you want the user to alter, and then manipulate that:
my_variables = {
'blah': [1,2]
'blah2': 5
}
toBeChanged = "blah2"
def foo():
print(my_variables['blah'])
def changeVariable():
my_variables[toBeChanged] = my_variables.get(toBeChanged,0) + 1
for time in range(5):
changeVariable()
This has the added advantage that if a user enters a variable that doesn't exist a default is chosen, and doesn't override any variables that might be important for future execution.

Resources