I'm working on a tool that would benefit from the ability to track all references to a given object from within python.
Specifically, I'd like to make a test doubles system that could replace all module-level attributes of a given type. For example, assume the following code is in module c:
from a import b
If a is a module, b is a reference to the object named a.b, but it is a separate reference. If my test double system later replaces a.b, c.b will still refer to the original object.
I would like to have my tool track all assignments of a.b to aliases, but module-level aliasing would go a long way toward my goal.
Metaphorically, what I'd like is to override Module.__setattribute__
:
def __setattribute__(self, name, value):
if isinstance(value, interesting_types):
# remember this use of the interesting object and call super for normal processing.
Assume that I can get my code loaded before modules which might be tracked can be loaded.
This sort of thing may work for you. First, some code:
a.py
b = 42
# other module definitions
fakery.py
class Fakery(object):
def __init__(self, mod):
self.module = __import__(mod)
import sys
sys.modules[self.module.__name__] = self
def __getattr__(self, name):
print "__getattr__ called with '%s'" % name
result = getattr(self.module, name)
if name == 'b':
result += 1
return result
Example
>>> import fakery
>>> fakery.Fakery('a')
<fakery.Fakery object at 0x109007110>
>>> from a import b
__getattr__ called with '__path__'
__getattr__ called with 'b'
>>> print b
43
>>> import a
>>> print a
<fakery.Fakery object at 0x109007110>
All you would have to do is modify the Fakery
class to do whatever it is that you want to do. In this case, I'm just adding 1 to a
's b
.
I hope it's clear how this works, but here's a quick explanation.
When a module is imported, a record of it gets stuffed into sys.modules
. When you instantiate a Fakery
object, what it does is import a module by name (using __import__
), and then replaces that module's entry in sys.modules
with itself.
Python only imports modules once, storing the imported module in sys.modules
. Every import after the first returns the entry in sys.modules. The Fakery
object inserts itself into sys.modules['a']
, taking the place of the real module. All subsequent import a
or from a import <whatever>
statements are now directed to an instance of Fakery
. Since it's just a class, you can do all kinds of crazy stuff using magic methods or metaprogramming.
__getattr__
is convenient because it gets called when a requested attribute doesn't exist.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With