Skip to content
This repository was archived by the owner on Apr 8, 2026. It is now read-only.

Commit 0263deb

Browse files
Add support for python 3.6 (#2836)
* Add support for python 3.6 * Add support for python 3.6 * Added check for python 3.6 to not install mujoco as no version exists * Fixed the install groups for python 3.6 * Re-added python 3.6 support for gym * black * Added support for dataclasses through dataclasses module in setup that backports the module * Fixed install requirements * Re-added dummy env spec with dataclasses * Changed type for compatability for python 3.6 * Added a python 3.6 warning * Fixed python 3.6 typing issue * Removed __future__ import annotation for python 3.6 support * Fixed python 3.6 typing
1 parent 273e3f2 commit 0263deb

27 files changed

Lines changed: 148 additions & 154 deletions

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
runs-on: ubuntu-latest
77
strategy:
88
matrix:
9-
python-version: ['3.7', '3.8', '3.9', '3.10']
9+
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
1010
steps:
1111
- uses: actions/checkout@v2
1212
- run: |

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ repos:
4141
hooks:
4242
- id: pyupgrade
4343
# TODO: remove `--keep-runtime-typing` option
44-
args: ["--py37-plus", "--keep-runtime-typing"]
44+
args: ["--py36-plus", "--keep-runtime-typing"]
4545
- repo: local
4646
hooks:
4747
- id: pyright

gym/core.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
"""Core API for Environment, Wrapper, ActionWrapper, RewardWrapper and ObservationWrapper."""
2-
from __future__ import annotations
3-
4-
from typing import Generic, Optional, SupportsFloat, TypeVar, Union
2+
import sys
3+
from typing import Generic, Optional, SupportsFloat, Tuple, TypeVar, Union
54

65
from gym import spaces
7-
from gym.logger import deprecation
6+
from gym.logger import deprecation, warn
87
from gym.utils import seeding
98
from gym.utils.seeding import RandomNumberGenerator
109

10+
if sys.version_info == (3, 6):
11+
warn(
12+
"Gym minimally supports python 3.6 as the python foundation not longer supports the version, please update your version to 3.7+"
13+
)
14+
1115
ObsType = TypeVar("ObsType")
1216
ActType = TypeVar("ActType")
1317

@@ -62,7 +66,7 @@ def np_random(self) -> RandomNumberGenerator:
6266
def np_random(self, value: RandomNumberGenerator):
6367
self._np_random = value
6468

65-
def step(self, action: ActType) -> tuple[ObsType, float, bool, dict]:
69+
def step(self, action: ActType) -> Tuple[ObsType, float, bool, dict]:
6670
"""Run one timestep of the environment's dynamics.
6771
6872
When end of episode is reached, you are responsible for calling :meth:`reset` to reset this environment's state.
@@ -92,7 +96,7 @@ def reset(
9296
seed: Optional[int] = None,
9397
return_info: bool = False,
9498
options: Optional[dict] = None,
95-
) -> Union[ObsType, tuple[ObsType, dict]]:
99+
) -> Union[ObsType, Tuple[ObsType, dict]]:
96100
"""Resets the environment to an initial state and returns the initial observation.
97101
98102
This method can reset the environment's random number generator(s) if ``seed`` is an integer or
@@ -201,7 +205,7 @@ def seed(self, seed=None):
201205
return [seed]
202206

203207
@property
204-
def unwrapped(self) -> Env:
208+
def unwrapped(self) -> "Env":
205209
"""Returns the base non-wrapped environment.
206210
207211
Returns:
@@ -248,7 +252,7 @@ def __init__(self, env: Env):
248252

249253
self._action_space: Optional[spaces.Space] = None
250254
self._observation_space: Optional[spaces.Space] = None
251-
self._reward_range: Optional[tuple[SupportsFloat, SupportsFloat]] = None
255+
self._reward_range: Optional[Tuple[SupportsFloat, SupportsFloat]] = None
252256
self._metadata: Optional[dict] = None
253257

254258
def __getattr__(self, name):
@@ -290,14 +294,14 @@ def observation_space(self, space: spaces.Space):
290294
self._observation_space = space
291295

292296
@property
293-
def reward_range(self) -> tuple[SupportsFloat, SupportsFloat]:
297+
def reward_range(self) -> Tuple[SupportsFloat, SupportsFloat]:
294298
"""Return the reward range of the environment."""
295299
if self._reward_range is None:
296300
return self.env.reward_range
297301
return self._reward_range
298302

299303
@reward_range.setter
300-
def reward_range(self, value: tuple[SupportsFloat, SupportsFloat]):
304+
def reward_range(self, value: Tuple[SupportsFloat, SupportsFloat]):
301305
self._reward_range = value
302306

303307
@property
@@ -311,11 +315,11 @@ def metadata(self) -> dict:
311315
def metadata(self, value):
312316
self._metadata = value
313317

314-
def step(self, action: ActType) -> tuple[ObsType, float, bool, dict]:
318+
def step(self, action: ActType) -> Tuple[ObsType, float, bool, dict]:
315319
"""Steps through the environment with action."""
316320
return self.env.step(action)
317321

318-
def reset(self, **kwargs) -> Union[ObsType, tuple[ObsType, dict]]:
322+
def reset(self, **kwargs) -> Union[ObsType, Tuple[ObsType, dict]]:
319323
"""Resets the environment with kwargs."""
320324
return self.env.reset(**kwargs)
321325

gym/envs/registration.py

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import annotations
2-
31
import contextlib
42
import copy
53
import difflib
@@ -10,12 +8,14 @@
108
import warnings
119
from dataclasses import dataclass, field
1210
from typing import (
13-
Any,
1411
Callable,
12+
Dict,
1513
Iterable,
14+
List,
1615
Optional,
1716
Sequence,
1817
SupportsFloat,
18+
Tuple,
1919
Union,
2020
overload,
2121
)
@@ -34,15 +34,11 @@
3434
if sys.version_info >= (3, 8):
3535
from typing import Literal
3636
else:
37-
38-
class Literal(str):
39-
def __class_getitem__(cls, item):
40-
return Any
41-
37+
from typing_extensions import Literal
4238

4339
from gym import Env, error, logger
4440

45-
ENV_ID_RE: re.Pattern = re.compile(
41+
ENV_ID_RE = re.compile(
4642
r"^(?:(?P<namespace>[\w:-]+)\/)?(?:(?P<name>[\w:.-]+?))(?:-v(?P<version>\d+))?$"
4743
)
4844

@@ -54,7 +50,7 @@ def load(name: str) -> type:
5450
return fn
5551

5652

57-
def parse_env_id(id: str) -> tuple[Optional[str], str, Optional[int]]:
53+
def parse_env_id(id: str) -> Tuple[Optional[str], str, Optional[int]]:
5854
"""Parse environment ID string format.
5955
6056
This format is true today, but it's *not* an official spec.
@@ -241,7 +237,7 @@ def _check_version_exists(ns: Optional[str], name: str, version: Optional[int]):
241237

242238

243239
def find_highest_version(ns: Optional[str], name: str) -> Optional[int]:
244-
version: list[int] = [
240+
version: List[int] = [
245241
spec_.version
246242
for spec_ in registry.values()
247243
if spec_.namespace == ns and spec_.name == name and spec_.version is not None
@@ -302,39 +298,39 @@ def load_env_plugins(entry_point: str = "gym.envs") -> None:
302298

303299

304300
@overload
305-
def make(id: Literal["CartPole-v1"], **kwargs) -> Env[np.ndarray, np.ndarray | int]: ...
301+
def make(id: Literal["CartPole-v0", "CartPole-v1"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, int]]: ...
306302
@overload
307-
def make(id: Literal["MountainCar-v0"], **kwargs) -> Env[np.ndarray, np.ndarray | int]: ...
303+
def make(id: Literal["MountainCar-v0"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, int]]: ...
308304
@overload
309-
def make(id: Literal["MountainCarContinuous-v0"], **kwargs) -> Env[np.ndarray, np.ndarray | Sequence[SupportsFloat]]: ...
305+
def make(id: Literal["MountainCarContinuous-v0"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, Sequence[SupportsFloat]]]: ...
310306
@overload
311-
def make(id: Literal["Pendulum-v1"], **kwargs) -> Env[np.ndarray, np.ndarray | Sequence[SupportsFloat]]: ...
307+
def make(id: Literal["Pendulum-v1"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, Sequence[SupportsFloat]]]: ...
312308
@overload
313-
def make(id: Literal["Acrobot-v1"], **kwargs) -> Env[np.ndarray, np.ndarray | int]: ...
309+
def make(id: Literal["Acrobot-v1"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, int]]: ...
314310

315311
# Box2d
316312
# ----------------------------------------
317313

318314

319315
@overload
320-
def make(id: Literal["LunarLander-v2", "LunarLanderContinuous-v2"], **kwargs) -> Env[np.ndarray, np.ndarray | int]: ...
316+
def make(id: Literal["LunarLander-v2", "LunarLanderContinuous-v2"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, int]]: ...
321317
@overload
322-
def make(id: Literal["BipedalWalker-v3", "BipedalWalkerHardcore-v3"], **kwargs) -> Env[np.ndarray, np.ndarray | Sequence[SupportsFloat]]: ...
318+
def make(id: Literal["BipedalWalker-v3", "BipedalWalkerHardcore-v3"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, Sequence[SupportsFloat]]]: ...
323319
@overload
324-
def make(id: Literal["CarRacing-v1", "CarRacingDomainRandomize-v1"], **kwargs) -> Env[np.ndarray, np.ndarray | Sequence[SupportsFloat]]: ...
320+
def make(id: Literal["CarRacing-v1", "CarRacingDomainRandomize-v1"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, Sequence[SupportsFloat]]]: ...
325321

326322
# Toy Text
327323
# ----------------------------------------
328324

329325

330326
@overload
331-
def make(id: Literal["Blackjack-v1"], **kwargs) -> Env[np.ndarray, np.ndarray | int]: ...
327+
def make(id: Literal["Blackjack-v1"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, int]]: ...
332328
@overload
333-
def make(id: Literal["FrozenLake-v1", "FrozenLake8x8-v1"], **kwargs) -> Env[np.ndarray, np.ndarray | int]: ...
329+
def make(id: Literal["FrozenLake-v1", "FrozenLake8x8-v1"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, int]]: ...
334330
@overload
335-
def make(id: Literal["CliffWalking-v0"], **kwargs) -> Env[np.ndarray, np.ndarray | int]: ...
331+
def make(id: Literal["CliffWalking-v0"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, int]]: ...
336332
@overload
337-
def make(id: Literal["Taxi-v3"], **kwargs) -> Env[np.ndarray, np.ndarray | int]: ...
333+
def make(id: Literal["Taxi-v3"], **kwargs) -> Env[np.ndarray, Union[np.ndarray, int]]: ...
338334

339335
# Mujoco
340336
# ----------------------------------------
@@ -415,7 +411,7 @@ def env_specs(self):
415411

416412

417413
# Global registry of environments. Meant to be accessed through `register` and `make`
418-
registry: dict[str, EnvSpec] = EnvRegistry()
414+
registry: Dict[str, EnvSpec] = EnvRegistry()
419415
current_namespace: Optional[str] = None
420416

421417

@@ -522,7 +518,7 @@ def register(id: str, **kwargs):
522518

523519

524520
def make(
525-
id: str | EnvSpec,
521+
id: Union[str, EnvSpec],
526522
max_episode_steps: Optional[int] = None,
527523
autoreset: bool = False,
528524
disable_env_checker: bool = False,

gym/envs/toy_text/frozen_lake.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
from __future__ import annotations
2-
31
from contextlib import closing
42
from io import StringIO
53
from os import path
6-
from typing import Optional
4+
from typing import List, Optional
75

86
import numpy as np
97

@@ -31,7 +29,7 @@
3129
}
3230

3331

34-
def generate_random_map(size: int = 8, p: float = 0.8) -> list[str]:
32+
def generate_random_map(size: int = 8, p: float = 0.8) -> List[str]:
3533
"""Generates a random valid map (one that has a path from start to goal)
3634
3735
Args:

gym/spaces/box.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Implementation of a space that represents closed boxes in euclidean space."""
2-
from __future__ import annotations
3-
4-
from typing import Optional, Sequence, SupportsFloat, Union
2+
from typing import List, Optional, Sequence, SupportsFloat, Tuple, Type, Union
53

64
import numpy as np
75

@@ -52,8 +50,8 @@ def __init__(
5250
low: Union[SupportsFloat, np.ndarray],
5351
high: Union[SupportsFloat, np.ndarray],
5452
shape: Optional[Sequence[int]] = None,
55-
dtype: type = np.float32,
56-
seed: Optional[int | seeding.RandomNumberGenerator] = None,
53+
dtype: Type = np.float32,
54+
seed: Optional[Union[int, seeding.RandomNumberGenerator]] = None,
5755
):
5856
r"""Constructor of :class:`Box`.
5957
@@ -105,7 +103,7 @@ def __init__(
105103
assert isinstance(high, np.ndarray)
106104
assert high.shape == shape, "high.shape doesn't match provided shape"
107105

108-
self._shape: tuple[int, ...] = shape
106+
self._shape: Tuple[int, ...] = shape
109107

110108
low_precision = get_precision(low.dtype)
111109
high_precision = get_precision(high.dtype)
@@ -121,7 +119,7 @@ def __init__(
121119
super().__init__(self.shape, self.dtype, seed)
122120

123121
@property
124-
def shape(self) -> tuple[int, ...]:
122+
def shape(self) -> Tuple[int, ...]:
125123
"""Has stricter type than gym.Space - never None."""
126124
return self._shape
127125

@@ -210,7 +208,7 @@ def to_jsonable(self, sample_n):
210208
"""Convert a batch of samples from this space to a JSONable data type."""
211209
return np.array(sample_n).tolist()
212210

213-
def from_jsonable(self, sample_n: Sequence[SupportsFloat]) -> list[np.ndarray]:
211+
def from_jsonable(self, sample_n: Sequence[SupportsFloat]) -> List[np.ndarray]:
214212
"""Convert a JSONable data type to a batch of samples from this space."""
215213
return [np.asarray(sample) for sample in sample_n]
216214

@@ -278,7 +276,7 @@ def get_precision(dtype) -> SupportsFloat:
278276
def _broadcast(
279277
value: Union[SupportsFloat, np.ndarray],
280278
dtype,
281-
shape: tuple[int, ...],
279+
shape: Tuple[int, ...],
282280
inf_sign: str,
283281
) -> np.ndarray:
284282
"""Handle infinite bounds and broadcast at the same time if needed."""

gym/spaces/dict.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
"""Implementation of a space that represents the cartesian product of other spaces as a dictionary."""
2-
from __future__ import annotations
3-
42
from collections import OrderedDict
53
from collections.abc import Mapping, Sequence
64
from typing import Dict as TypingDict
7-
from typing import Optional
5+
from typing import Optional, Union
86

97
import numpy as np
108

@@ -53,8 +51,8 @@ class Dict(Space[TypingDict[str, Space]], Mapping):
5351

5452
def __init__(
5553
self,
56-
spaces: Optional[dict[str, Space]] = None,
57-
seed: Optional[dict | int | seeding.RandomNumberGenerator] = None,
54+
spaces: Optional[TypingDict[str, Space]] = None,
55+
seed: Optional[Union[dict, int, seeding.RandomNumberGenerator]] = None,
5856
**spaces_kwargs: Space,
5957
):
6058
"""Constructor of :class:`Dict` space.
@@ -101,7 +99,7 @@ def __init__(
10199
None, None, seed # type: ignore
102100
) # None for shape and dtype, since it'll require special handling
103101

104-
def seed(self, seed: Optional[dict | int] = None) -> list:
102+
def seed(self, seed: Optional[Union[dict, int]] = None) -> list:
105103
"""Seed the PRNG of this space and all subspaces."""
106104
seeds = []
107105
if isinstance(seed, dict):
@@ -188,9 +186,9 @@ def to_jsonable(self, sample_n: list) -> dict:
188186
for key, space in self.spaces.items()
189187
}
190188

191-
def from_jsonable(self, sample_n: dict[str, list]) -> list:
189+
def from_jsonable(self, sample_n: TypingDict[str, list]) -> list:
192190
"""Convert a JSONable data type to a batch of samples from this space."""
193-
dict_of_list: dict[str, list] = {}
191+
dict_of_list: TypingDict[str, list] = {}
194192
for key, space in self.spaces.items():
195193
dict_of_list[key] = space.from_jsonable(sample_n[key])
196194
ret = []

gym/spaces/discrete.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Implementation of a space consisting of finitely many elements."""
2-
from __future__ import annotations
3-
4-
from typing import Optional
2+
from typing import Optional, Union
53

64
import numpy as np
75

@@ -23,7 +21,7 @@ class Discrete(Space[int]):
2321
def __init__(
2422
self,
2523
n: int,
26-
seed: Optional[int | seeding.RandomNumberGenerator] = None,
24+
seed: Optional[Union[int, seeding.RandomNumberGenerator]] = None,
2725
start: int = 0,
2826
):
2927
r"""Constructor of :class:`Discrete` space.

gym/spaces/multi_binary.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Implementation of a space that consists of binary np.ndarrays of a fixed shape."""
2-
from __future__ import annotations
3-
4-
from typing import Optional, Sequence, Union
2+
from typing import Optional, Sequence, Tuple, Union
53

64
import numpy as np
75

@@ -29,7 +27,7 @@ class MultiBinary(Space[np.ndarray]):
2927
def __init__(
3028
self,
3129
n: Union[np.ndarray, Sequence[int], int],
32-
seed: Optional[int | seeding.RandomNumberGenerator] = None,
30+
seed: Optional[Union[int, seeding.RandomNumberGenerator]] = None,
3331
):
3432
"""Constructor of :class:`MultiBinary` space.
3533
@@ -49,7 +47,7 @@ def __init__(
4947
super().__init__(input_n, np.int8, seed)
5048

5149
@property
52-
def shape(self) -> tuple[int, ...]:
50+
def shape(self) -> Tuple[int, ...]:
5351
"""Has stricter type than gym.Space - never None."""
5452
return self._shape # type: ignore
5553

0 commit comments

Comments
 (0)