-1

Here are two object makers I made:

def make_assassination(i):
    neighbors = []
    def test(graph):
            for n in graph.neighbors(i):
                neighbors.append(n)
                graph.remove_edge(i, n)

    def reset(graph):
            for n in neighbors:
               graph.add_edge(n, i)

    return {'test': test, 'reset': reset, 'strategy': lambda : 'assassinate(%d)' % len(neighbors)}


def make_toggle(i, j):
    return {'edge': (i, j),
            'strategy': lambda : '',
            'test': lambda graph : toggle_edge(graph, i, j),
            'reset': lambda graph : toggle_edge(graph, i, j)
            }

Both return an object (dictionary) with keys 'test', 'reset' and a lambda 'strategy'. One object also wants to add a key 'edge'.

I was thinking of ways to convert this to something more class-oriented or typed. It would go like:

class Action(self, **kwargs):
    self.test = test
    self.reset = reset
    self.strategy = strategy

Then I'm not sure how to pass the extra edge.

I think there is a strategy where the class can do something akin to extending dictionary, or the child "classes" can access action's underlying dictionary and amend it. Coming from a Java background this sounds to get into the object's internals more than I'm sure is good design.

Another option is for an 'extra_stuff' kwarg. Also a bit of an antipattern.

There is also an argument I should just leave as a raw dictionary. In fact I am not even sure if there is an advantage to using a class at all.

Which of these patterns are acceptable design?

0

Python is a very dynamic language, so you don't have to create any particular class hierarchy. Your classes for Assassination and Toggle can be entirely independent even when they declare the same methods. You can therefore decide to add a method only to one of these classes.

While Python does not generally require it, there would be two reasons to define interfaces that are then inherited by Assassination and Toggle: if you rely on isinstance() checks in your code, or if you use an external static type checker like MyPy or a Python IDE with type-aware autocomplete suggestions. Python does not have explicit interfaces like Java, but you can create abstract base classes (ABC). Inside an ABC, you can mark methods that must be implemented with the @abstractmethod decorator. But it doesn't seem like any of these cases applies in your case.

In this particular case, the edge isn't a method but more like an instance field. In Python, instance fields are created by simply assigning them in the __init__ method.

I would implement these classes like this:

class Assassination(object):
  def __init__(self, i):
    self._neighbors = []
    self._i = i

  def strategy(self):
    return 'assasinate(%d)' % len(self._neighbors)

  def test(self, graph):
    for n in graph.neighbors(self._i):
      self._neighbors.append(n)
      graph.remove_edge(self._i, n)

  def reset(self, graph):
    for n in self._neighbors:
      graph.add_edge(n, self._i)

class Toggle(object):
  def __init__(self, i, j):
    self.edge = (i, j)

  def strategy(self):
    return ''

  def test(self, graph):
    (i, j) = self.edge
    return toggle_edge(graph, i, j)

  def reset(self, graph):
    (i, j) = self.edge
    return toggle_edge(graph, i, j)

The advantage of explicitly defining classes like this is that the available methods etc. are fairly obvious, and that you can document them easily with docstrings. Fields can't have docstrings, but we can hide them behind a property. Python's properties are like Java's setters and getters.

class Toggle(object):
  def __init__(self, i, j):
    self._edge = (i, j)

  @property
  def edge(self):
    """The edge toggled by test() and reset()"""
    return self._edge

  ...

If your class has no methods but is only a dict-like collection of instance fields, you can use an empty class and directly assign fields, they spring into existence automatically. If you want a convenient kwargs interface, you can directly access the __dict__ that underlies the object. While that would be legitimate metaprogramming, it isn't exactly obvious:

class Dynamic(object):
  def __init__(self, **kwargs):
    self.__dict__.update(kwargs)

In Python 3, the built-in types.SimpleNamespace class does exactly this, and adds equality and repr() implementations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.