Runtime traps in embedded Python 2.7
The embedded runtime is not modern Python. Treat text, callbacks, imports, and iteration as compatibility hazards.
| Trap | Why it breaks | Safer habit |
|---|---|---|
| Unicode and bytes | str is bytes and UI/localization paths often expect text. |
Decode external bytes deliberately and encode only at API boundaries. |
| Dict mutation during iteration | Changing a dict while iterating it can skip items or raise runtime errors. | Iterate over data.keys() or a copied list of keys. |
| Integer division | 3 / 2 produces 1, not 1.5, when both operands
are integers.
|
Use float(value) or an explicit decimal operand before ratios,
percentages, and layout scaling. |
| Dict collection expectations | keys(), items(), and values() return lists in
Python 2, while iterator forms have separate names. |
Use iteritems() for large read-only loops and copied lists when you
need mutation safety. |
| Late-bound closures | Loop variables captured by callbacks can all point at the final value. | Bind values with default arguments in the lambda or nested function. |
| Captured function rebound as a method | A plain function assigned to a new-style class becomes a descriptor; later access through the class or instance can add an implicit first argument. | Use staticmethod(captured_function) when preserving a zero-argument
native callable, including during restoration. |
| Modern syntax | f-strings, pathlib, annotations, dataclasses, and many modern stdlib helpers are absent. | Use old-style formatting, os.path, simple classes, and explicit
validation.
|
| Import-time side effects | The client may not have account, avatar, services, or resources ready during import. | Define behavior during import; attach to runtime systems later. |
reload() and stale imports |
Reloading a module during development does not update objects already imported or captured by handlers. | Restart the client for reliable validation, especially after changing hooks, callbacks, classes, or generated contracts. |
| No exception chaining | raise NewError from error is unavailable and copied Python 3 handlers
will not parse. |
Log the original exception with traceback before returning a fallback or raising a Python 2-compatible error. |
| Old stdlib behavior | Bundled libraries may lack modern keyword arguments, TLS defaults, JSON behavior, or path helpers. | Check the embedded runtime before using examples copied from modern Python documentation. |
Illustrative. This authored loop demonstrates default-argument binding;
it is not an extracted client caller or a complete inspection feature. It supplies no
BigWorld import, vehicleIDs collection or
_inspect_vehicle implementation.
# Bind loop values for delayed callbacks.
for vehicleID in vehicleIDs:
BigWorld.callback(0.0, lambda vehicleID=vehicleID: _inspect_vehicle(vehicleID))
Each iteration creates a new lambda whose default stores the current
vehicleID object. A later call with no argument uses that default even after
the loop variable is rebound; an explicit argument can override it. This retains a reference,
not a deep copy or a snapshot of the vehicle, arena or account behind an ID.
_inspect_vehicle is looked up when the lambda runs, so its existence and behavior
are not captured. The historical
CPython
2.7.18 function-definition reference defines default evaluation at function creation
and reuse when arguments are omitted. Mutable defaults retain the same object. This language
rule is separate from embedded-client runtime verification.
The loop requests one zero-delay callback per reached item if iteration and registration succeed. It discards every returned handle and supplies no pending-work registry, cancellation, active/context guard, deduplication, work bound or readiness check. Repeating it can request more work for the same ID. Binding the value does not establish that its entity or owner will still be current when the callback runs. Preserve the separate callback timing and cleanup contract; zero delay does not make the call a synchronous result.
A missing vehicleIDs fails before scheduling. A missing BigWorld,
or iteration/registration failure, can stop the loop without rolling back earlier
registrations. A missing _inspect_vehicle fails later if the lambda is invoked
without that helper. These lines provide no exception handling, recovery, result collection
or completion signal. The lambda returns the helper result to its caller; the fragment
defines no engine contract for that return value. Choose a real owner and retain handles
when cleanup requires them. A shipped adaptation also needs the optional diagnostic and
version-attribution contract described under runtime
debugging; this illustration configures no reporter and is not a complete mod.
Named loggers
Use a stable named logger instead of the root logger. A name such as
mod.example.storage makes log filtering and bug reports much easier without tying
the message to a source repository or feature-specific class name.
Illustrative. This authored logging fragment is not a complete loader or an
extracted client caller. _read_json is undefined: its name supplies no file, JSON,
schema, encoding, size, readiness or persistence contract.
import logging
LOG = logging.getLogger('mod.example.storage')
def load_config(path):
try:
return _read_json(path)
except Exception:
LOG.exception('Failed to load configuration from %s', path)
return {}
Successful helper results pass through unchanged, including None, a non-dictionary
or a borrowed object. There is no copy, validation, merge, migration or asynchronous-result
handling. A missing _read_json raises NameError inside the same
try and takes the same fallback as another Exception from the call.
The handler catches Exception, not every BaseException such as
KeyboardInterrupt or SystemExit. It does not distinguish a missing file,
malformed input and a programming error. It calls LOG.exception before creating a
fresh empty dictionary. That fallback requires logging to return normally; a failure in logger
lookup, formatting or downstream handling is not caught by the same handler. There is no rollback,
previous-configuration restore or file repair, and {} need not be a usable schema.
Choose the real loader’s fallback boundary before adapting this illustration.
In the current bundled logging source,
Logger.exception supplies exc_info=1, error checks whether
ERROR is enabled, and _log obtains sys.exc_info() before creating and
handling a record on that path. Calling it inside the active handler therefore retains the current
traceback when record creation is reached. This fragment installs no handler and changes no level;
it proves no emission, storage or delivery.
Prefer lazy %s formatting over building large strings before the level check. Neither
that formatting nor the logger name redacts the supplied path: use bounded, non-sensitive context.
A shipped mod also needs version attribution and mod-owned traceback context for optional
exception reporting; a mod-looking logger name alone does not establish attribution or submission.
Keep reporting infrastructure optional and preserve the real fallback behavior. See
diagnostics guidance. This is source/control-flow inspection,
not execution of the helper, logger, reporter or client; no file recovery or report delivery was tested.