private-Konvention in Python

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

hallo zusammen,

ich möchte gern richtig verstehen, wie "private" innerhalb von Python funktioniert.
Das Konzept hinter "private" ist mir klar. Ich kenne es bereits aus anderen Programmiersprachen.

Ich ziele explizit auf die Syntax ab und wähle im folgenden Code daher auch metasyntaktische Variablennamen.

Aus diversen Tutorials habe ich entnommen, dass private-Methoden durch zwei Underscores definiert werden. Also per Konvention. (z.B. hier https://draeger-it.blog/python-15-priva ... -methoden/)

Hier mal zwei Code-Schnipsel, die zu denen ich eine Frage habe:

Code: Alles auswählen

class Foo():
    def __init__(self):
        self.__baz = 1
          
    def __baz__(self):
        self.__baz = self.__baz + 1

foo = Foo()
foo.__baz()
liefert folgende Meldung

Code: Alles auswählen

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-32-a0f99160f6ab> in <module>()
      7 
      8 foo = Foo()
----> 9 foo.__baz()

AttributeError: 'Foo' object has no attribute '__baz'
soweit so gut.

Aber nun zu meiner Frage:
Warum liefert der nachfolgende Code _keine_ Fehlermeldung?
Da __sizeof__ ja auch mit zwei Underscores beginnt, würde ich erwarten, dass hier ebenfalls eine Fehlermeldung kommt.

Code: Alles auswählen

import pandas as pd
data = [['Ham', 1],
        ['Spam', 2],
        ['Eggs', 3],
        ]
df = pd.DataFrame(data, columns=['bar', 'quz'])

print(df.__sizeof__())
ich habe mir auch mal die Methode per "STRG+Klick-Ansprung" angeschaut. Aber ich erkenne dort nichts, was mir die Frage evtl. beantworten könnte.
Ebennfalls habe ich ausprobiert, ob es ggf. an den "endenden" Underscores liegt. Aber dies scheint auch nicht der Fall zu sein.

Code: Alles auswählen

    def __sizeof__(self):
        """
        Generates the total memory usage for an object that returns
        either a value or Series of values
        """
        if hasattr(self, "memory_usage"):
            mem = self.memory_usage(deep=True)
            return int(mem if is_scalar(mem) else mem.sum())

        # no memory_usage attribute, so fall back to object's 'sizeof'
        return super().__sizeof__()

Ich danke Euch schon mal im Voraus, wenn ihr mich hier ein wenig erhellen könntet.
Ich freue mich auch,, wenn ihr mir ein Stichwort nennt, was ich googeln kann, um es selbst herauszufinden.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@Buchfink,
wenn ihr mir ein Stichwort nennt, was ich googeln kann, um es selbst herauszufinden
Das Stichwort lautet "magic methods". Die vor und nachgestellten Unterstriche haben eine andere Bedeutung und Funktionalität als die allein vorangestellten Unterstriche.
https://www.python-kurs.eu/python3_magi ... thoden.php
Benutzeravatar
__blackjack__
User
Beiträge: 13071
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Es gibt in Python kein „private“, insbesondere sind das *nicht* zwei führende Unterstriche. Es gibt die Konvention für Implementierungsdetails *einen* führenden Unterstrich zu verwenden, um zu signalisieren, dass man das nicht verwenden sollte. Aus dem von Dir verlinkten Tutorial: „Da sich nicht alle Programmierer an diese Regeln halten, sollten wir also sicherstellen das dieser Wert korrekt manipuliert werden kann.“ — Nein! Das muss man überhaupt nicht sicherstellen und das kann man auch gar nicht. Programmierer die sich nicht daran halten, haben entweder einen guten Grund oder eben Pech gehabt, wenn ihnen das irgendwann auf die Füsse fällt.

Denn auch bei den doppelten führenden Unterstrichen muss isch der Programmierer nicht daran halten, denn auch solche Attribute sind noch problemlos erreichbar, nur unter einem leicht anderen Namen:

Code: Alles auswählen

In [39]: class Foo: 
    ...:     def __init__(self): 
    ...:         self.__baz = 1 
    ...:                                                                        

In [40]: foo = Foo()                                                            

In [41]: foo._Foo__baz                                                          
Out[41]: 1
Auch Zuweisung eines anderen Wertes ist so problemlos möglich, und die Regel wie dieser tatsächliche Attributname gebildet wird, durch das hinzufügen des Klassennamens ist spezifiziert. Das ist also grundsätzlich genau so, in jeder Python-Implementierung. Das ist also alles andere als eine Hürde die jemanden, der da dran kommen will, ernsthaft daran hindern würde. Nix mit „private“.

Dieser Mechanismus ist dafür gedacht Namenskollisionen bei sehr tiefen Vererbungshierarchien oder bei Mehrfachvererbung zu verhindern. Beides wird in Python nur sehr selten gemacht, entsprechend selten sind auch die Grüne dieses „name mangling“ mit den beiden führenden Unterstrichen zu verwenden.

Die meisten Tutorials die das als „private“ vorstellen sind von Leuten die dieses Konzept irgendwie zwanghaft auch in Python suchen und sich auf dieses Feature stürzen weil es so ähnlich zu sein scheint, aber ohne tatsächlich Zugriffsschutz zu bieten. Ich wäre bei solchen Tutorials sehr skeptisch ob die einem Python beibringen oder nicht doch eher wie man in der Programmiersprache die der Autor *eigentlich* kennt programmiert und das in Python-Syntax presst.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@rogerb

Danke für den Link. Das hat mir schon mal sehr weitergeholfen.

und @_blackjack_
Denn auch bei den doppelten führenden Unterstrichen muss isch der Programmierer nicht daran halten, denn auch solche Attribute sind noch problemlos erreichbar, nur unter einem leicht anderen Namen:
Danke für diese Info. Das hat mir irgendwie "gefehlt"

Ich fasse nochmal zusammen, was ich nun verstanden habe.

__<name>__ --> Verwendung nur im Kontext von überladenen Operatoren.
__<name> --> Verwendung nur im Kontext Mehrfachvererbung/tiefe Vererbungshierarchie (beides ist ja eher zu vermeiden)
_<name> --> Konvention, um anderen Entwicklern zu sagen, dass die Methode nicht aufrufen _sollen_

Korrekt soweit?

Ich habe nun verstanden, dass es in Python das mir bekannte Konzept aus private/protected/public nicht gibt.
Aus einer anderen Programmiersprache kenne ich die Problematik, dass dort zwischen "private" und "strict private" unterschieden wird. Wenn man sich da vertut, kann dies zu schwer zu findenden Fehlern führen.

Ich muss mal schauen, ob die PyCharm bemängelt, wenn man Methoden "von außen" nicht aufruft.

LG und vielen Dank für die aufschlussreichen Antworten!
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

Ich habe nun nochmal in PyCharm nachgesehen. Man bekommt einen Hinweis, wenn man foo._baz() aufruft.
Das ist sehr beruhigend :)

Code: Alles auswählen

class Foo:
    def __init__(self):
        self.__baz = 1

    def _baz(self):
        self.__baz = self.__baz + 1
        print("Hello World")


foo = Foo()
foo._baz()
Meldung
Access to a protected member _baz of a class

--> das ist gut :) Das heißt, ich schaue einfach, dass mein Code keine Hinweise/Warnungen enthält.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Du könntest noch im Style Guide for Python Code das Kapitel Descriptive: Naming Styles lesen. Auch dort findest du etwas zum Thema "Unterstriche".

Auf Dauer solltest du ohnehin den kompletten Style Guide lesen - und dich dann am besten auch noch daran halten.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@/me
danke für die Links. Ich werde mir das auf jeden Fall zeitnah anschauen.

Einige Styleguide-Regeln wurden bereits auch in den Tutorials "angerissen". Aber vermutlich ist das nicht vollumfänglich.
In PyCharm bekommt man Hinweise, wenn man sich nicht an die Konventionen hält, was ich persönlich sehr praktisch finde, weil man sich das dann gleich richtig angewöhnt.

Für VisualStudioCode muss ich das noch nachsehen, ob Konventionsverletzungen angemeckert werden.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

Hallo zusammen

@/me, danke für den Link. Hier zeigt sich einmal wieder, dass es wichtig ist, die Primärquellen zu kennen.

Ich habe das PEP 8 gestern nun einmal genauer studiert.
Vieles ist intuitiv und "habe ich schon immer so gemacht" :)
Bis auf die "private"-Thematik, passen die Python-spezifischen Konventionen auch zu dem, was ich in diversen Tutorials so mitgenommen habe.

Allerdings habe ich dennoch ein paar Fragen. (Einiges konnte ich bereits per Google selbst beantworten, aber leider nicht alles)

https://www.python.org/dev/peps/pep-000 ... mendations
When implementing ordering operations with rich comparisons, it is best to implement all six operations (__eq__, __ne__, __lt__, __le__, __gt__, __ge__) rather than relying on other code to only exercise a particular comparison.
Wie ist dieser Passus zu verstehen?
1) Wenn man A sagt muss man auch B sagen: sprich, wenn man z.B. __eg__ implementiert, sollte man auch die anderen 5 Operationen implementieren?
2) Man sollte andere Methodenbezeichner vermeiden, die z.B. einen Vergleich von Objekten bewerkstelligen?
3) 1 + 2
4) ganz anders :)

LG
__deets__
User
Beiträge: 14523
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da geht es darum, dass man logisch gesehen nur einen Operator braucht, um alle anderen darzustellen. Nehmen wir an wir haben nur <. Dann ist > trivial einfach durch Vertauschung der Operanden gegeben.

not a < b and not b < a

der == Operator. Etc pp.

Das kann man auch mit > machen, und wahrscheinlich auch Kombinationen anderer.

Nur soll man das nicht, man soll alle explizit angeben. Warum kann ich auch nicht sagen. Ich vermute es kostet einfach Zeit, rauszufinden, was der Code implementiert.

Aber genau darauf soll man sich nicht verlassen, dass jemand alles durchprobiert. Und es eben selbst anbieten. Populäre Pakete für Daten Klassen können das vereinfachen.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__deets__
dankeschön für Deine Antwort, ich verstehe das nun besser.
Allerdings habe ich direkt schon eine Rückfrage:

Was genau meinst du mit "Populäre Pakete für Daten Klassen können das vereinfachen."?
Meinst Du damit z.B. " functools.total_ordering()decorator" (von dem auch in PEP 8 die Rede ist)?

Mir fehlt immer noch etwas das konkrete Fallbeispiel. Ich werde mich gleich mal dran setzen und das mal ausprobieren. Ggf. wird es dann nochmal deutlicher.
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Buchfink hat geschrieben: Sonntag 17. Oktober 2021, 12:22 Was genau meinst du mit "Populäre Pakete für Daten Klassen können das vereinfachen."?
Das meinte er: https://docs.python.org/3/library/dataclasses.html
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@ThomasL
dankeschön. Ich schaue mir das an.
Benutzeravatar
__blackjack__
User
Beiträge: 13071
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Also bei *populäre* Pakete für Datenklassen hätte ich ja eher an `attrs` gedacht, statt an `dataclasses` die mir diese sch#@… Typannotationen andrehen wollen. ;-)

Was die Implementierung der Vergleichsoperatoren angeht: Da ist gemeint, dass man wenn man nur `__lt__()` und `__eq__()` implementiert, ``a < b`` und ``not a >= b`` nicht das gleiche Ergebnis haben müssen. Was mal ganz vorsichtig ausgedrückt, sehr überraschend ist. Solche Objekte funktionierten mit `list.sort()` und `sorted()` weil die nur ``<`` und ``==`` intern verwenden. Aber man sollte sich da halt nicht drauf verlassen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack_
Die "dataclasses" hab ich mir gestern Abend noch angeschaut. Auf den ersten Blick sieht das sehr praktisch aus. Ich kenne ähnliche Ideen aus Frameworks in anderen Sprachen.
Allerdings muss ich gestehen, dass ich in der Praxis nie einen UseCase hatte, bei dem ich das verwendet habe und den Eindruck hatte, dass es mir wahnsinnig viel Arbeit abnimmt.
Ist ein bisschen wie "Flickflack". Ich bin selten in Situationen, wo ich denke: "Jetzt hilft nur noch ein Flickflack!" :)
Die Idee dahinter ist trotzdem irgendwie schön und in Python auch durchaus eleganter gelöst, als ich das sonst so kenne.

"attrs" sagt mir leider noch gar nichts. Da werde ich nächstes WE mal googeln müssen. :) --> Dankeschön für den Hinweis.

Lieben Dank auch für die Erklärung mit "sorted". Das war der Aspekt, der mir zum theoretischen Verständnis noch gefehlt hat. Am WE muss ich das testweise mal implementieren. (Manche Sachen muss man irgendwie mal wirklich ausprobieren, um die zu kapieren)
Benutzeravatar
__blackjack__
User
Beiträge: 13071
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Also ich finde `attrs` nimmt einem deutlich Arbeit ab. Und ich sehe das auch nicht nur für eine bestimmte Art von Klassen die nur Daten enthalten, sondern das ist IMHO generell sehr nützlich. Einmal weil es einem eine Menge Boilerplate-Code schreiben abnimmt. Und es drängt auch ein bisschen in Richtung testbarerem Code, weil man ”gezwungen” oder zumindest ”angehalten” ist, mit dem eher einfachen, automatisch erzeugten `__init__()` auszukommen. ”Konstruktoren” mit Logik sind dann in Klassenmethoden. Mich persönlich muss man dazu nicht drängen, aber wenn man das sowieso schon so macht, wird `__init__()` schreiben einfach nur lästige Fleissarbeit. Genau wie `__repr__()` und die ganzen Vergleichsmethoden und `__hash__()`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack__
mit "attrs" meinst Du vermutlich das hier?
https://www.attrs.org/en/stable/examples.html

Ich werde mich mal näher mit dem Unterschied zwischen "attrs" und "dataclasses" befassen müssen. Ich habe jetzt nur kurz überflogen.
Das Argument mit der Bolilerplate kann ich vollkommen nachvollziehen und das ist auch das was ich als ästhetisch empfinde. Und dass es Tipparbeit abnimmt, kann ich auch sofort nachvollziehen.

Dass mir gestern spontan kein UseCase eingefallen ist, liegt vermutlich daran, dass ich noch nicht ganz den Einsatzzweck verstanden habe. Manches muss man eben ausprobieren, um es richtig zu verstehen.
Mich würde interessieren, in welchen Konstellationen Du das z.B. üblicherweise so einsetzt? (Sorry, ich fürchte, dass ich damit jetzt den Thread "sprenge" - bitte einfach sagen, dann lagere ich das Thema aus)
Bei pandas war es übrigens umgekehrt. Da sind mir jede Menge UseCases aus meinen Arbeitsalltag eingefallen.

Das mit der Testbarkeit ist ein guter Hinweis. Damit habe ich mich allerdings unter Python noch gar nicht befasst. (In meiner aktuellen Sprache geht das leider nur mit extrem viel Boilerplate. Ich bin schon sehr gespannt wie das unter Python funktioniert)

LG
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack__
ich hab nochmal nachgedacht und ich denke, dass ich nun verstehe, was ich _nicht_ an den DataClasses hinsichtlich Einsatzzweck verstanden habe.

Hier mal ein Erklärungsversuch:
Die Idee auf Objekt-Ebene zu vergleichen, um zu sortieren, ist sehr charmant. Und _exakt_ für diesen UseCase ist mir nicht viel eingefallen. Denn ca. 99% der Daten, die ich sortieren möchte, liegen bereits in einer Datenbank. Diese sortiere ich in der Regel bereits per SQL mit ORDER BY und habe dann in der Regel eine bereits sortiere Liste, die dann z.B. in einer Oberfläche in einem Grid visualisiert werden. Wenn diese dann innerhalb des Grids nochmal umsortiert werden, übernimmt den Vergleich wiederum die Grid-Komponente.
Allerdings scheint es ja noch mehr Dunder-Methoden zu geben, die ich ggf. noch etwas genauer ansehen muss. :)

LG
Benutzeravatar
__blackjack__
User
Beiträge: 13071
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Die übliche Konstellation in der ich das einsetze sind Klassen ganz allgemein. Ich suche da nicht nach Klassen wo ich das einsetzen könnte, sondern es kommt manchmal vor, dass ich es aus irgendwelchen Gründen *nicht* einsetze. Zum Beispiel bei GUI-Klassen wo ja in der Regel in der `__init__()` mehr passieren muss, als einfach nur Attribute zu setzen. Ansonsten ist mir alleine das mir der immer gleiche Code von `__init__()` und `__repr__()` abgenommen wird, ein Grund das fast immer einzusetzen.

Das man die dann auch sortieren kann ist ein netter Nebeneffekt, wobei es dann so etwas wie eine ”natürliche” Reihenfolge geben muss und man die Attribute auch in der Reihenfolge angeben muss, die diese Reihenfolge beim sortieren erzeugt. Wobei auch das bei der Fehlersuche oder in (Unit-)Tests nett sein kann, wenn man eine Sequenz mit Objekten in eine kanonische Reihenfolge bringen kann, ob die nun irgendwie ”natürlich” ist, oder nicht.

Was Tests angeht, unbedingt `pytest` anschauen bevor man blindlings das `unittest` aus der Standardbibliothek verwendet. Das in der Standardbibliothek bietet eine auf XUnit basierende API, die man auch von anderen Sprachen kennt. Das ist aber so ähnlich wie XML und (Mini)DOM. Die Idee die gleiche API für alle möglichen Sprachen zu haben ist nur solange nett, wie man in Python nicht Code schreiben muss der wie Java aussieht. Oder noch schlimmer, denn selbst in Java gibt es Alternativen. Wenn die API ”portierbar” sein soll, muss man sich wohl oder übel auf die kleinste Schnittmenge an Spracheigenschaften beschränken, die alle Sprachen bieten. Wobei `pytest` *auch* mit Unit-Tests klar kommt, die mit der `unittest`-API erstellt wurden. Man muss sich da nicht entscheiden, oder wenn man schon klassische Tests hat, alles wegwerfen und neu schreiben.

Mal das Beispiel aus der `unittest`-Dokumentation:

Code: Alles auswählen

import unittest


class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual("foo".upper(), "FOO")

    def test_isupper(self):
        self.assertTrue("FOO".isupper())
        self.assertFalse("Foo".isupper())

    def test_split(self):
        s = "hello world"
        self.assertEqual(s.split(), ["hello", "world"])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)
Mit `pytest`:

Code: Alles auswählen

import pytest


def test_upper():
    assert "foo".upper() == "FOO"


@pytest.mark.parametrize("text, expected", [("FOO", True), ("Foo", False)])
def test_isupper(text, expected):
    assert text.isupper() == expected


def test_split():
    text = "hello world"
    assert text.split() == ["hello", "world"]
    # check that text.split fails when the separator is not a string
    with pytest.raises(TypeError):
        text.split(2)
Wobei es hier schon einen Unterschied in der Anzahl der Testfälle gibt: `test_isupper()` sind bei der `pytest`-Variante *zwei* Tests die unabhängig voneinander durchgeführt werden. Auch wenn der erste fehlschlägt, wird der zweite noch durchgeführt.

Die `setUp()` und `tearDown()` Varianten von XUnit kann man auch nutzen, aber `pytest` führt dafür auch einen Fixture-Mechanismus ein mit dem man Funktionen per Namen registrieren kann, und die werden dann ausgeführt wenn der registrierte Name in den Argumenten des Tests (oder einer anderen Fixturefunktion) auftauchen. Das Beispiel aus der `unittest`-Doku:

Code: Alles auswählen

import unittest


class WidgetTestCase(unittest.TestCase):
    def setUp(self):
        self.widget = Widget("The widget")

    def test_default_widget_size(self):
        self.assertEqual(
            self.widget.size(), (50, 50), "incorrect default size"
        )

    def test_widget_resize(self):
        self.widget.resize(100, 150)
        self.assertEqual(
            self.widget.size(), (100, 150), "wrong size after resize"
        )

    def tearDown(self):
        self.widget.dispose()
Kann mit `pytest` so aussehen:

Code: Alles auswählen

import pytest


@pytest.fixture(name="widget")
def create_widget():
    widget = Widget("The widget")
    yield widget
    widget.dispose()


def test_default_widget_size(widget):
    assert widget.size() == (50, 50), "incorrect default size"


def test_widget_resize(widget):
    widget.resize(100, 150)
    assert widget.size == (100, 150), "wrong size after resize"
Ja, dass das über die Argumentnamen geht ist ein bisschen magisch. Quasi „dependency injection“ über Namen statt über Typen. Es hat aber beispielsweise den Vorteil, dass man Fixtures leicht kombinieren kann, denn man kann ja über weitere Argumente mit Namen mehr als ein Fixture anfordern. Das ist IMHO deutlich leichter ”composable” als die `setUp()`/`tearDown()`-Methoden. Spielzeugbeispiel:

Code: Alles auswählen

import pytest


@pytest.fixture(name="paragraph")
def create_paragraph():
    return Paragraph("Lorem ipsum…")


@pytest.fixture(name="document")
def create_document():
    return Document()


@pytest.fixture(name="filled_document")
def create_filled_document(document, paragraph):
    document.add_title("Test-Titel")
    document.body.add(paragraph)
    ...
    return document


def test_document_save(filled_document, tmpdir):
    path = tmpdir / "test.doc"
    filled_document.save(path)
    assert path.exists() and path.is_file()
    content = path.read_text(encoding="utf-8")
    assert "Test-Titel" in content
    ...
Wobei im Test `tmpdir` verwendet wird, was eines der praktischen Fixtures ist, die schon mitgeliefert werden. Da bekommt man pro Test ein leeres temporäres Verzeichnis herein gereicht, das nach dem Test samt Inhalt wieder gelöscht wird.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack__

vielen lieben Dank für die Mühe und die Hinweise! Ich werde das heute Abend genauer lesen.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack__
Ansonsten ist mir alleine das mir der immer gleiche Code von `__init__()` und `__repr__()` abgenommen wird, ein Grund das fast immer einzusetzen
Ah, verstehe. Also der umgekehrte Weg sozusagen. Ich hatte das auch kurz in Erwägung gezogen, dann aber im Hinblick auf YAGNI wieder verworfen.
Ich hab mir aber nun __repr__() näher angeschaut und bin zum Ergebnis gekommen, dass ein solcher Standard durchaus Sinn macht. Insbesondere beim Debugging/Logging könnte das nützlich sein.
oder in (Unit-)Tests nett sein kann, wenn man eine Sequenz mit Objekten in eine kanonische Reihenfolge bringen kann, ob die nun irgendwie ”natürlich” ist, oder nicht.
Ja, das stimmt natürlich. Ein nicht-deterministischer Test aufgrund einer Zufallssortierung (z.B. auf Basis der Datenbank) ist nicht nützlich.

Leider schaffe ich es heute nicht mehr, den Code zu sichten. Das mache ich morgen.
Antworten