-
-
Notifications
You must be signed in to change notification settings - Fork 810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Proposal] Change type of metadata from dict
to Mapping
#951
Comments
So I would love to say yes to this, but I know of cases where the metadata is dynamically determined on
Another example is the HumanRendering wrapper that enables environments with only rgb array support to have human like rendering. To make the metadata render modes correct, HumanRendering adds "human" to the modes
If you can think of a type aware method of solving this, that would be great but I'm not aware of one. In a side comment, MyPy seems like the better type checker for our compared to Pyright particular with our use of NumPy so would be happy to accept any PRs that changed us across. This wouldn't need to happen all at once |
Hi, thanks for the quick reply! The logic that you describe in I've had a quick look into these two classes, and it seems to me that Regarding Gymnasium/gymnasium/envs/mujoco/mujoco_env.py Lines 85 to 88 in dcec2f2
it looks like no modification of the dict actually happens. Instead, it is merely an assert on the value set by the subclasses.
As for Gymnasium/gymnasium/wrappers/rendering.py Lines 480 to 482 in dcec2f2
this is where Python's semantics save us: This piece of code does not actually modify the metadata mapping. Instead, it makes a deepcopy of it, modifies a nested list object (which is Fine™ as far as Python and its type checkers are concerned) and then assigns this new object to metadata . Bot reassignment and nested .append() are type-correct, because Mapping only prevents modifications of the dict itself, e.g.:
metadata["render.modes"] = some_new_list
render_fps = metadata.setdefault("render_fps", 25) Finally, I've grepped for any occurrences of Gymnasium/gymnasium/envs/mujoco/ant_v5.py Lines 295 to 302 in dcec2f2
Here, self.metadata is assigned inside the __init__() method. This indeed rules out any use of ClassVar , fair enough. However, this case still qualifies as a Mapping because only the object as a whole is replaced.
Finally, I've put a small experiment together here: https://gist.github.com/troiganto/ce6d3eef305d14ea2c954291927e5d78 (Regarding a switch from Pyright to Mypy: that sounds like a super interesting project! I might bring it up again in a new issue if I find the time, but unfortunately I'm completely swamped right now porting a lot of code from Gym to Gymnasium. Priorities unfortunately haven't allowed me to tackle this until now 😓 ) |
Proposal
I propose to change the type annotation of
Env.metadata
fromdict[str, Any]
toMapping[str, Any]
.Motivation
The purpose of
metadata
is to be a read-only store of certain information about envs. As far as I can tell, the attribute is only ever set during class creation and only read afterwards.This is not what the type annotation
dict[str, Any]
communicates, however. In the type hierarchy, it is considered a mutable mapping, meaning that mutating item access is explicitly allowed.This makes it difficult to define an env when using type checkers or linters. For example, given this piece of code:
The ruff linter gives this warning:
The only solution right now is to disable this (generally sensible) warning either globally or for each env individually.
Changing the type annotation is a reasonably small change, should not change any runtime behavior and makes the code communicate better the use cases it anticipates. The only code that would negatively impacted (i.e. receive linter warnings where there were none before) would be code that is already highly suspect, e.g. code that modifies the metadata after class creation.
Pitch
I propose to change the definition of
Env.metadata
, which currently looks like this:to use the ABC
Mapping
:Alternatives
One could also go one step further and declare
metadata
aClassVar[Mapping[str, Any]]
. This would imply that the metadata is only supposed to be defined on the class itself and not e.g. in__init__()
.However, I haven't clearly thought through the implications that this change would have, especially given that e.g. on
Wrapper
, the metadata arguably is an instance rather than class attribute. Some incomplete testing tells me that this would at least be a non-trivial change:Additional context
I use Mypy rather than Pyright for type checking, so it might be worthwhile that changing the annotation from
dict
toMapping
won't break anything for Pyright users.Checklist
The text was updated successfully, but these errors were encountered: