@@ -84,6 +84,9 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
8484 It is an abstract base class; :class:`StructLayout`, :class:`UnionLayout`,
8585 :class:`ArrayLayout`, and :class:`FlexibleLayout` implement concrete layout rules.
8686 New layout rules can be defined by inheriting from this class.
87+
88+ Like all other shape-castable objects, all layouts are immutable. New classes deriving from
89+ :class:`Layout` must preserve this invariant.
8790 """
8891
8992 @staticmethod
@@ -274,14 +277,6 @@ class StructLayout(Layout):
274277 """
275278
276279 def __init__ (self , members ):
277- self .members = members
278-
279- @property
280- def members (self ):
281- return {key : field .shape for key , field in self ._fields .items ()}
282-
283- @members .setter
284- def members (self , members ):
285280 offset = 0
286281 self ._fields = {}
287282 if not isinstance (members , Mapping ):
@@ -300,6 +295,10 @@ def members(self, members):
300295 self ._fields [key ] = Field (shape , offset )
301296 offset += cast_shape .width
302297
298+ @property
299+ def members (self ):
300+ return {key : field .shape for key , field in self ._fields .items ()}
301+
303302 def __iter__ (self ):
304303 return iter (self ._fields .items ())
305304
@@ -348,14 +347,6 @@ class UnionLayout(Layout):
348347 Dictionary of union members.
349348 """
350349 def __init__ (self , members ):
351- self .members = members
352-
353- @property
354- def members (self ):
355- return {key : field .shape for key , field in self ._fields .items ()}
356-
357- @members .setter
358- def members (self , members ):
359350 self ._fields = {}
360351 if not isinstance (members , Mapping ):
361352 raise TypeError ("Union layout members must be provided as a mapping, not {!r}"
@@ -372,6 +363,10 @@ def members(self, members):
372363 .format (shape )) from e
373364 self ._fields [key ] = Field (shape , 0 )
374365
366+ @property
367+ def members (self ):
368+ return {key : field .shape for key , field in self ._fields .items ()}
369+
375370 def __iter__ (self ):
376371 return iter (self ._fields .items ())
377372
@@ -429,34 +424,26 @@ class ArrayLayout(Layout):
429424 Amount of elements.
430425 """
431426 def __init__ (self , elem_shape , length ):
432- self .elem_shape = elem_shape
433- self .length = length
434-
435- @property
436- def elem_shape (self ):
437- return self ._elem_shape
438-
439- @elem_shape .setter
440- def elem_shape (self , elem_shape ):
441427 try :
442428 Shape .cast (elem_shape )
443429 except TypeError as e :
444430 raise TypeError ("Array layout element shape must be a shape-castable object, "
445431 "not {!r}"
446432 .format (elem_shape )) from e
433+ if not isinstance (length , int ) or length < 0 :
434+ raise TypeError ("Array layout length must be a non-negative integer, not {!r}"
435+ .format (length ))
447436 self ._elem_shape = elem_shape
437+ self ._length = length
438+
439+ @property
440+ def elem_shape (self ):
441+ return self ._elem_shape
448442
449443 @property
450444 def length (self ):
451445 return self ._length
452446
453- @length .setter
454- def length (self , length ):
455- if not isinstance (length , int ) or length < 0 :
456- raise TypeError ("Array layout length must be a non-negative integer, not {!r}"
457- .format (length ))
458- self ._length = length
459-
460447 def __iter__ (self ):
461448 offset = 0
462449 for index in range (self ._length ):
@@ -519,39 +506,14 @@ class FlexibleLayout(Layout):
519506 Fields defined in the layout.
520507 """
521508 def __init__ (self , size , fields ):
522- self .size = size
523- self .fields = fields
524-
525- @property
526- def size (self ):
527- """:meta private:""" # work around Sphinx bug
528- return self ._size
529-
530- @size .setter
531- def size (self , size ):
532509 if not isinstance (size , int ) or size < 0 :
533510 raise TypeError ("Flexible layout size must be a non-negative integer, not {!r}"
534511 .format (size ))
535- if hasattr (self , "_fields" ) and self ._fields :
536- endmost_name , endmost_field = max (self ._fields .items (),
537- key = lambda pair : pair [1 ].offset + pair [1 ].width )
538- if endmost_field .offset + endmost_field .width > size :
539- raise ValueError ("Flexible layout size {} does not cover the field '{}', which "
540- "ends at bit {}"
541- .format (size , endmost_name ,
542- endmost_field .offset + endmost_field .width ))
543- self ._size = size
544-
545- @property
546- def fields (self ):
547- return {** self ._fields }
548-
549- @fields .setter
550- def fields (self , fields ):
551- self ._fields = {}
552512 if not isinstance (fields , Mapping ):
553513 raise TypeError ("Flexible layout fields must be provided as a mapping, not {!r}"
554514 .format (fields ))
515+ self ._size = size
516+ self ._fields = {}
555517 for key , field in fields .items ():
556518 if not isinstance (key , (int , str )) or (isinstance (key , int ) and key < 0 ):
557519 raise TypeError ("Flexible layout field name must be a non-negative integer or "
@@ -560,12 +522,21 @@ def fields(self, fields):
560522 if not isinstance (field , Field ):
561523 raise TypeError ("Flexible layout field value must be a Field instance, not {!r}"
562524 .format (field ))
563- if field .offset + field .width > self . _size :
525+ if field .offset + field .width > size :
564526 raise ValueError ("Flexible layout field '{}' ends at bit {}, exceeding "
565527 "the size of {} bit(s)"
566- .format (key , field .offset + field .width , self . _size ))
528+ .format (key , field .offset + field .width , size ))
567529 self ._fields [key ] = field
568530
531+ @property
532+ def size (self ):
533+ """:meta private:""" # work around Sphinx bug
534+ return self ._size
535+
536+ @property
537+ def fields (self ):
538+ return {** self ._fields }
539+
569540 def __iter__ (self ):
570541 return iter (self ._fields .items ())
571542
0 commit comments