2017-07-17 2 views
2

In mehreren meiner Klassen, möchte ich mit dem Code am Ende wie folgt sowohl __str__ und __repr__ und in der Regel implementieren:Wie man richtig sowohl __str__ implementieren und __repr__

class MyClass(object): 
    def __init__(self, a): 
     self.a = a 

    def __str__(self): 
     return 'MyClass({})'.format(self.a) 

    def __repr__(self): 
     return 'MyClass({!r})'.format(self.a) 

Was ich erwarten würde das tut, was:

>>> myobject = MyClass(np.array([1, 2])) 
>>> str(myobject) 
'MyClass([1 2])' 
>>> repr(myobject) 
'MyClass(array([1, 2]))' 

jedoch der Code verletzt DRY und als die Anzahl der Argumente, dies zu wachsen beginnt beibehalten wird mühsam und ich habe festgestellt, oft vor, dass entweder von __str__ oder __repr__ hat „out of sync“ kommen mit der anderen Seite.

Gibt es eine bessere Möglichkeit, gleichzeitig __str__ und __repr__ ohne Duplizierung zu implementieren?

+1

Ist es wirklich notwendig, dass 'str' und' repr' zwei verschiedene Strings zurückgeben? –

+1

Sie könnten einfach "__str__ = __repr__" (oder umgekehrt) sagen, um die Implementierung zu kopieren. – jasonharper

+0

Es ist nicht notwendig, dass sie unterschiedliche Zeichenfolge zurückgeben, aber oft vorzuziehen. Vielleicht ist dieses Beispiel einfach zu vermitteln. –

Antwort

1

Keine Notwendigkeit für die Duplizierung, implementieren Sie einfach nicht __str__.

Auf diese Weise verhält sich das Objekt wie __str__ = __repr__.

Ich denke, Sie sollten auch this answer lesen.

+0

Ich bin mir der Möglichkeit bewusst, nicht zu duplizieren, aber für "große" Objekte kann der Unterschied, hier nur "Array (...)", ziemlich groß sein und sowohl str als auch repr können nützlich sein. –

+0

Dann folgen Sie der '__repr__' ist eindeutig' __str__' ist lesbare Richtlinie. Die Implementierung ist bis zu Ihrer Definition von eindeutig und lesbar. – nutmeg64

2

Es gibt keine Regeln oder klare Richtlinien für die Implementierung __str__ und __repr__-zumindest keine, die überall konsistent gefolgt werden (nicht einmal in der stdlib). Es gäbe also keine Möglichkeit, das "Standardverhalten" automatisch zu erhalten, einfach weil es kein Standardverhalten gibt. Es liegt an Ihnen, also wenn Sie Richtlinien für sich selbst festlegen, können Sie vielleicht auch ein Dienstprogramm entwickeln, um es Ihnen leichter zu machen, ihnen zu folgen.

In Ihrem Fall könnten Sie zum Beispiel eine Basisklasse erstellen, die die __str__ und __repr__ Implementierungen bietet:

class AutoStRepr(object): 
    _args = [] 
    def __repr__(self): 
     return '{}({})'.format(type(self).__name__, 
      ', '.join(repr(getattr(self, a)) for a in self._args)) 
    def __str__(self): 
     return '{}({})'.format(type(self).__name__, 
      ', '.join(str(getattr(self, a)) for a in self._args)) 

Sie könnten dann auf einer Reihe von verschiedenen Arten verwenden, dass:

class MyClass(AutoStRepr): 
    _args = ['a'] 
    def __init__(self, a): 
     self.a = a 

class MyOtherClass(AutoStRepr): 
    _args = ['a', 'bc'] 
    def __init__(self, a, b, c): 
     self.a = a 
     self.bc = b * c 
>>> MyClass('foo') 
MyClass('foo') 
>>> MyOtherClass('foo', 2, 5) 
MyOtherClass('foo', 10) 
+0

Keine schlechte Antwort, aber in gewisser Weise verstößt dies ebenso gegen das DRY-Prinzip wie die ursprüngliche Lösung - Sie möchten diese beiden massiven '.formate wahrscheinlich in eine Hilfsfunktion umwandeln. –

+0

@Rawing Diese "massiven Joins"? Wie sind sie massiv? Es ist nur eine Verknüpfung über einen einfachen Generatorausdruck. Und nein, das ist keine Verletzung von DRY; Jede Lösung, die das Wiederholen vermeidet, würde nur eine verrückte Menge an Komplexität hinzufügen, die keinen wirklichen Nutzen hat. Bei DRY geht es darum, die gleiche Logik immer wieder zu vermeiden. Dies hier ist * zwei mal * und es ist eigentlich eine andere Logik, die am Ende noch mehr divergieren könnte. – poke

2

Da Ihre __str__ und __repr__ dem gleichen Muster folgen, könnten Sie Schreiben Sie eine Funktion, um die String-Repräsentation für Sie zu erstellen. Es wäre ein Objekt, eine Liste der Attribute nehmen und str oder repr als Argument:

def stringify(obj, attrs, strfunc): 
    values = [] 
    # get each attribute's value and convert it to a string 
    for attr in attrs: 
     value = getattr(obj, attr) 
     values.append(strfunc(value)) 

    # get the class name 
    clsname = type(obj).__name__ 

    # put everything together 
    args = ', '.join(values) 
    return '{}({})'.format(clsname, args) 

print(stringify(MyClass('foo'), ['a'], repr)) 
# output: MyClass('foo') 

Ich würde empfehlen, diese Funktion in einer Klasse setzen, die Sie dann erben von:

class Printable: 
    def __str__(self): 
     return self.__stringify(str) 

    def __repr__(self): 
     return self.__stringify(repr) 

    def __stringify(self, strfunc): 
     values = [] 
     for attr in self._attributes: 
      value = getattr(self, attr) 
      values.append(strfunc(value)) 

     clsname = type(self).__name__ 
     args = ', '.join(values) 
     return '{}({})'.format(clsname, args) 

class MyClass(Printable): 
    _attributes = ['a'] 

    def __init__(self, a): 
     self.a = a 

Und Sie können es sogar vollautomatisch erledigen, indem Sie die Attribute direkt aus der Signatur der __init__ Funktion übernehmen:

import inspect 

class AutoPrintable: 
    def __str__(self): 
     return self.__stringify(str) 

    def __repr__(self): 
     return self.__stringify(repr) 

    def __stringify(self, strfunc): 
     sig= inspect.signature(self.__init__) 
     values= [] 
     for attr in sig.parameters: 
      value= getattr(self, attr) 
      values.append(strfunc(value)) 

     clsname= type(self).__name__ 
     args= ', '.join(values) 
     return '{}({})'.format(clsname, args) 

class MyClass(AutoPrintable): 
    def __init__(self, a, b): 
     self.a = a 
     self.b = b 

print(str(MyClass('foo', 'bar'))) # output: MyClass(foo, bar) 
print(repr(MyClass('foo', 'bar'))) # output: MyClass('foo', 'bar') 
Verwandte Themen