@@ -252,6 +252,15 @@ class ShareableList:
252
252
packing format for any storable value must require no more than 8
253
253
characters to describe its format."""
254
254
255
+ # The shared memory area is organized as follows:
256
+ # - 8 bytes: number of items (N) as a 64-bit integer
257
+ # - (N + 1) * 8 bytes: offsets of each element from the start of the
258
+ # data area
259
+ # - K bytes: the data area storing item values (with encoding and size
260
+ # depending on their respective types)
261
+ # - N * 8 bytes: `struct` format string for each element
262
+ # - N bytes: index into _back_transforms_mapping for each element
263
+ # (for reconstructing the corresponding Python value)
255
264
_types_mapping = {
256
265
int : "q" ,
257
266
float : "d" ,
@@ -283,7 +292,8 @@ def _extract_recreation_code(value):
283
292
return 3 # NoneType
284
293
285
294
def __init__ (self , sequence = None , * , name = None ):
286
- if sequence is not None :
295
+ if name is None or sequence is not None :
296
+ sequence = sequence or ()
287
297
_formats = [
288
298
self ._types_mapping [type (item )]
289
299
if not isinstance (item , (str , bytes ))
@@ -294,10 +304,14 @@ def __init__(self, sequence=None, *, name=None):
294
304
]
295
305
self ._list_len = len (_formats )
296
306
assert sum (len (fmt ) <= 8 for fmt in _formats ) == self ._list_len
297
- self ._allocated_bytes = tuple (
298
- self ._alignment if fmt [- 1 ] != "s" else int (fmt [:- 1 ])
299
- for fmt in _formats
300
- )
307
+ offset = 0
308
+ # The offsets of each list element into the shared memory's
309
+ # data area (0 meaning the start of the data area, not the start
310
+ # of the shared memory area).
311
+ self ._allocated_offsets = [0 ]
312
+ for fmt in _formats :
313
+ offset += self ._alignment if fmt [- 1 ] != "s" else int (fmt [:- 1 ])
314
+ self ._allocated_offsets .append (offset )
301
315
_recreation_codes = [
302
316
self ._extract_recreation_code (item ) for item in sequence
303
317
]
@@ -308,13 +322,9 @@ def __init__(self, sequence=None, *, name=None):
308
322
self ._format_back_transform_codes
309
323
)
310
324
325
+ self .shm = SharedMemory (name , create = True , size = requested_size )
311
326
else :
312
- requested_size = 8 # Some platforms require > 0.
313
-
314
- if name is not None and sequence is None :
315
327
self .shm = SharedMemory (name )
316
- else :
317
- self .shm = SharedMemory (name , create = True , size = requested_size )
318
328
319
329
if sequence is not None :
320
330
_enc = _encoding
@@ -323,7 +333,7 @@ def __init__(self, sequence=None, *, name=None):
323
333
self .shm .buf ,
324
334
0 ,
325
335
self ._list_len ,
326
- * (self ._allocated_bytes )
336
+ * (self ._allocated_offsets )
327
337
)
328
338
struct .pack_into (
329
339
"" .join (_formats ),
@@ -346,10 +356,12 @@ def __init__(self, sequence=None, *, name=None):
346
356
347
357
else :
348
358
self ._list_len = len (self ) # Obtains size from offset 0 in buffer.
349
- self ._allocated_bytes = struct .unpack_from (
350
- self ._format_size_metainfo ,
351
- self .shm .buf ,
352
- 1 * 8
359
+ self ._allocated_offsets = list (
360
+ struct .unpack_from (
361
+ self ._format_size_metainfo ,
362
+ self .shm .buf ,
363
+ 1 * 8
364
+ )
353
365
)
354
366
355
367
def _get_packing_format (self , position ):
@@ -371,7 +383,6 @@ def _get_packing_format(self, position):
371
383
def _get_back_transform (self , position ):
372
384
"Gets the back transformation function for a single value."
373
385
374
- position = position if position >= 0 else position + self ._list_len
375
386
if (position >= self ._list_len ) or (self ._list_len < 0 ):
376
387
raise IndexError ("Requested position out of range." )
377
388
@@ -388,7 +399,6 @@ def _set_packing_format_and_transform(self, position, fmt_as_str, value):
388
399
"""Sets the packing format and back transformation code for a
389
400
single value in the list at the specified position."""
390
401
391
- position = position if position >= 0 else position + self ._list_len
392
402
if (position >= self ._list_len ) or (self ._list_len < 0 ):
393
403
raise IndexError ("Requested position out of range." )
394
404
@@ -408,9 +418,9 @@ def _set_packing_format_and_transform(self, position, fmt_as_str, value):
408
418
)
409
419
410
420
def __getitem__ (self , position ):
421
+ position = position if position >= 0 else position + self ._list_len
411
422
try :
412
- offset = self ._offset_data_start \
413
- + sum (self ._allocated_bytes [:position ])
423
+ offset = self ._offset_data_start + self ._allocated_offsets [position ]
414
424
(v ,) = struct .unpack_from (
415
425
self ._get_packing_format (position ),
416
426
self .shm .buf ,
@@ -425,23 +435,26 @@ def __getitem__(self, position):
425
435
return v
426
436
427
437
def __setitem__ (self , position , value ):
438
+ position = position if position >= 0 else position + self ._list_len
428
439
try :
429
- offset = self ._offset_data_start \
430
- + sum ( self . _allocated_bytes [: position ])
440
+ item_offset = self ._allocated_offsets [ position ]
441
+ offset = self . _offset_data_start + item_offset
431
442
current_format = self ._get_packing_format (position )
432
443
except IndexError :
433
444
raise IndexError ("assignment index out of range" )
434
445
435
446
if not isinstance (value , (str , bytes )):
436
447
new_format = self ._types_mapping [type (value )]
437
448
else :
438
- if len (value ) > self ._allocated_bytes [position ]:
449
+ allocated_length = self ._allocated_offsets [position + 1 ] - item_offset
450
+
451
+ if len (value ) > allocated_length :
439
452
raise ValueError ("exceeds available storage for existing str" )
440
453
if current_format [- 1 ] == "s" :
441
454
new_format = current_format
442
455
else :
443
456
new_format = self ._types_mapping [str ] % (
444
- self . _allocated_bytes [ position ] ,
457
+ allocated_length ,
445
458
)
446
459
447
460
self ._set_packing_format_and_transform (
@@ -463,33 +476,35 @@ def __repr__(self):
463
476
464
477
@property
465
478
def format (self ):
466
- "The struct packing format used by all currently stored values ."
479
+ "The struct packing format used by all currently stored items ."
467
480
return "" .join (
468
481
self ._get_packing_format (i ) for i in range (self ._list_len )
469
482
)
470
483
471
484
@property
472
485
def _format_size_metainfo (self ):
473
- "The struct packing format used for metainfo on storage sizes ."
474
- return f" { self ._list_len } q"
486
+ "The struct packing format used for the items' storage offsets ."
487
+ return "q" * ( self ._list_len + 1 )
475
488
476
489
@property
477
490
def _format_packing_metainfo (self ):
478
- "The struct packing format used for the values ' packing formats."
491
+ "The struct packing format used for the items ' packing formats."
479
492
return "8s" * self ._list_len
480
493
481
494
@property
482
495
def _format_back_transform_codes (self ):
483
- "The struct packing format used for the values ' back transforms."
496
+ "The struct packing format used for the items ' back transforms."
484
497
return "b" * self ._list_len
485
498
486
499
@property
487
500
def _offset_data_start (self ):
488
- return (self ._list_len + 1 ) * 8 # 8 bytes per "q"
501
+ # - 8 bytes for the list length
502
+ # - (N + 1) * 8 bytes for the element offsets
503
+ return (self ._list_len + 2 ) * 8
489
504
490
505
@property
491
506
def _offset_packing_formats (self ):
492
- return self ._offset_data_start + sum ( self ._allocated_bytes )
507
+ return self ._offset_data_start + self ._allocated_offsets [ - 1 ]
493
508
494
509
@property
495
510
def _offset_back_transform_codes (self ):
0 commit comments