CQ013: NoGlobalState
Overview
| Property | Value |
|---|---|
| ID | CQ013 |
| Name | NoGlobalState |
| Group | code-quality |
| Severity | WARNING |
Description
Flags module-level mutable state that can lead to hard-to-debug issues, thread-safety problems, and unpredictable behavior.
Module-level mutable variables are problematic because:
- Shared state across imports: All modules importing your code share the same mutable objects
- Thread-safety issues: Concurrent access to mutable globals can cause race conditions
- Testing difficulties: Tests may affect each other through shared state
- Hidden dependencies: Functions that modify global state have implicit side effects
- Unpredictable behavior: Order of operations matters when global state is involved
What it checks
The check scans all Python files in the package (excluding test files, hidden directories, and __pycache__), identifies module-level assignments to mutable types:
- List literals:
[]or[1, 2, 3] - Dict literals:
{}or{"key": "value"} - Set literals:
{1, 2, 3} - Mutable constructor calls:
list(),dict(),set(),defaultdict(),OrderedDict(),Counter(),deque()
Result states
- PASSED: No mutable global state found
- FAILED: Mutable global variables detected
- NOT_APPLICABLE: Package directory not found
Ignored patterns
The following are NOT flagged:
- UPPER_CASE constants: Assumed to be treated as immutable by convention
__all__list: Standard Python module export list- Type annotations without values:
items: list[str](no assignment) - ClassVar annotations: Class-level variables that are explicitly typed as ClassVar
# These ARE flagged as mutable global state
cache = {}
pending_items = []
active_connections = set()
counters = defaultdict(int)
# These are NOT flagged
ALLOWED_EXTENSIONS = ["py", "txt"] # UPPER_CASE constant
__all__ = ["func1", "func2"] # Standard export list
items: list[str] # Type annotation onlyHow to fix
Use functions or classes to encapsulate state
# Before - mutable global state
cache = {}
def get_data(key):
if key not in cache:
cache[key] = fetch_from_database(key)
return cache[key]
# After - encapsulate in a class
class DataCache:
def __init__(self):
self._cache = {}
def get(self, key):
if key not in self._cache:
self._cache[key] = fetch_from_database(key)
return self._cache[key]
# Create instance where needed
cache = DataCache()Use function-local state or closures
# Before - global mutable state
results = []
def process_item(item):
result = transform(item)
results.append(result)
return result
# After - return values instead of mutating globals
def process_item(item):
return transform(item)
def process_all(items):
return [process_item(item) for item in items]Use dependency injection
# Before - hidden dependency on global state
_config = {}
def initialize(settings):
global _config
_config = settings
def get_setting(key):
return _config[key]
# After - explicit dependency injection
def create_app(config):
def get_setting(key):
return config[key]
return get_settingUse UPPER_CASE for intentional constants
# If you intentionally want a module-level collection that won't change,
# use UPPER_CASE to signal it's a constant (check will ignore it)
SUPPORTED_FORMATS = ["json", "yaml", "toml"]
DEFAULT_SETTINGS = {"timeout": 30, "retries": 3}Use functools.lru_cache for memoization
from functools import lru_cache
# Before - manual cache as global dict
_cache = {}
def expensive_computation(arg):
if arg not in _cache:
_cache[arg] = do_expensive_work(arg)
return _cache[arg]
# After - use lru_cache decorator
@lru_cache(maxsize=128)
def expensive_computation(arg):
return do_expensive_work(arg)Why WARNING severity?
This check is a WARNING because:
- Some legitimate use cases exist for module-level state (singletons, registries)
- Legacy code may rely on this pattern
- The check cannot determine if the state is truly problematic
However, you should prefer explicit state management for:
- Better testability
- Thread safety
- Code clarity
Configuration
Skip this check
[tool.pycmdcheck]
skip = ["CQ013"]CLI
pycmdcheck --skip CQ013