BFS-enhanced method lookup for Python classes
Project description
BFSMRO
⚠️ Note: This is a pet project for practicing advanced Python concepts like metaprogramming, context managers, and dynamic method resolution.
It is not intended for production use. Features like__slots__,property, and full descriptor support are not implemented.
BFS-enhanced method lookup for Python classes and instances.
This context manager allows a class or instance to access @classmethod, @staticmethod, and instance methods from its subclasses — even if they’re not in the MRO.
It uses Breadth-First Search (BFS) to find methods downward in the inheritance tree, while preserving normal MRO lookup (upward) as the first priority.
Ideal for dynamic plugin systems, framework extensions, and exploratory programming.
🔧 Features
- ✅ Works on classes and instances
- ✅ Supports
@classmethod,@staticmethod, and instance methods - ✅ Lookup order: MRO first (up), then BFS in subclasses (down)
- ✅ Opt-in
debugmode: logs lookup and enhances error messages - ✅ Thread-safe mode available
- ✅ Zero changes to existing classes
💡 Why BFSMRO?
Python's standard MRO only searches upward in the inheritance tree. BFSMRO adds downward lookup while maintaining:
- Backward Compatibility: Normal MRO has priority
- Binding Consistency: Methods use caller's context (
self/cls) - Dynamic Discovery: Find methods added after class definition
This enables novel patterns like:
from bfs_mro import BFSMRO
# Framework core remains unchanged
class Core: pass
class PluginA(Core):
def feature_a(self):
return f"Feature A from plugin, used by {self.__class__.__name__}"
class PluginB(Core):
def feature_b(self):
return f"Feature B from plugin, used by {self.__class__.__name__}"
# Core can now access plugin features
with BFSMRO(Core()) as enhanced:
print(enhanced.feature_a()) # → "Feature A from plugin, used by Core"
print(enhanced.feature_b()) # → "Feature B from plugin, used by Core"
Perfect for plugin architectures, testing scenarios, and exploratory programming where you want to access subclass functionality dynamically
🚀 Usage Guide
⚠️ Critical Limitation: Name Shadowing
Due to Python's scoping rules, using the same name in both the expression and as clause causes issues:
Classes for following examples:
from bfs_mro import BFSMRO
class Wizard: pass
class WhiteWizard(Wizard):
@classmethod
def cast_light(cls):
return f"{cls.__name__} casts light"
def heal(self):
return f"A {self.__class__.__name__} heals"
Outside functions: works once, then breaks subsequent usage:
with BFSMRO(Wizard) as Wizard:
print(Wizard.cast_light()) # This works
# But now Wizard is the proxy object, not the class!
wizard = Wizard() # ❌ TypeError: 'UniversalProxy' object is not callable
with BFSMRO(wizard) as wizard:
print(wizard.heal())
Inside functions: fails immediately:
def bad_example():
with BFSMRO(Wizard) as Wizard: # "UnboundLocalError: cannot access local variable 'Wizard' where it is not associated with a value"
Wizard.cast_light()
bad_example()
✅ Solution 1: Use Different Names
Use different name in the as clause:
def good_example():
with BFSMRO(Wizard) as EnhancedWizard:
assert EnhancedWizard.cast_light() == "Wizard casts light"
wizard = Wizard()
with BFSMRO(wizard) as enhanced_wizard:
assert enhanced_wizard.heal() == "A Wizard heals"
✅ Solution 2: Preserve Original Name
Store the original class/instance in a temporary variable:
_Wizard = Wizard
def good_example():
with BFSMRO(_Wizard) as Wizard:
assert Wizard.cast_light() == "Wizard casts light"
wizard = _Wizard()
with BFSMRO(wizard) as wizard:
assert wizard.heal() == "A Wizard heals"
🔍 Method Binding Semantics
BFSMRO preserves Python's standard method binding behavior - the calling object determines cls/self, not the defining class:
class Wizard: pass
class WhiteWizard(Wizard):
@classmethod
def whoami(cls):
return f"Method called on {cls.__name__}"
def instance_class(self):
return f"Instance of {self.__class__.__name__}"
# Class method: cls = Wizard (the enhanced class, not WhiteWizard!)
with BFSMRO(Wizard) as EnhancedWizard:
print(EnhancedWizard.whoami()) # → "Method called on Wizard"
# Instance method: self = Wizard() (the enhanced instance)
wizard = Wizard()
with BFSMRO(wizard) as enhanced_wizard:
print(enhanced_wizard.instance_class()) # → "Instance of Wizard"
This matches Python's normal behavior. Just like super().__init__() passes the current self to the parent method, BFSMRO passes the enhanced object to borrowed methods.
📦 Installation
pip install bfs-mro
🛠 Development
# Clone and install in dev mode
git clone https://github.com/AlexShchW/bfs_mro.git
cd bfs_mro
pip install -e .[dev]
# Run tests
pytest
📄 License MIT — see LICENSE file.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bfs_mro-0.1.2.tar.gz.
File metadata
- Download URL: bfs_mro-0.1.2.tar.gz
- Upload date:
- Size: 6.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1506e423e793ca316420fb7f39c4eb68ee4724eef8db27502784c9bdf23d0eb4
|
|
| MD5 |
5f64fc62fac92fc17ff3f531654facdb
|
|
| BLAKE2b-256 |
949f6ae10d2acd805c4dea923730ef9b867b4e91defd1e1c6774d73ba6513bc1
|
File details
Details for the file bfs_mro-0.1.2-py3-none-any.whl.
File metadata
- Download URL: bfs_mro-0.1.2-py3-none-any.whl
- Upload date:
- Size: 5.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5b53338cf929a5960d9c15b503db2161b0d19001ca6eeff3c4183975e19d0f1a
|
|
| MD5 |
a0bc8fb3300c1e4ea73de2e2f492bb95
|
|
| BLAKE2b-256 |
e0d762b163ab9ad86670a2bd29f5b6484f6a501c3c28d6c43f9468d13cd66148
|