On Mon, Nov 23, 2020 at 8:20 AM Brian Coleman <brianfcole...@gmail.com>
wrote:

> Take as an example a function designed to process a tree of nodes similar
> to that which might be output by a JSON parser. There are 4 types of node:
>
> - A node representing JSON strings
> - A node representing JSON numbers
> - A node representing JSON arrays
> - A node representing JSON dictionaries
>
> The function transforms a tree of nodes, beginning at the root node, and
> proceeding recursively through each child node in turn. The result is a
> Python object, with the following transformation applied to each node type:
>
> - A JSON string `->` Python `str`
> - A JSON number `->` Python `float`
> - A JSON array `->` Python `list`
> - A JSON dictionary `->` Python `dict`
>
> I have implemented this function using 3 different approaches:
>
> - The visitor pattern
> - `isinstance` checks against the node type
> - Pattern matching
>

I've always thought that the alternative to a "switch case" construct in
Python (and I suppose most OO languages) is subclassing and method
overriding. I guess that's what the "visitor pattern" is here, but it seems
to be adding a bunch of unnecessary bolierplate.

So: given that you have a special "Node" object anyway, the thing to do is
to have those Node object know how to unpack themselves. Then the top
"traverse the tree" function becomes a single method or attribute access:

tree = node_tree.value

here's what the Nodes look like in this example:

class Node:
    def __init__(self, val):
        self._value = val

    @property
    def value(self):
        return self._value


class StringNode(Node):
    pass


class NumberNode(Node):
    pass


class ListNode(Node):
    @property
    def value(self):
        return [item.value for item in self._value]


class DictNode(Node):
    @property
    def value(self):
        return {k: item.value for k, item in self._value.items()}

Of course, this requires that you have control of the Node objects, rather
than getting them from some other library -- but that seems to be what all
the examples here are anyway.

If you do need to parse out a tree of object that are not "special"
already, then you need to do some type of pattern matching / isinstance
checking. In this case, I wrote a function that builds up a tree of Nodes
from arbitrary Python objects:

def make_nodes_from_obj(obj):
    if isinstance(obj, str):
        return StringNode(obj)
    if isinstance(obj, Real):
        return NumberNode(obj)
    if isinstance(obj, Sequence):
        return ListNode([make_nodes_from_obj(item) for item in obj])
    if isinstance(obj, Mapping):
        return DictNode({k: make_nodes_from_obj(item)
                         for k, item in obj.items()})

And that could benefit from pattern matching, I suppose, though it's not
very compelling to me.

And in "real world" code, I've done just this -- building a system for
saving / restoring dataclasses to/from JSON. In that case, each of the
dataclasses knows how to save itself and build itself from JSON-compatible
python objects (numbers, dicts, strings, lists) -- so again, no need for
pattern matching there either. And what I really like about the approach of
putting all the logic in the "nodes" is that I can make new types of nodes
without having to touch the code at the "top" that visits those nodes.

In short -- I'm still looking for a more compelling example :-)

-CHB

-- 

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

chris.bar...@noaa.gov
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/S6Y24VDUD5RL2SDU3LE56P3AONSAGMGM/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to