155 Stimmen

Sollte ich in Python die Namensverwirrung verwenden?

In anderen Sprachen lautet eine allgemeine Richtlinie, die zur Erzeugung von besserem Code beiträgt, immer alles so versteckt wie möglich zu machen. Wenn Sie sich unsicher sind, ob eine Variable privat oder geschützt sein sollte, ist es besser, sie privat zu verwenden.

Gilt das Gleiche auch für Python? Sollte ich zuerst bei allem zwei führende Unterstriche verwenden und sie nur weniger versteckt machen (nur ein Unterstrich), wenn ich sie brauche?

Wenn die Konvention nur einen Unterstrich zu verwenden ist, würde ich auch gerne den Grund dafür wissen.

Hier ist ein Kommentar, den ich zu JBernardos Antwort hinterlassen habe. Es erläutert, warum ich diese Frage gestellt habe und auch warum ich wissen möchte, warum Python sich von den anderen Sprachen unterscheidet:

Ich komme aus Sprachen, die dich dazu erziehen, zu denken, dass alles nur so öffentlich sein sollte, wie nötig und nicht mehr. Der Grundgedanke ist, dass dies die Abhängigkeiten reduzieren und den Code sicherer machen wird. Die Python-Methode, die Dinge umzukehren - von öffentlich zu versteckt zu gehen - ist für mich seltsam.

250voto

brandizzi Punkte 24822

Bei Zweifel, lass es "public" - das heißt, füge nichts hinzu, um den Namen deines Attributs zu verbergen. Wenn Sie eine Klasse mit einem internen Wert haben, kümmern Sie sich nicht darum. Anstatt zu schreiben:

class Stack(object):

    def __init__(self):
        self.__storage = [] # Zu steif

    def push(self, value):
        self.__storage.append(value)

Schreibe stattdessen standardmäßig folgendes:

class Stack(object):

    def __init__(self):
        self.storage = [] # Kein Verstümmeln

    def push(self, value):
        self.storage.append(value)

Dies ist sicherlich ein kontroverser Weg, Dinge zu tun. Python-Neulinge hassen es, und auch einige alte Python-Leute verachten dieses Standardverhalten - aber es ist sowieso der Standard, daher empfehle ich Ihnen, ihm zu folgen, auch wenn Sie sich unwohl fühlen.

Wenn Sie wirklich die Nachricht "Kann nicht berührt werden!" an Ihre Benutzer senden möchten, ist der übliche Weg, die Variable mit einem Unterstrich voranzustellen. Dies ist nur eine Konvention, aber die Leute verstehen es und achten besonders darauf, wenn sie mit solchen Dingen umgehen:

class Stack(object):

    def __init__(self):
        self._storage = [] # Das ist in Ordnung, aber die Pythonistas verwenden es, um entspannter zu sein

    def push(self, value):
        self._storage.append(value)

Dies kann auch nützlich sein, um Konflikte zwischen Eigenschaftsnamen und Attributnamen zu vermeiden:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

Was ist mit dem doppelten Unterstrich? Nun, wir verwenden den doppelten Unterstrich hauptsächlich um versehentliche Überladung von Methoden und Namenskonflikte mit Attributen von Superklassen zu vermeiden. Es kann ziemlich wertvoll sein, wenn Sie eine Klasse schreiben, die viele Male erweitert werden soll.

Wenn Sie es für andere Zwecke verwenden möchten, können Sie dies tun, aber es ist weder üblich noch empfohlen.

BEARBEITEN: Warum ist das so? Nun, der übliche Python-Stil betont nicht die Privatsphäre - im Gegenteil! Es gibt viele Gründe dafür - die meisten von ihnen kontrovers... Lassen Sie uns einige davon sehen.

Eigenschaften in Python

Heutzutage verwenden die meisten OO-Sprachen den entgegengesetzten Ansatz: Was nicht verwendet werden sollte, sollte nicht sichtbar sein, daher sollten Attribute privat sein. Theoretisch würde dies wohl verwaldbarere, weniger gekoppelte Klassen ergeben, weil niemand die Werte der Objekte leichtfertig ändern würde.

Aber es ist nicht so einfach. Beispielsweise haben Java-Klassen viele Getter, die nur Werte holen und Setter, die nur Werte setzen. Sie benötigen, sagen wir, sieben Zeilen Code, um ein einziges Attribut zu deklarieren - was ein Python-Programmierer als unnötig komplex bezeichnen würde. Außerdem schreiben Sie viel Code, um ein öffentliches Feld zu erhalten, da Sie den Wert praktisch mit den Gettern und Settern ändern können.

Warum also dieser privat-nach-Standard-Politik folgen? Machen Sie Ihre Attribute einfach standardmäßig öffentlich. Natürlich ist dies in Java problematisch, denn wenn Sie beschließen, eine Validierung zu Ihrem Attribut hinzuzufügen, müssten Sie alle:

person.age = age;

in Ihrem Code zu, sagen wir,

person.setAge(age);

setAge() sein:

public void setAge(int age) {
    if (age >= 0) {
        this.age = age;
    } else {
        this.age = 0;
    }
}

In Java (und anderen Sprachen) ist also der Standard, trotzdem Getter und Setter zu verwenden, denn sie können zwar nervig zu schreiben sein, können Ihnen aber viel Zeit sparen, wenn Sie sich in der von mir beschriebenen Situation befinden.

Aber in Python müssen Sie es nicht tun, da Python Eigenschaften hat. Wenn Sie diese Klasse haben:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self.age = age

...und dann beschließen, Altersvalidierungen vorzunehmen, müssen Sie die Stücke Ihres Codes, in denen person.age = age steht, nicht ändern. Fügen Sie einfach eine Eigenschaft hinzu (wie unten gezeigt)

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

Wenn Sie das tun können und trotzdem person.age = age verwenden, warum würden Sie dann private Felder und Getter und Setter hinzufügen?

(Siehe auch Python ist nicht Java und diesen Artikel über die Nachteile der Verwendung von Gettern und Settern).

Alles ist sowieso sichtbar - und der Versuch, zu verbergen, macht die Arbeit kompliziert

Auch in Sprachen mit privaten Attributen können Sie auf sie über eine Reflection/Introspection-Bibliothek zugreifen. Und die Leute tun das viel, in Frameworks und zur Bewältigung von dringenden Bedürfnissen. Das Problem ist, dass Introspektionsbibliotheken nur einen komplizierten Weg darstellen, um das zu tun, was Sie mit öffentlichen Attributen tun könnten.

Da Python eine sehr dynamische Sprache ist, ist es kontraproduktiv, diese Last Ihren Klassen hinzuzufügen.

Das Problem ist nicht nicht sehen zu können - sondern müssen sehen zu müssen

Für einen Pythonista ist die Kapselung nicht die Unfähigkeit, die internen Details von Klassen zu sehen, sondern die Möglichkeit, sie zu vermeiden. Kapselung ist die Eigenschaft eines Komponenten, die der Benutzer ohne Rücksicht auf interne Details verwenden kann. Wenn Sie eine Komponente verwenden können, ohne sich über ihre Implementierung Gedanken machen zu müssen, dann ist sie (in der Meinung eines Python-Programmierers) kapsuliert.

Jetzt, wenn Sie eine Klasse geschrieben haben, die Sie ohne Nachdenken über Implementierungsdetails verwenden können, ist es kein Problem, wenn Sie aus irgendeinem Grund möchten, in die Klasse hineinzuschauen. Der Punkt ist: Ihre API sollte gut sein, und der Rest sind Details.

Guido hat es gesagt

Nun, das ist nicht umstritten: er hat es tatsächlich gesagt. (Suchen Sie nach "offenes Kimono.")

Dies ist Kultur

Ja, es gibt einige Gründe, aber keinen kritischen Grund. Dies ist hauptsächlich ein kultureller Aspekt des Programmierens in Python. Ehrlich gesagt könnte es auch anders sein - ist es aber nicht. Sie könnten genauso gut fragen, warum einige Sprachen standardmäßig private Attribute verwenden? Aus dem gleichen Hauptgrund wie für die Praxis in Python: weil es die Kultur dieser Sprachen ist, und jede Wahl hat Vor- und Nachteile.

Da es bereits diese Kultur gibt, ist es ratsam, ihr zu folgen. Andernfalls werden Sie von Python-Programmierern genervt sein, die Ihnen sagen, dass Sie das __ aus Ihrem Code entfernen sollen, wenn Sie eine Frage auf Stack Overflow stellen :)

37voto

Erstens - Was ist Name Mangling?

Name Mangling wird aufgerufen, wenn Sie sich in einer Klassendefinition befinden und __beliebiger_name oder __beliebiger_name_ verwenden, das heißt, zwei (oder mehr) führende Unterstriche und höchstens ein nachgestellter Unterstrich.

class Demo:
    __beliebiger_name = "__beliebiger_name"
    __beliebiger_anderer_name_ = "__beliebiger_anderer_name_"

Und jetzt:

>>> [n for n in dir(Demo) if 'beliebig' in n]
['_Demo__beliebiger_name', '_Demo__beliebiger_anderer_name_']
>>> Demo._Demo__beliebiger_name
'__beliebiger_name'
>>> Demo._Demo__beliebiger_anderer_name_
'__beliebiger_anderer_name_'

Wenn Sie zweifeln, was tun?

Der offensichtliche Zweck besteht darin, zu verhindern, dass Unterklasseattribute verwendet werden, die von der Klasse verwendet werden.

Ein möglicher Wert besteht darin, Namenskollisionen mit Unterklassebenutzern zu vermeiden, die das Verhalten überschreiben möchten, damit die Funktionalität der Elternklasse wie erwartet funktioniert. Allerdings ist das Beispiel in der Python-Dokumentation nicht Liskov-substituierbar, und mir fallen keine Beispiele ein, in denen ich das nützlich gefunden habe.

Die Nachteile sind, dass die kognitive Belastung für das Lesen und Verstehen einer Code-Basis zunimmt, insbesondere beim Debuggen, wenn man den doppelten Unterstrich im Quellcode sieht und einen veränderten Namen im Debugger sieht.

Mein persönlicher Ansatz besteht darin, es absichtlich zu vermeiden. Ich arbeite an einer sehr großen Code-Basis. Die seltenen Verwendungen davon fallen sofort auf und scheinen nicht gerechtfertigt zu sein.

Sie müssen sich dessen aber bewusst sein, um es zu erkennen, wenn Sie es sehen.

PEP 8

PEP 8, der Python-Standardbibliotheksstil-Leitfaden, sagt derzeit (gekürzt):

Es gibt Kontroversen über die Verwendung von __Namen.

Wenn Ihre Klasse zur Vererbung gedacht ist und Sie Attribute haben, die Subklassen nicht verwenden sollen, erwägen Sie, sie mit zwei führenden Unterstrichen und ohne nachgestellte Unterstriche zu benennen.

  1. Beachten Sie, dass nur der einfache Klassenname im veränderten Namen verwendet wird, sodass bei einer Unterklasse, die sowohl den gleichen Klassennamen als auch Attributnamen wählt, weiterhin Namenskollisionen auftreten können.

  2. Die Namensvermengung kann einige Verwendungen, wie Debuggen und __getattr__(), weniger bequem machen. Allerdings ist der Namensmangelalgorithmus gut dokumentiert und einfach manuell durchzuführen.

  3. Nicht jeder mag Namensmangel. Versuchen Sie, das Bedürfnis zu vermeiden, versehentliche Namenskonflikte mit der potenziellen Verwendung durch erfahrene Benutzer abzuwägen.

Wie funktioniert es?

Wenn Sie zwei Unterstriche (ohne abschließende doppelte Unterstriche) in einer Klassendefinition voranstellen, wird der Name verfälscht, und es wird ein Unterstrich gefolgt vom Klassennamen dem Objekt vorgestellt:

>>> class Foo(object):
...     __foobar = None
...     _foobaz = None
...     __fooquux__ = None
... 
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']

Beachten Sie, dass Namen nur verfälscht werden, wenn die Klassendefinition analysiert wird:

>>> Foo.__test = None
>>> Foo.__test
>>> Foo._Foo__test
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: type object 'Foo' has no attribute '_Foo__test'

Manche, die neu in Python sind, haben manchmal Schwierigkeiten zu verstehen, was passiert, wenn sie nicht manuell auf einen Namen zugreifen können, den sie in einer Klassendefinition sehen. Dies ist kein starkes Argument dagegen, aber es ist etwas zu berücksichtigen, wenn Sie ein Lernpublikum haben.

Ein Unterstrich?

Wenn die Konvention nur die Verwendung eines Unterstrichs vorsieht, würde ich auch gerne wissen, was die Begründung dafür ist.

Wenn meine Absicht ist, dass Benutzer ihre Hände von einem Attribut lassen, tendiere ich dazu, nur einen Unterstrich zu verwenden, aber das liegt daran, dass in meinem mentalen Modell Unterklassebenutzer Zugriff auf den Namen hätten (den sie ohnehin haben, da sie den verfälschten Namen leicht erkennen können).

Wenn ich Code überprüfen würde, der das Präfix __ verwendet, würde ich fragen, warum sie die Namensvermengung aufrufen, und ob sie nicht genauso gut mit einem einzelnen Unterstrich auskommen könnten, unter Berücksichtigung, dass bei Unterklassebenutzern, die die gleichen Namen für die Klass und Klassenattribut wählen, trotzdem eine Namenskollision auftreten wird.

17voto

Matt Joiner Punkte 105454

Ich würde nicht sagen, dass Übung besseren Code produziert. Sichtbarkeitsmodifikatoren lenken Sie nur von der anstehenden Aufgabe ab und zwingen als Nebeneffekt Ihre Schnittstelle dazu, so verwendet zu werden, wie Sie es beabsichtigt haben. Im Allgemeinen verhindert die Durchsetzung von Sichtbarkeit, dass Programmierer Dinge durcheinander bringen, wenn sie die Dokumentation nicht richtig gelesen haben.

Ein weit bessere Lösung ist der Weg, den Python fördert: Ihre Klassen und Variablen sollten gut dokumentiert sein und ihr Verhalten klar. Der Quellcode sollte verfügbar sein. Dies ist ein weit ausbaubarerer und zuverlässigerer Weg, Code zu schreiben.

Meine Strategie in Python ist diese:

  1. Schreiben Sie einfach die dämliche Sache, machen Sie keine Annahmen darüber, wie Ihre Daten geschützt werden sollten. Dies setzt voraus, dass Sie schreiben, um die idealen Schnittstellen für Ihre Probleme zu erstellen.
  2. Verwenden Sie einen vorangestellten Unterstrich für Dinge, die wahrscheinlich nicht extern verwendet werden und nicht Teil der normalen "Client-Code"-Schnittstelle sind.
  3. Verwenden Sie doppelten Unterstrich nur für Dinge, die rein zur Bequemlichkeit innerhalb der Klasse dienen oder erheblichen Schaden anrichten würden, wenn sie versehentlich zugänglich wären.

Vor allem sollte klar sein, was alles tut. Dokumentieren Sie es, wenn es von jemand anderem verwendet wird. Dokumentieren Sie es, wenn Sie möchten, dass es in einem Jahr nützlich ist.

Als Randnotiz: In diesen anderen Sprachen sollten Sie tatsächlich mit protected gehen: Man weiß nie, ob Ihre Klasse später geerbt wird und wofür sie verwendet wird. Am besten schützen Sie nur die Variablen, von denen Sie sicher sind, dass sie von fremdem Code nicht verwendet werden können oder sollten.

10voto

Winston Ewert Punkte 42219

Sie sollten nicht mit privaten Daten beginnen und sie nach Bedarf öffentlich machen. Vielmehr sollten Sie damit beginnen, die Schnittstelle Ihres Objekts zu ermitteln. Das heißt, Sie sollten damit beginnen herauszufinden, was die Welt sieht (die öffentlichen Dinge) und dann herausfinden, welche privaten Dinge für das passieren müssen.

In anderen Sprachen ist es schwierig, dasjenige privat zu machen, was einmal öffentlich war. Das heißt, ich würde eine Menge Code brechen, wenn ich meine Variable privat oder geschützt machen würde. Aber mit Properties in Python ist das nicht der Fall. Vielmehr kann ich die gleiche Schnittstelle beibehalten, auch wenn ich die internen Daten neu anordne.

Der Unterschied zwischen _ und __ ist, dass Python tatsächlich versucht, letzteres durchzusetzen. Natürlich bemüht es sich nicht sehr stark darum, aber es macht es schwierig. Mit _ sagt man anderen Programmierern nur, was die Absicht ist, sie können sie ignorieren auf eigene Gefahr. Das Ignorieren dieser Regel ist manchmal jedoch hilfreich. Beispiele hierfür sind Debugging, temporäre Hacks und das Arbeiten mit Third-Party-Code, der nicht dazu gedacht war, auf die Weise verwendet zu werden, wie Sie es tun.

8voto

Jonathan Sternberg Punkte 6061

Es gibt bereits viele gute Antworten darauf, aber ich werde eine weitere anbieten. Dies ist auch teilweise eine Antwort auf Leute, die immer wieder sagen, dass doppelte Unterstriche nicht privat sind (das ist es wirklich).

Wenn Sie sich Java/C# ansehen, haben beide private/protected/public. All diese sind Kompilierzeit-Konstrukte. Sie werden nur zur Kompilierungszeit durchgesetzt. Wenn Sie Reflektion in Java/C# verwenden würden, könnten Sie leicht auf private Methoden zugreifen.

Jedes Mal, wenn Sie eine Funktion in Python aufrufen, verwenden Sie implizit Reflektion. Diese Code-Stücke sind in Python gleich.

lst = []
lst.append(1)
getattr(lst, 'append')(1)

Die "Punkt"-Syntax ist nur syntaktischer Zucker für das letztere Code-Stück. Hauptsächlich weil es bereits hässlich ist, getattr mit nur einem Funktionsaufruf zu verwenden. Von da an wird es nur schlimmer.

Also damit kann es keine Java/C#-Version von private geben, da Python den Code nicht kompiliert. Java und C# können zur Laufzeit nicht überprüfen, ob eine Funktion privat oder öffentlich ist, da diese Informationen verloren gehen (und sie haben keine Kenntnis davon, von wo aus die Funktion aufgerufen wird).

Jetzt mit dieser Information ergibt die Namensverunstaltung der doppelten Unterstriche am meisten Sinn für die Erreichung von "Privatheit". Wenn also eine Funktion von der 'self'-Instanz aufgerufen wird und feststellt, dass sie mit '__' beginnt, führt sie die Namensverunstaltung einfach dort durch. Es ist nur mehr syntaktischer Zucker. Dieser syntaktische Zucker ermöglicht das Äquivalent von 'private' in einer Sprache, die nur Reflektion für den Zugriff auf Datenelemente verwendet.

Haftungsausschluss: Ich habe noch nie jemanden aus der Python-Entwicklung so etwas sagen hören. Der eigentliche Grund für das Fehlen von "privat" ist kultureller Natur, aber Sie werden auch feststellen, dass die meisten Skript-/interpretierten Sprachen kein private haben. Ein strikt durchsetzbares privat ist in nichts außerhalb der Kompilierzeit praktikabel.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X