Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Principle:ARISE Initiative Robosuite Environment Wrapper Pattern

From Leeroopedia
Knowledge Sources
Domains Robotics, Software_Architecture
Last Updated 2026-02-15 07:00 GMT

Overview

A decorator-based pattern for transparently extending simulation environment behavior through composable wrappers that intercept and augment core environment methods without modifying the underlying implementation.

Description

Simulation environments expose a standard interface consisting of step, reset, render, and observation/action specification methods. Frequently, additional behaviors need to be layered on top of this core interface: logging data to disk, converting observations to a different format, applying domain randomization, or adapting the interface for a specific learning framework. Modifying the environment class directly for each of these concerns would violate the single-responsibility principle and create an unwieldy monolithic class.

The wrapper pattern solves this by defining a base Wrapper class that holds a reference to an inner environment and delegates all method calls to it by default. Concrete wrapper subclasses override specific methods to inject additional behavior before or after delegating to the inner environment. Because wrappers themselves conform to the same interface as the environment, they can be composed (stacked) in arbitrary order, each layer adding its own concern.

The base wrapper implements transparent delegation through Python's __getattr__ mechanism. Any attribute or method not explicitly defined on the wrapper is automatically forwarded to the inner environment. This means that wrappers only need to override the methods they care about; all other functionality passes through unchanged. A special safeguard ensures that if a delegated method returns the inner environment object itself (e.g., for method chaining), the wrapper returns itself instead, preserving the wrapping chain.

A double-wrap guard prevents accidentally applying the same wrapper type twice, which would typically indicate a configuration error. The unwrapped property traverses the wrapper chain to return the original base environment, which is useful when direct access to the underlying simulation is needed (e.g., for accessing internal state not exposed through the wrapper interface).

Usage

Use environment wrappers whenever you need to add cross-cutting concerns to a simulation environment without modifying its source code. Common applications include data collection (recording observations and actions to HDF5 files), Gymnasium compatibility (adapting the interface to the Gymnasium API), domain randomization (randomizing visual and physical properties), and visualization (adding indicator overlays). Wrappers should be applied in a logical order, with lower-level wrappers (data collection) applied before higher-level ones (Gymnasium adaptation).

Theoretical Basis

Decorator pattern:

The wrapper pattern is a specific application of the decorator (or wrapper) structural design pattern from object-oriented software design:

EnvironmentInterface
  |-- step(action)         -> (obs, reward, done, info)
  |-- reset()              -> obs
  |-- render(**kwargs)     -> frame
  |-- observation_spec()   -> spec
  |-- action_spec          -> (low, high)

Wrapper(EnvironmentInterface)
  |-- env: EnvironmentInterface    (inner environment)
  |-- step(action)         -> env.step(action)    (default delegation)
  |-- reset()              -> env.reset()
  |-- __getattr__(attr)    -> getattr(env, attr)  (transparent proxy)

Composition over inheritance:

Instead of creating a deep inheritance hierarchy where each behavior requires a new subclass:

# Anti-pattern: inheritance explosion
class DataCollectionEnv(DomainRandomizationEnv(GymEnv(BaseEnv))):
    ...

# Wrapper pattern: composable layers
env = BaseEnv()
env = DataCollectionWrapper(env)
env = DomainRandomizationWrapper(env)
env = GymWrapper(env)

Transparent delegation with identity preservation:

def __getattr__(self, attr):
    orig_attr = getattr(self.env, attr)
    if callable(orig_attr):
        def hooked(*args, **kwargs):
            result = orig_attr(*args, **kwargs)
            if result is self.env:
                return self       # preserve wrapper chain
            return result
        return hooked
    return orig_attr

This ensures that:

  • Attribute access falls through to the inner environment
  • Method calls are proxied transparently
  • Return values that reference the inner environment are replaced with the wrapper, maintaining the decorator chain
  • The unwrapped property recursively traverses to the base environment

Double-wrap prevention:

def _warn_double_wrap(self):
    env = self.env
    while isinstance(env, Wrapper):
        if env.class_name() == self.class_name():
            raise Exception("Double wrap detected")
        env = env.env

Related Pages

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment