Worauf ich bei der Python-Codierung achten muss: Kommentare, Typanmerkungen, Datenklassen, Aufzählungen (Aufzählung)

Überblick

Normalerweise codiere ich in Python. Python funktioniert oft, auch wenn ich es grob schreibe, also schreibe ich es einfach richtig auf. Wenn ich diesen Code jedoch später überprüfe oder anderen zeige, erhalte ich am Ende etwas wie "Was machst du ..." oder "Wie benutzt du diese Funktion ...". Als Memorandum werde ich die Dinge auflisten, die "Wenn Sie auf diese Dinge achten, wird es bis zu einem gewissen Grad leichter zu verstehen sein".

Kommentar

** Beschreiben Sie den Zweck der Verarbeitung, anstatt den Verarbeitungsinhalt zu kommentieren **

Ich bin mir bewusst, dass ich den Verarbeitungszweck in natürlicher Sprache kommentiere, was schwierig ist, wenn ich nur den Funktionsnamen oder den Variablennamen verwende, so dass ich oft den Verarbeitungsinhalt vergesse ~~.

Der Code lautet beispielsweise wie folgt.

Kommentarbeispiel, das den Verarbeitungsinhalt beschreibt


# 1/Auf 4 reduzieren ← Kann man sich aus Funktionsnamen und Variablennamen vorstellen
small_image = resize(image, scale=1/4)

Beispielkommentar, der den Zweck der Verarbeitung beschreibt


#Schrumpfen, um einen stabilen Speicher beim Eingießen in das Modell zu gewährleisten
small_image = resize(image, scale=1/4)

Ich denke nicht, dass es falsch ist, zu kommentieren, was los ist. Ich freue mich sehr über Kommentare zum Verarbeitungsinhalt, wenn der Code mehrere Zeilen umfasst oder der Code aufgrund der Optimierung auf den ersten Blick nicht verständlich ist.

Wie im obigen Beispiel halte ich es jedoch für überflüssig, denselben Inhalt wie den Code zu kommentieren, wenn das Lesen des Codes kein Problem darstellt.

Wenn Sie einen Kommentar mit dem Bewusstsein schreiben, dass "das Motiv und der Zweck der Verarbeitung aus dem Code schwer zu folgen sind", wird der Code meiner Meinung nach leicht zu merken sein, selbst wenn Sie im Laufe der Zeit zurückblicken.

Geben Sie eine Anmerkung ein

** Es gibt keinen Verlust, nur die Funktionsargumente und Rückgabewerte auszuführen **

Die Typanmerkung in Python ist Kommentar + α, was keine Typübereinstimmung zur Laufzeit garantiert, aber ich denke, sie sollte so weit wie möglich mit Anmerkungen versehen werden.

Ein guter Ort, um Anmerkungen einzugeben

Anmerkungsbeispiel


#Sehr gute Anmerkungen zu Methodenargumenten und Rückgabewerten (kein Blick in die Methode erforderlich)
class Person:
    def __init__(self, first_name: str, last_name: str, age: int):  #Anmerkung für Methodenargumente
        self._name: str = first_name + ' ' + last_name  #Anmerkung für Variablen
        self._age = age

    def is_older_than(self, age: int) -> bool:  #Anmerkung zum Rückgabewert der Methode
        return self._age > age

Insbesondere können die Argumente und Rückgabewerte der veröffentlichten Funktionen nur verwendet werden, wenn sie mit Typanmerkungen versehen sind. Daher halte ich sie für wesentlich. Natürlich können Sie es in "docstring" schreiben. Ein ziemlich ausgefeilter Editor sollte auch "docstring" analysieren.

Geben Sie täglich verwendete Anmerkungen ein

Hier erfahren Sie, wie Sie Variablen mit integrierten Typen wie "int" und "float" sowie Variablen, die Klassen instanziieren, mit Annotationen versehen.

Tägliche Typanmerkung


age: int = 0
weight: float = 0.0
name: str = 'string'
is_student: bool = True

taro: Person = Person('taro', 'suzuki', 20)

Andere eingebaute Typen, die häufig verwendet werden, sind "list", "dict" und "tuple". Das sind die gleichen

python


friends: list = [daisuke, tomoko]
parents: tuple = (mother, father)
contacts: dict = {'email': 'xxx@mail', 'phone_number': 'XXXX'}

Sie können wie folgt kommentieren, aber Sie können das Typisierungsmodul verwenden, um detailliertere Anmerkungen zu machen. Im obigen Beispiel können Sie beispielsweise sehen, dass "Freunde" eine Liste ist, aber ich weiß nicht, welche Art von Elementen enthalten sein sollen. Hier erfahren Sie, wie Sie ein Element mit "Typing" kommentieren.

Detaillierte Annotation alltäglicher Typen durch Eingabe

Detaillierte Annotation alltäglicher Typen durch Eingabe


from typing import List, Tuple, Dict  #Importieren für Typanmerkungen

friends: List[Person] = [daisuke, tomoko]  #Liste mit Personeninstanzen als Elemente
parents: Tuple[Person, Person] = (mother, father)  #Ein Taple mit einer Personeninstanz als zwei Elementen
contacts: Dict[str, str] = {'email': 'xxx@mail', 'phone_number': 'XXXX'}  #Schlüssel ist str,Wörterbuch mit dem Wert str

typing ermöglicht detailliertere Typanmerkungen. Persönlich bin ich sehr erleichtert zu wissen, welche Art von Wörterbuchtyp ich erwarte, insbesondere wenn die Typanmerkung von "Dict" Anmerkungen zu Schlüssel und Wert enthält.

Diese können auch in einer verschachtelten Struktur verschachtelt sein. Beispielsweise können einige Personen mehrere E-Mail-Adressen und Telefonnummern haben. Dann möchten Sie, dass der Wert von "Kontakte" "Liste [str]" anstelle von "str" ist. Zu jener Zeit

python


#Schlüssel ist str,Ein Wörterbuch, in dem value eine Liste von str ist
contacts: Dict[str, List[str]] = 
    {
        'email': 
            ['xxx@mail', 'yyy@mail'], 
        'phone_number':
            ['XXXX', 'YYYY']
     }

Es ist möglich, wie folgt zu kommentieren.

Bequeme Typanmerkung durch Eingabe

typing ermöglicht verschiedene andere als die oben aufgeführten Anmerkungen. Ich werde "Union" und "Optional" als diejenigen vorstellen, die ich oft benutze.

Union [A, B, C]: A, B, C.

Schreiben Sie eine Funktion, die das Gewicht einer "Person" -Instanz ändert. Es ist eine sehr einfache Implementierung, aber es ändert das Gewicht um das empfangene Gewicht.

python


class Person:
    ...
    def update_weight(self, weight: float) -> float
        self.weight += weight
        return self.weight

Auf den ersten Blick sieht es gut aus, aber es ist ein wenig traurig, nur "float" als Abwechslung zu akzeptieren. Es kann ein wenig praktisch sein, wenn Sie auch "int" erhalten. Ich möchte eine Typanmerkung machen, die OK für "int" oder "float" sagt. Sie können "Union" verwenden, wenn Sie "OK mit einem dieser Typen" sagen.

python


class Person:
    ...
    def update_weight(self, weight: Union[int, float]) -> float
        self.weight += weight
        return self.weight

Dadurch können sowohl "int" als auch "float" in das Argument "update_weight" aufgenommen werden.

Optional [A]: A oder Keine

Wenn Sie anfangen, "Union" zu verwenden, werden Sie häufig feststellen, dass Sie anfangen, "Union [A, None]" in Ihren Code zu schreiben.

Angenommen, Sie definieren eine Berufsklasse, die Ihren Beruf repräsentiert. Geben wir der Klasse "Person" einen Beruf für diese Person. Vielleicht ist "Person" jedoch ein Student und hat keinen Beruf. Ich möchte sagen, dass mein Beruf "Beruf" oder "Keine" ist. In einem solchen Fall können Sie "Union" verwenden.

python


class Person:
    def __init__(..., occupation: Union[Occupation, None]):
        self._occupation = occupation

Nehmen wir als weiteres Beispiel an, Sie möchten eine Funktion haben, die die Pass-ID dieser Person als Zeichenfolge abruft. Möglicherweise haben Sie jedoch keinen Reisepass. Eine Möglichkeit besteht darin, eine leere Zeichenfolge zurückzugeben. Wenn Sie jedoch klarstellen möchten, dass es nicht vorhanden ist, können Sie "Keine" zurückgeben.

python


class Person:
    def get_passport_id(self) -> Union[str, None]:
        if self.has_passport:
            return self._passport._id
        else:
            return None

Je häufiger Gelegenheiten für diese Typanmerkungen verwendet werden, desto ärgerlicher wird es. Für einen solchen Fall wird "Optional" vorbereitet. "Optional" wird wie "Optional [A]" verwendet und bedeutet "A" oder "Keine". Optional [A] = Union [A, Keine].

Bei Verwendung von "Optional" sieht das vorherige Beispiel folgendermaßen aus:

python


class Person:
    def __init__(..., occupation: Optional[Occupation]):
        self._occupation = occupation

    def get_passport_id(self) -> Optional[str]:
        if self.has_passport:
            return self._passport._id
        else:
            return None

Ich denke, es ist etwas einfacher, die Absicht des Codes zu vermitteln, wahrscheinlich weil das Wort "Optional" zugewiesen wurde.

Geben Sie Anmerkungen ein, die bei der Eingabe hilfreich sein können

Ich benutze es nicht so oft, aber es könnte nützlich sein

NewType: Erstellen Sie einen neuen Typ

Sie können einen neuen Typ definieren. Zum Beispiel als eine Funktion, die nach der entsprechenden "Person" -Instanz aus der "person_id" vom Typ "int" sucht

python


def find_by_id(person_id: int) -> Person:
    ...

Ich denke, Sie können so etwas schreiben. Hier ist der Argumentname ein beschreibender Name von "person_id", so dass er möglicherweise nicht so sehr verwechselt wird, aber Sie können versehentlich einen Fehler machen, indem Sie etwas wie "Occupation_id" übergeben, das mit demselben "int" -Typ wie ein Argument definiert ist. nicht. Um solche versehentlichen Fehler zu vermeiden, wagen Sie es, die Klasse "PersonId" zu definieren

python


class PersonId(int):
    pass


def find_by_id(person_id: PersonId) -> Person:
    ...

p = find_by_id(PersonId(10))

Mag in Ordnung sein, aber dies verursacht den Aufwand für die Instanziierung. Mit NewType

python


from typing import NewType


PersonId = NewType('PersonId', int)

def find_by_id(person_id: PersonId) -> Person:
    ...

p = find_by_id(PersonId(10))

Dies hat jedoch einen geringen Overhead, da nur eine Funktion aufgerufen wird, die nichts tut und zur Laufzeit 10 zurückgibt. Auch als die Bedeutung des Codes ist es leicht zu erkennen, dass es "int" ist, aber durch die Neudefinition als einen anderen Typ wird menschliches Versagen verhindert.

TypeAlias: Alias für komplexe Typen

Wenn es sich beispielsweise um "Dict [str, List [str]]" handelt, können Sie "Nun," str "ist ein Schlüssel und" str "ist ein Wörterbuch, dessen Elementliste" value "ist, aber dies ist" List [Dict] ". [str, Union [int, float, None]]] Es ist schwer zu verstehen, wenn es um `geht, und es ist mühsam, jedes Mal so viele Typanmerkungen mit einer Funktion hinzuzufügen, die diesen Typ austauscht. Wenn Sie in diesem Fall TypeAlias verwenden,

TypeAlias


TypeReportCard = List[Dict[str, Union[int, float, None]]]]

def set_report_card(report_card: TypeReportCard):
    ...

set_report_card([{'math': 9.5}, {'english': 7}, {'science': None}])
#Fehlerbeispiel-> set_report_card(TypeReportCard([{'math': 9.5}, {'english': 7}, {'science': None}])) 

Sie können klar wie schreiben. Erstellen Sie einfach einen Alias und es ist kein spezieller Import erforderlich. Im Gegensatz zu "NewType" handelt es sich nur um einen Alias. Wenn Sie ihn also tatsächlich als Argument verwenden, müssen Sie ihn nicht in einen Aliasnamen einschließen.

Es kann interessant sein, [PEP 484] zu lesen (https://www.python.org/dev/peps/pep-0484/).

Datenklasse

** Berücksichtigen Sie bei Verwendung des Wörterbuchtyps die Datenklasse einmal **

Ich wollte die Kontaktinformationen einer bestimmten Person als Daten haben, daher werde ich sie in einem Wörterbuchtyp ausdrücken.

contacts_Wörterbuch



contacts: Dict[str, Optional[str]] = 
    {
        'email': 'xxx@mail',
        'phone_number': None
     }

Wenn Sie sich das ansehen, ist es ein sehr häufiger Code. Eines Tages möchten Sie wissen, "ob ein Kontakt eine Telefonnummer hat".

python


def has_phone_number(contacts: Dict[str, Optional[str]]) -> bool:
    return contacts.get('phone_number', None) is not None
#    return 'phone_number' in contacts and contacts['phone_number']ist nicht keiner ist in Ordnung

Ich denke, es funktioniert ohne Probleme. Zwei Wochen später brauchte ich diese Funktion wieder. Dank der Typanmerkung kann ich mich daran erinnern, wie die "Kontakte" aussehen, und die Funktion erfolgreich aufrufen.

has_phone_number({'email': 'xxx@mail', 'phone-number': 'XXXX'})

Der Rückgabewert dieser Funktion ist jedoch "False". Wenn Sie genau hinschauen, können Sie sehen, dass phone_number in phone-number geändert wurde. Infolgedessen existiert phone_number nicht und das Ergebnis ist False. Die Typanmerkung von Dict [str, Optional [str]] kannte den erforderlichen Schlüsselnamen nicht, sodass ich mich nicht genau an den Schlüsselnamen erinnern konnte, den ich vor zwei Wochen festgelegt hatte.

Dieses Beispiel ist möglicherweise leicht zu verstehen, da die Implementierung von "has_phone_number" direkt oben geschrieben wurde. Was aber, wenn diese Funktion weit entfernt implementiert ist? Was ist, wenn Sie nicht sofort bemerken, dass das Ergebnis "Falsch" ist? Ich denke, das Debuggen wird schwierig sein.

Es ist Standard, direkt eingebettete Konstanten, die im Code erscheinen, so weit wie möglich zu vermeiden. Sie sollten jedoch auch vorsichtig mit Schlüsseln vom Typ Wörterbuch sein.

In diesem Fall sollten Sie Datenklasse einmal in Betracht ziehen.

Ersetzen des Wörterbuchs durch die Datenklasse

Datenklasse


#Durch Datenklasse ersetzen
import dataclasses


@dataclasses.dataclass
class Contacts:
    email: Optional[str]
    phone_number: Optional[str]

dataclasses.dataclass generiert automatisch __init __, daher ist die Notation als Klasse oben ausreichend. Darüber hinaus hat es verschiedene Funktionen wie die automatische Generierung von "eq" und die automatische Generierung von "repr", aber ich werde es diesmal weglassen.

Wenn Sie den Kontakt wie oben als Datenklasse definieren, kann has_phone_number wie folgt implementiert werden.

c = Contacts(email='xxx@mail', phone_number='XXXX')

def has_phone_number(contacts: Contacts) -> bool:
    return contacts.phone_number is not None

Wenn Sie auf diese Weise als Feld in der Datenklasse zugreifen, werden Sie keinen Tippfehler machen (da der Editor das Überprüfen und Vorschlagen unterstützt).

Anders als bei der Definition in "Dict [str, Optional [str]]" ist der Schlüsselname (Feldname) festgelegt, und jeder Schlüssel erhält einen Typ. Um welche Art von Daten handelt es sich also? Es ist konkreter, ob Sie anfordern.

Hinweis: Ersetzen des Wörterbuchs durch NamedTuple

Die Datenklasse ist eine Funktion aus Python 3.7. Wenn Sie also 3.6 oder weniger verwenden, können Sie "NamedTuple" bei der "Eingabe" berücksichtigen. NamedTuple wird so verwendet.

NamedTuple


#Ersetzen Sie durch NamedTuple
from typing import NamedTuple


class Contacts(NamedTuple):
    email: Optional[str]
    phone_number: Optional[str]

c = Contacts(email='xxx@mail', phone_number='XXXX')

def has_phone_number(contacts: Contacts) -> bool:
    return contacts.phone_number is not None

Die Beschreibung ist fast identisch mit "Datenklasse", aber während "Datenklasse" die Möglichkeit hat, detailliertere Einstellungen vorzunehmen, ist "Named Tuple" nur ein benannter Taple.

Aufzählungstyp (Aufzählung)

Der letzte ist Aufzählungstyp.

Es ist sehr nützlich, wenn eine Variable nur A, B oder C annehmen kann ... Zum Beispiel überfliege ich diese Art von Code oft.

def display(object_shape: str):
    if object_shape == 'circle':
        ...
    elif object_shape == 'rectangle':
        ...
    elif object_shape == 'triangle':
        ...
    else:
        raise NotImplementedError

display('circle')

Wenn der Prozess nach dem Status aufgeteilt wird, wird er so geschrieben, und die Statusverwaltung wird später kompliziert, oder der Statusname ist nicht bekannt und die Implementierung wird gesucht. Wenn Sie sich nur den Typ ansehen, sieht es so aus, als ob mit str alles in Ordnung ist, aber in Wirklichkeit erhalten Sie bis auf einige Zeichenfolgen einen Fehler.

In einem solchen Fall können Sie möglicherweise mithilfe des Aufzählungstyps (enum) klar schreiben.

Aufzählungstyp


from enum import Enum


class ObjectShape(Enum):
    CIRCLE = 0
    RECTANGLE = 1
    TRIANGLE = 2

def display(object_shape: ObjectShape):
    if object_shape is ObjectShape.CIRCLE:
        ...
    elif object_shape is ObjectShape.RECTANGLE:
        ...
    elif object_shape is ObjectShape.TRIANGLE:
        ...
    else:
        raise NotImplementedError

display(ObjectShape.CIRCLE)

Jetzt müssen Sie sich keine Gedanken mehr über Tippfehler machen und können auf einen Blick sehen, was erlaubt ist, wenn Sie sich die Form ansehen. Auch dieses Mal habe ich alle "Enum" -Kennungen manuell zugewiesen, aber da diese Nummer keine besondere Bedeutung haben sollte,

auto


import enum


class ObjectShape(enum.Enum):
    CIRCLE = enum.auto()
    RECTANGLE = enum.auto()
    TRIANGLE = enum.auto()

Ich denke, es ist am besten, einen eindeutigen Wert automatisch so zuzuweisen.

Recommended Posts

Worauf ich bei der Python-Codierung achten muss: Kommentare, Typanmerkungen, Datenklassen, Aufzählungen (Aufzählung)
Was war überraschend an Python-Klassen?
Geben Sie Anmerkungen für Python2 in Stub-Dateien ein!
Datenanalyse in Python: Ein Hinweis zu line_profiler
Über __all__ in Python