Dispatch dinamico da una stringa (Python)

1

Supponiamo che abbia una stringa per un nome di classe (o parte di un nome di classe), e ho un'implementazione da qualche parte. Tutto quello che voglio, è essere in grado di ottenere un'istanza di una classe con il nome nella variabile.

Ad esempio,

dynamic_class_name = "Circle"  # not a constant, comes from somewhere
corresponding_class = some_magic(dynamic_class_name)
corresponding_instance = corresponding_class()

Riesco a pensare a diversi approcci dall'utilizzo della funzione __import__() , delle ricerche degli attributi del modulo, aggiungendo register alle classi, altri modi di usare gli spazi dei nomi, effettuando auto-registrazione tramite metaclassing, fino all'utilizzo dell'architettura di componenti pesanti e interfacce ...

La domanda è, quale sarebbe il modo più semplice ma elegante (comprensibile, intuitivo, regolare, non hacker) per sostituire il codice brutto come?

if dynamic_class_name == "Circle":
  inst = Circle()
elif dynamic_class_name == "Square":
  inst = Square()
...

Il bonus sarebbe avere un impatto minimo sulla capacità di IDE (come PyCharm) di dedurre tipi e istanze.

Un altro vantaggio è che non è necessario avere un elenco speciale con le classi in un unico posto, quindi è possibile inserire nuove classi.

    
posta Roman Susi 22.06.2017 - 08:49
fonte

4 risposte

6

Un'idea al di sopra della mia testa sarebbe quella di creare un dizionario che trattiene le tue lezioni. Ciò significherebbe che puoi avere l'istanziazione e la gestione degli errori su pochissime righe.

>>> class Circle():
...     pass
... 
>>> class Square():
...     pass
... 
>>> d = { "square": Square, "circle": Circle}
>>> d["square"]()
<__main__.Square instance at 0x7ffbb1408e60>

Non posso davvero dire che è molto chiaro cosa sta succedendo, ma una buona nomenclatura e alcuni commenti utili probabilmente lo allevieranno; come chiamare il tuo dizionario instantiators o qualcosa di simile. Dovrai anche raccogliere le classi in qualche modo.

Ecco alcuni suggerimenti:

Potresti potenzialmente utilizzare uno dei metodi menzionati in quelle risposte e includerlo in una funzione con un nome descrittivo.

Avendo scritto del codice dinamico di stile simile in altre lingue, trovo che tipicamente, non sorprendentemente, finisca con un codice che è più difficile da leggere e molto più complesso in generale. Per tutte le sue inelegance, la soluzione if else che hai è comprensibile per tutti coloro che conoscono anche un po 'di programmazione di base.

    
risposta data 22.06.2017 - 10:05
fonte
3

Trovo che metterli in un modulo che importi sia più pulito e non inquini lo spazio dei nomi globale. Quindi puoi utilizzare getattr per istanziarli dinamicamente.

import shapes
shape_class = getattr(shapes, dynamic_class_name.capitalize())
inst = shape_class()
    
risposta data 01.07.2017 - 11:34
fonte
0

Ho finito per utilizzare il seguente accordo:

class MyClass(object):
    variety = None
    variety_versions = {}

    @classmethod
    def register(cls):
        MyClass.variety_versions[cls.variety] = cls

    @classmethod
    def for_variety(cls, variety_id):
        return MyClass.variety_versions.get(variety_id, MyClassDefaultVariety)

...

class MyClassSomething(MyClass):
    variety = 'MyClassSomething'
    ...
MyClassSomething.register()

...
# Usage:
inst = MyClass.for_variety(dynamic_class_variety)(...)

Cioè, l'attributo class è usato per contenere un registro di classi. Il metodo di classe register viene utilizzato per aggiungere una classe al registro. Un altro metodo di classe for_variety è usato per ottenere la classe. È banale aggiungere controlli di varietà duplicati, soluzioni generalizzate per più di un attributo di varietà, ecc. La .register() può essere sostituita con il decoratore di classi.

    
risposta data 06.07.2017 - 15:02
fonte
-1

A seconda del numero di classi diverse che hai bisogno di istanziare e della complessità della logica, questo potrebbe essere un buon caso per Modello di progettazione di fabbrica . Una classe factory incapsula la logica di creazione per un gruppo di classi che condividono tutte un'interfaccia comune. Questo ha il vantaggio di essere più estendibile se in seguito si scopre che è necessario più logica per determinare quale classe deve essere istanziata. Ad esempio:

class MyClass(object):
    pass

class AClass(MyClass):
    pass

class BClass(MyClass):
   pass

class ClassFactory(object):

    def create(self, type):
        if type == "a":
            return AClass()
        elif type == "b":
            return BClass()

factory = ClassFactory()
object = factory.create("a")
isinstance(object, MyClass) # True
isinstance(object, AClass) # True
    
risposta data 06.07.2017 - 21:27
fonte

Leggi altre domande sui tag