How do I extend a class that I never instantiate myself?
Steven D'Aprano
steve at pearwood.info
Sat Oct 10 18:54:29 EDT 2015
On Sun, 11 Oct 2015 04:02 am, speeze.pearson at gmail.com wrote:
> (This is a long post, but the question is simple. Most of this is
> just me enumerating what I've already tried.)
>
> Someone wrote a library that creates and manipulates `Node`s.
> I would like to write another layer on top of this, to make
> trees that behave just like the library's trees, but whose nodes
> have some extra methods.
>
> "Subclass!" is the classic OO answer, and my first instinct.
> Problem: I never instantiate `Node` myself. `Node`s are created
> somewhere deep inside the library, and there's no way to tell it
> "don't use `Node`, use this class instead."
But you have trees of nodes returned?
Here are a few solutions:
# Solution 1: inject a new method into each and every instance in the tree.
# Does not work for __dunder__ methods, but should work for any class that
# includes a per-instance __dict__ (that's most classes apart from
# built-ins).
from types import MethodType
def foo(self):
return self.spam or self.eggs
for node in the_tree:
node.foo = MethodType(foo, node)
# Later...
for node in the_tree:
print node.foo()
# Solution 2: hack the node type of each instance.
# Should work fine for adding new methods, even __dunder__ methods.
# Depending on the original class, may or may not work for overriding
# existing methods.
class MyNode(Node):
def foo(self):
return self.spam or self.eggs
for node in the_tree:
node.__class__ = MyNode
# Later...
for node in the_tree:
print node.foo()
Solution #2 is especially good to test your team's internal procedures and
code review practices. Any code review which doesn't flag it as a "What the
hell are you doing??? Is this even legal???" change is clearly deficient.
Yes, it is legal, in fact it is not just legal but a deliberately supported
feature of Python. But it is a bit tricky to get the details right. As
written above, it should work, but for me to really be comfortable with
using this in production, I would want to have written the original class
specifically with this in mind.
While both of the above have their uses, really the *right* way to do this
is to stop being such an OOP purist. This isn't Java, we have functions for
a reason.
# Solution 3: just use a damn function. Works for any class, whether pure
# Python or an extension class. Doesn't require any special support in the
# original class, and guaranteed not to give your code reviewers a
# conniption fit.
def foo(node):
return node.spam or node.eggs
# Later...
for node in the_tree:
print foo(node)
> Does anyone know a programming pattern that solves this problem?
>
> (That's it! That's the question. Everything after this is
> descriptions of solutions I'm not happy enough with.)
>
> "Write a wrapper!" is my next instinct. Basically,
>
> class MyNode(object):
> ...
> def foo(self, *args, **kwargs):
> return self.node.foo(*args, **kwargs)
> def bar(self, *args, **kwargs):
> return self.node.bar(*args, **kwargs)
> ...
>
> This would be tedious, but acceptable, if I knew how to make
> `MyNode.parent` and `MyNode.children` return `MyNode`s
> instead of `Node`s.
class MyNode(Node):
@property
def parent(self):
parent = super(MyNode, self).parent
if parent is not None:
parent = MyNode(parent)
return parent
# etc.
Still a lot of work, and you really need to understand the original Node
class very well. This is the problem with subclassing: it requires you to
be intimately familiar with the *implementation details* of the class you
subclass.
> My last idea is that my approach is *entirely* wrong: I shouldn't
> be trying to make the tree's nodes instances of my own class --
> I should just use the existing library's `Node` class, and write
> my library in a functional style. Don't define `MyNode.foo()`,
> instead define `mylibrary.foo(my_node)`.
> I've got nothing against functional programming, but mixing it
> so closely with OO would, I think, result in a confusing,
> schizophrenic interface.
Do you find len(alist) to be confusing and schizophrenic?
> Last possibly relevant fact: the library I want to extend is
> `xml.dom.minidom`. By `Node` I mean `Element`, and the specific
> methods I want to add will modify attributes used for CSS/JavaScript.
It may be relevant. Try it and see.
--
Steven
More information about the Python-list
mailing list