I'm currently looking at a maintainable, and easy-to-use way of creating "views" on python objects. Specifically, i have a very large collection of classes that share a couple common methods, and i would like to wrap instances of these classes in order to modify the behaviour of these methods.
Of course, i could create for every class a new class, following the wrapper pattern, redefining the full interface and redirecting every method to the original object, except those that i wish to override. This isn't practical, due to the shear amount of code required and the maintenance required when any class would change.
Some experimentation have shown me i could generate the wrappers with heavy use of metaclasses and introspection to "recreate" the interface in a wrapper object, but this proved fairly horrendous to use and debug, especially if A's have properties (code not included)
A second attempt showed that it can be done with fairly minimal code by sharing the __dict__
attribute and overriding __class__
. This leads to the following code (https://repl.it/repls/InfantileAshamedProjector):
##############################
# Existing code
##############################
class A1:
def __init__(self, eggs):
self.eggs = eggs
# Lots of complicated functions and members
def hello(self):
print ("hello, you have %d eggs" % self.eggs)
def meeting(self):
self.hello()
print ("goodbye")
# Lots of complicated functions calling hello.
# Lots of A2, A3, A4 with the same pattern
##############################
# "Magic" code for view generation
##############################
class FutureView:
pass
def create_view(obj, name):
class View(obj.__class__):
def hello(self):
print ("hello %s, you have %d eggs" % (name, self.eggs))
view = FutureView()
view.__dict__ = obj.__dict__
view.__class__ = View
return view
##############################
# Sample of use
##############################
a = A1(3)
a.hello() # Prints hello, you have 3 eggs
v = create_view(a, "Bob")
v.hello() # Prints hello Bob, you have 3 eggs
a.eggs = 5
a.hello() # Prints hello, you have 5 eggs
v.hello() # Prints hello Bob, you have 5 eggs
a.meeting() # Prints hello, you have 5 eggs. Goodbye
v.meeting() # Prints hello Bob, you have 5 eggs. Goodbye
This makes for fairly short code, and modifying the A1, A2, etc... classes doesn't require any change to the patch, which is very nice. However, i'm obviously worried about the implications of sharing __dict__
between multiple classes. My questions are:
__dict__
?__dict__
and the __class__
?__dict__
, i'm forced to add it to the class itself, either as a class member, or as a "captured" variable. Is there any other position where i could put it? Ideally, i would like to avoid have to create a new class for every instance of name (i want to only dynamically create a new class for every original class)Other considered solutions:
Proxy objects (see juanpa.arrivillaga answer) is an almost perfect solution, but it falls short when the patched function is called internally by another function. Specifically, in the code posted above, the final calls to the meeting
function will use the original implementation instead of the patched implementation. See https://repl.it/repls/OrneryLongField for an example.
What does built-in class attribute __dict__ do in Python? A special attribute of every module is __dict__. This is the dictionary containing the module's symbol table. A dictionary or other mapping object used to store an object's (writable) attributes.
The __dict__ in Python represents a dictionary or any mapping object that is used to store the attributes of the object. They are also known as mappingproxy objects. To put it simply, every object in Python has an attribute that is denoted by __dict__.
It sounds to me like you want a proxy object, which is what a view generally is. Briefly, the pattern can be as simple (for read-only proxies) as this:
class View:
def __init__(self, obj):
self._obj = obj
def __getattr__(self, attr):
return getattr(self._obj, attr)
The nice thing about __getattr__
is that it is only called when an attribute is not found. If you want write access, then you'll need to be a bit more careful, and implement __setattribute__
which is always called, and it becomes easy to inadvertently trigger infinite recursion.
Note, because we are using getattr
on the object being proxied, we don't have to manage recreating the interface! Method resolution, the descriptor protocol (so property
), inheritance, etc is all handled by the usual machinery:
In [1]: class View:
...: def __init__(self, obj):
...: self._obj = obj
...: def __getattr__(self, attr):
...: return getattr(self._obj, attr)
...:
In [2]: class UrFoo:
...: def __init__(self, value):
...: self.value = value
...: def foo(self):
...: return self.value
...:
In [3]: class Foo(UrFoo):
...: def frognicate(self):
...: return self.value * 42
...: @property
...: def baz(self):
...: return 0
...:
In [4]: foo = Foo(8)
In [5]: view = View(foo)
In [6]: view.foo()
Out[6]: 8
In [7]: view.frognicate()
Out[7]: 336
In [8]: view.baz
Out[8]: 0
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