Made fields in python structs assigned per-instance instead of at the class scope#351
Conversation
|
@Mat198 Can you have a look please? |
christophfroehlich
left a comment
There was a problem hiding this comment.
Thank you for opening this PR. Can you please fix pre-commit issues? and better install it for the future.
There was a problem hiding this comment.
Pull request overview
This PR updates the Python code generation path so generated parameter “struct” fields are initialized per-instance (in __init__) rather than at class scope, preventing unintended shared state across Params instances and nested sub-structs/maps (as reported in #313).
Changes:
- Add
__init__methods to generated Python parameter structs and move field initialization into__init__. - Update generated Python variable declarations to use
self.<field> = ...instead of class attributes. - Extend YAML parsing/codegen to support emitting sub-struct instance initialization via a new
is_structpath.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py |
Adds an is_struct path in variable codegen and injects generated “struct instance” fields intended for Python __init__. |
generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/parameter_library_header |
Wraps Params fields in an __init__ method and updates stamp_ to self.stamp_. |
generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_variable |
Switches generated assignments to self.<name> = .... |
generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_struct |
Adds __init__ for all generated struct classes and removes class-scope instantiation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Hmm, I was accidentally working under the assumption that parse_yaml.py was only used for Python code, but I see that that isn't quite correct. I'll have to take another look find a way to cleanly scope it to just python code |
Yeah. Sure! |
|
Alright, I reworked my solution to add python-specific fields to the Jinja data for both |
|
Thanks for your contribution @alexnavtt! Could you please remove the module reload from the consistency test (test_params_consistency.py)? This shoudn't be necessary with this fix. |
|
Done |
christophfroehlich
left a comment
There was a problem hiding this comment.
Can you please add a test for this (as suggested by copilot, too)?
@Mat198 If the changes are working for you, please leave a review via github UI. Thanks!
|
Hey @alexnavtt, the consistency tests are failling. It's very easy to fix but I don't have ter permissions to edit it. Just fix the import of the admitance_controller parameters: To run the tests locally, you need to build with tests enabled: Them run the tests: To run the precommit tests you can use: Or install them: |
Mat198
left a comment
There was a problem hiding this comment.
The consistency test is broken as stated in the previous comment.
|
Alright, I've added tests for both the problem that I was seeing the shared state in maps, and the problem that @Mat198 was seeing with shared state across instances: def test_params_do_not_share_state(self):
params1 = self.listener.get_params()
params1.pid.rate = 1.0
params2 = self.listener.get_params()
params2.pid.rate = 2.0
self.assertNotEqual(params1.pid.rate, params2.pid.rate)
self.node.set_parameters([Parameter('pid.rate', value='3.0')])
params2 = self.listener.get_params()
self.assertNotEqual(params1.pid.rate, params2.pid.rate)
def test_maps_do_not_share_state(self):
self.node.set_parameters(
[
Parameter(
'nested_map_struct.A.nested_struct.nested_struct_field',
value='valueA',
),
Parameter(
'nested_map_struct.B.nested_struct.nested_struct_field',
value='valueB',
),
]
)
params = self.listener.get_params()
self.assertNotEqual(
params.nested_map_struct.get_entry('A').nested_struct.nested_struct_field,
params.nested_map_struct.get_entry('B').nested_struct.nested_struct_field,
) |
Mat198
left a comment
There was a problem hiding this comment.
Great work, @alexnavtt! LGTM
christophfroehlich
left a comment
There was a problem hiding this comment.
Thanks for the follow-up! LGTM
0cccad8
into
PickNikRobotics:main
|
I am hesitant to backport this to humble: In theory this is a behavior change, even if it was maybe never intended to work like this. |


As stated in #313, struct variables declared in python modules were declared at the class scope, causing unwanted state sharing in certain circumstances. This PR fixes that by declaring all fields within an
__init__function, including sub-struct instances.As an examples of changes made, I'll highlight my use case and what changed. The parameter definition file is located here. When setting multiple depth camera sensors via this parameter set, I noticed that the
camera_infoparameter would be shared and take the last set value across all Map entries. This issue could also appear when creating multipleParamsinstances.This is the generated code prior to the changes:
And this is the generated code after the changes:
I have tested this code in my project and verified that it fixes the issue for me.
Also, this is my first time working with this codebase, so I would appreciate a thourough review making sure I didn't accidentally affect some other part of the system.