Pythonische Art, Kompositionsaliasnamen zu erstellen

8

Was ist die pythonischste und korrekteste Art, Kompositionsaliasnamen zu erstellen?

Hier ist ein hypothetisches Szenario:

class House:
    def cleanup(self, arg1, arg2, kwarg1=False):
        # do something

class Person:
    def __init__(self, house): 
        self.house = house
        # aliases house.cleanup
        # 1.
        self.cleanup_house = self.house.cleanup

    # 2.
    def cleanup_house(self, arg1, arg2, kwarg1=False):
        return self.house.cleanup(arg1=arg1, arg2=arg2, kwarg1=kwarg1)

AFAIK mit # 1 meine getesteten Editoren verstehen diese genauso gut wie # 2 - automatische Vervollständigung, Dokumentzeichenfolgen usw.

Gibt es irgendwelche Nachteile bei # 1 Ansatz? Welcher Weg ist aus Pythons Sicht korrekter?

Eine Erweiterung der Methode Nr. 1, die nicht einstellbar und vom Typ angedeutet ist, wäre immun gegen alle in den Kommentaren genannten Probleme:

class House:

    def cleanup(self, arg1, arg2, kwarg1=False):
        """clean house is nice to live in!"""
        pass


class Person:
    def __init__(self, house: House):
        self._house = house
        # aliases
        self.cleanup_house = self.house.cleanup

    @property
    def house(self):
        return self._house
Granitosaurus
quelle
5
Was ist, wenn eine Person ein neues Haus bekommt?
user2357112 unterstützt Monica
Nicht in dieser Wirtschaft! Scherz beiseite Ich denke, das Hausattribut uneinstellbar zu machen und einen Setter hinzuzufügen, würde es tun. Die Frage ist nun, was einfacher zu pflegen ist - dies oder das Umschreiben von Signaturen vieler Methoden?
Granitosaurus
2
# 2 stimmt mit dem überein, was man intuitiv erwarten würde Person.cleanup_house. Es bekommt das Haus, das mit dieser Person verbunden ist, und räumt es auf. # 1 wird ein Haus aufräumen, und zu einem bestimmten Zeitpunkt (Initialisierung) war dieses Haus das richtige. Es geht nicht gut mit der Person, die das Haus wechselt, oder wenn eine Person kein Haus hat
Hymns For Disco

Antworten:

8

Bei der ersten Methode gibt es eine Reihe von Problemen:

  1. Der Alias ​​wird nicht aktualisiert, wenn sich das Attribut, auf das er sich bezieht, ändert, es sei denn, Sie springen durch zusätzliche Rahmen. Sie könnten zum Beispiel houseeine propertymit einem Setter machen, aber das ist keine triviale Arbeit für etwas, das es nicht erfordern sollte. Am Ende dieser Antwort finden Sie eine Beispielimplementierung.
  2. cleanup_housewird nicht vererbbar sein. Ein in einer Klasse definiertes Funktionsobjekt ist ein Nicht-Daten-Deskriptor, der vererbt und überschrieben sowie an eine Instanz gebunden werden kann. Ein Instanzattribut wie im ersten Ansatz ist in der Klasse überhaupt nicht vorhanden. Die Tatsache, dass es sich um eine gebundene Methode handelt, ist zufällig. Eine untergeordnete Klasse kann beispielsweise nicht darauf zugreifen super().cleanup_house.
  3. person.cleanup_house.__name__ != 'cleanup_house'. Dies überprüfen Sie nicht oft, aber wenn Sie dies tun, erwarten Sie, dass der Funktionsname lautet cleanup.

Die gute Nachricht ist, dass Sie Signaturen nicht mehrmals wiederholen müssen, um Ansatz 2 zu verwenden. Python bietet die sehr praktische Notation splat ( *) / splatty-splat ( **) zum Delegieren aller Argumentprüfungen an die zu verpackende Methode:

def cleanup_house(self, *args, **kwargs):
    return self.house.cleanup(*args, **kwargs)

Und das ist es. Alle regulären und Standardargumente werden unverändert übergeben.

Dies ist der Grund, warum # 2 bei weitem der pythonischere Ansatz ist. Ich habe keine Ahnung, wie es mit Editoren interagieren wird, die Typhinweise unterstützen, es sei denn, Sie kopieren die Methodensignatur.

Eine Sache, die ein Problem sein kann, ist, dass cleanup_house.__doc__es nicht dasselbe ist wie house.cleanup.__doc__. Dies könnte möglicherweise eine Konvertierung von housea verdienen property, deren Setter zuweist cleanup_house.__doc__.


Um das Problem 1. (aber nicht 2. oder 3.) zu beheben, können Sie es houseals Eigenschaft mit einem Setter implementieren . Die Idee ist, die Aliase zu aktualisieren, wenn sich das houseAttribut ändert. Dies ist im Allgemeinen keine gute Idee, aber hier ist eine alternative Implementierung zu dem, was Sie in der Frage haben, die wahrscheinlich ein bisschen besser funktionieren wird:

class House:
    def cleanup(self, arg1, arg2, kwarg1=False):
        """clean house is nice to live in!"""
        pass


class Person:
    def __init__(self, house: House):
        self.house = house  # use the property here

    @property
    def house(self):
        return self._house

    @house.setter
    def house(self, value):
        self._house = house
        self.cleanup_house = self._house.cleanup
Verrückter Physiker
quelle
Es ist jedoch *args, **kwargsschwierig, mit Funktionen zur automatischen Vervollständigung von Unterbrechungen zu arbeiten. Sie müssen Docstrings auch zweimal definieren house.cleanupund / person.cleanup_houseoder irgendwie hacken. Bei diesem Ansatz müssen Sie die Signaturen und Dokumentzeichenfolgen ständig manuell verwalten, was in großen Programmen eine Menge Arbeit bedeuten kann.
Granitosaurus
2
@ Granitosaurus. Ich bin mir nicht sicher wie *argsund bin **kwargsumständlich. Sie sind buchstäblich da, sodass Sie keine Unterschriften pflegen müssen. __doc__kann durch direkte Zuordnung gepflegt werden.
Mad Physicist
Nun, Argumente und Wargs verschleiern die Signatur und die Redakteure wissen nicht, was die erwarteten Werte sind - etwas umständlich zu arbeiten. Obwohl es dieses Problem scheint kann irgendwie durch Zugabe gelöst functool.wraps(<parent_func>)Dekorateur, obwohl das einleitet als auch viele Ungereimtheiten.
Granitosaurus
Ich bevorzuge wirklich Ihren vorgeschlagenen Ansatz mit Setzern! Ich habe es gerade getestet und es ist voll funktionsfähig mit den erwarteten Editorfunktionen (automatische Vervollständigung, Signaturen und Dokumente, zumindest auf PyCharm)!
Granitosaurus
@ Granitosaurus. Solange Sie sich der funktionalen Probleme bewusst sind, liegt es ganz bei Ihnen, welchen Ansatz Sie wählen.
Mad Physicist
4

Ich wollte hier nur einen weiteren Ansatz hinzufügen: Wenn Sie houseöffentlich und einstellbar sein möchten (ich würde so etwas im Allgemeinen als unveränderlich behandeln), können Sie cleanup_housedie Immobilie wie folgt gestalten:

class House:
    def cleanup(self, arg1, arg2, kwarg1=False):
        """clean house is nice to live in!"""
        print('cleaning house')


class Person:
    def __init__(self, house: House):
        self.house = house

    @property
    def cleanup_house(self):
        return self.house.cleanup

Zumindest in einer Ipython-REPL scheinen die Code-Vervollständigung und die Dokumentzeichenfolge so zu funktionieren, wie Sie es sich erhoffen. Beachten Sie, wie es mit Typanmerkungen interagieren würde ...

EDIT: Also, mypy 0.740 kann zumindest nicht auf die Typensignatur von schließen person.cleanup_house, also ist das nicht großartig, obwohl es nicht überraschend ist:

(py38) Juans-MBP:workspace juan$ cat test_mypy.py
class House:
    def cleanup(self, arg1:int, arg2:bool):
        """clean house is nice to live in!"""
        print('cleaning house')


class Person:
    house: House
    def __init__(self, house: House):
        self.house = house  # use the property here

    @property
    def cleanup_house(self):
        return self.house.cleanup

person = Person(House())
person.cleanup_house(1, True)
person.cleanup_house('Foo', 'Bar')
reveal_type(person.cleanup_house)
reveal_type(person.house.cleanup)
(py38) Juans-MBP:workspace juan$ mypy test_mypy.py
test_mypy.py:19: note: Revealed type is 'Any'
test_mypy.py:20: note: Revealed type is 'def (arg1: builtins.int, arg2: builtins.bool) -> Any'

Ich würde immer noch einfach mit # 2 gehen.

juanpa.arrivillaga
quelle
1
Dies übertrifft meine Idee, einen Nicht-Daten-Deskriptor zu schreiben, der wie eine Wrapper-Funktion fungiert, aber auch die Anmerkungen und Signaturen des Ziels weiterleitet.
Mad Physicist