Come scrivere un'interfaccia Python libera in cui le sottoclassi possono aggiungere dati extra?

-1

Ecco due creatori di oggetti che ho creato:

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)
            }

Entrambi restituiscono un oggetto (dizionario) con le chiavi "test", "reset" e una "strategia" lambda. Un oggetto vuole anche aggiungere una chiave "edge".

Stavo pensando a come convertirlo in qualcosa di più orientato alla classe o tipizzato. Sarebbe come:

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

Quindi non sono sicuro di come superare il margine extra.

Penso che esista una strategia in cui la classe può fare qualcosa di simile all'estensione del dizionario, oppure le "classi" secondarie possono accedere al dizionario sottostante dell'azione e modificarlo. Provenendo da uno sfondo Java, questo sembra entrare nell'interno dell'oggetto più di quanto io sia sicuro sia un buon design.

Un'altra opzione è per un kwarg "extra_stuff". Anche un po 'antipattern.

C'è anche un argomento che dovrei lasciare come dizionario grezzo. In effetti non sono nemmeno sicuro se c'è un vantaggio nell'usare un class .

Quali di questi modelli sono design accettabile?

    
posta djechlin 16.07.2017 - 16:35
fonte

1 risposta

0

Python è un linguaggio molto dinamico, quindi non devi creare alcuna particolare gerarchia di classi. Le tue classi per Assassination e Toggle possono essere completamente indipendenti anche quando dichiarano gli stessi metodi. Puoi quindi decidere di aggiungere un metodo solo a una di queste classi.

Sebbene Python in genere non lo richieda, ci sarebbero due ragioni per definire le interfacce che sono ereditate da Assassination e Toggle : se ti affidi a isinstance() controlli nel tuo codice, o se usi un esterno tipo di controllo di tipo statico come MyPy o un IDE Python con suggerimenti di completamento automatico in base al tipo. Python non ha interfacce esplicite come Java, ma puoi creare classi base astratte (ABC). All'interno di un ABC, puoi contrassegnare i metodi che devono essere implementati con @abstractmethod decorator. Ma non sembra che uno di questi casi si applichi nel tuo caso.

In questo caso particolare, edge non è un metodo ma è più simile a un campo di istanza. In Python, i campi di istanza vengono creati semplicemente assegnandoli nel metodo __init__ .

Vorrei implementare queste classi in questo modo:

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)

Il vantaggio di definire esplicitamente classi come questa è che i metodi disponibili, ecc. sono abbastanza ovvi e che è possibile documentarli facilmente con docstring. I campi non possono avere docstring, ma possiamo nasconderli dietro una proprietà. Le proprietà di Python sono come setter e getter di Java.

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

  ...

Se la tua classe non ha metodi ma è solo una collezione di campi di istanza dict-like, puoi usare una classe vuota e assegnare direttamente i campi, che nascono automaticamente. Se vuoi una comoda interfaccia kwargs, puoi accedere direttamente al __dict__ che sottostà all'oggetto. Anche se sarebbe metaprogrammazione legittima, non è esattamente ovvio:

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

In Python 3, la classe di types.SimpleNamespace integrata fa esattamente questo e aggiunge le implementazioni di uguaglianza e repr() .

    
risposta data 16.07.2017 - 22:07
fonte

Leggi altre domande sui tag