483 Stimmen

Wie behandeln Python-Funktionen die Arten von Parametern, die Sie übergeben?

Wenn ich mich nicht irre, funktioniert die Erstellung einer Funktion in Python folgendermaßen:

def my_func(param1, param2):
    # stuff

Sie geben jedoch nicht die Typen dieser Parameter an. Wenn ich mich recht erinnere, ist Python eine stark typisierte Sprache, und als solche sollte Python es nicht zulassen, dass Sie einen Parameter eines anderen Typs übergeben, als der Funktionsersteller erwartet. Aber woher weiß Python, dass der Benutzer der Funktion die richtigen Typen übergibt? Stirbt das Programm einfach, wenn es den falschen Typ hat, vorausgesetzt, die Funktion verwendet den Parameter tatsächlich? Müssen Sie den Typ angeben?

1143voto

erb Punkte 12513

Die anderen Antworten haben gut erklärt, wie man Enten tippt und die einfache Antwort von tzot :

In Python gibt es keine Variablen wie in anderen Sprachen, wo Variablen einen Typ und einen Wert haben, sondern Namen, die auf Objekte zeigen, die ihren Typ kennen.

Allerdings Seit 2010 (als die Frage zum ersten Mal gestellt wurde) hat sich eine interessante Sache geändert, nämlich die Einführung von PEP 3107 (implementiert in Python 3). Sie können nun tatsächlich den Typ eines Parameters und den Typ des Rückgabetyps einer Funktion wie folgt angeben:

def pick(l: list, index: int) -> int:
    return l[index]

Hier können wir sehen, dass pick benötigt 2 Parameter, eine Liste l und eine ganze Zahl index . Sie sollte auch eine ganze Zahl zurückgeben.

Hier wird also impliziert, dass l ist eine Liste von ganzen Zahlen, die wir ohne großen Aufwand sehen können, aber bei komplexeren Funktionen kann es etwas verwirrend sein, was die Liste enthalten soll. Wir wollen auch den Standardwert von index 0 sein. Um dieses Problem zu lösen, können Sie schreiben pick stattdessen so:

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

Beachten Sie, dass wir jetzt einen String als Typ von l was zwar syntaktisch zulässig ist, sich aber nicht gut für das programmatische Parsing eignet (worauf wir später zurückkommen).

Es ist wichtig zu beachten, dass Python keine Fehlermeldung auslöst. TypeError wenn Sie einen Float in index Der Grund dafür ist einer der wichtigsten Punkte in der Designphilosophie von Python: "Wir sind hier alle mündige Erwachsene" Das bedeutet, dass Sie wissen müssen, was Sie an eine Funktion übergeben können und was nicht. Wenn Sie wirklich Code schreiben wollen, der TypeErrors auslöst, können Sie die isinstance Funktion, um zu prüfen, ob das übergebene Argument vom richtigen Typ oder einer Unterklasse davon ist, wie hier:

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

Warum Sie dies selten tun sollten und was Sie stattdessen tun sollten, erfahren Sie im nächsten Abschnitt und in den Kommentaren.

PEP 3107 verbessert nicht nur die Lesbarkeit des Codes, sondern hat auch einige passende Anwendungsfälle, über die Sie hier lesen können ici .


Die Typ-Annotation hat in Python 3.5 mit der Einführung von PEP 484 das ein Standardmodul einführt typing für Typ-Hinweise.

Diese Typ-Hinweise stammen von der Typüberprüfung mypy ( GitHub ), die jetzt PEP 484 willfährig.

En typing Modul kommt mit einer ziemlich umfassenden Sammlung von Typ-Hinweisen, einschließlich:

  • List , Tuple , Set , Dict - für list , tuple , set y dict beziehungsweise.
  • Iterable - nützlich für Generatoren.
  • Any - obwohl es alles Mögliche sein könnte.
  • Union - wenn es sich um einen beliebigen Typ innerhalb einer bestimmten Gruppe von Typen handeln kann, im Gegensatz zu Any .
  • Optional - wenn es könnte keine sein. Kurzform für Union[T, None] .
  • TypeVar - mit Generika verwendet.
  • Callable - wird in erster Linie für Funktionen verwendet, kann aber auch für andere Callables verwendet werden.

Dies sind die gebräuchlichsten Typenhinweise. Eine vollständige Liste finden Sie in der Dokumentation für das Typisierungsmodul .

Hier ist das alte Beispiel, das die im Typisierungsmodul eingeführten Anmerkungsmethoden verwendet:

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

Eine leistungsstarke Funktion ist die Callable die es Ihnen ermöglicht, Methoden, die eine Funktion als Argument annehmen, mit Anmerkungen zu versehen. Zum Beispiel:

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

Das obige Beispiel könnte durch die Verwendung von TypeVar 代わりに Any Aber das ist eine Übung für den Leser, denn ich glaube, ich habe meine Antwort schon mit zu vielen Informationen über die wunderbaren neuen Funktionen der Tipphilfe gefüllt.


Wenn man früher Python-Code dokumentiert hat, zum Beispiel mit Sphinx einige der oben genannten Funktionen könnten durch das Schreiben von Dokumentationsstrings, die wie folgt formatiert sind, erreicht werden:

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

Wie Sie sehen, sind dafür eine Reihe zusätzlicher Zeilen erforderlich (die genaue Anzahl hängt davon ab, wie explizit Sie sein wollen und wie Sie Ihren Docstring formatieren). Aber es sollte Ihnen nun klar sein, wie PEP 3107 bietet eine Alternative, die in vieler (aller?) Hinsicht überlegen ist. Dies gilt insbesondere in Kombination mit PEP 484 das, wie wir gesehen haben, ein Standardmodul bereitstellt, das eine Syntax für diese Typ-Hinweise/Anmerkungen definiert, die so verwendet werden kann, dass sie eindeutig und präzise, aber dennoch flexibel ist, was eine leistungsstarke Kombination ergibt.

Meiner persönlichen Meinung nach ist dies eine der besten Funktionen in Python überhaupt. Ich kann es kaum erwarten, dass die Leute anfangen, die Möglichkeiten dieser Funktion zu nutzen. Entschuldigung für die lange Antwort, aber das passiert, wenn ich mich aufrege.


Ein Beispiel für Python-Code, der stark auf Type Hinting zurückgreift, finden Sie hier ici .

226voto

Alex Martelli Punkte 805329

Python ist stark typisiert, weil jedes Objekt hat ein Typ, jedes Objekt kennt Typs ist es unmöglich, ein Objekt eines Typs versehentlich oder absichtlich so zu verwenden, "als ob" es ein Objekt eines anderen Typs wäre. verschiedene Typ, und alle elementaren Operationen mit dem Objekt werden an seinen Typ delegiert.

Dies hat nichts zu tun mit Namen . A Name in Python hat keinen "Typ": Wenn ein Name definiert ist, bezieht sich der Name auf einen Objekt y el Objekt hat zwar einen Typ (aber das erzwingt nicht unbedingt einen Typ für die Name : ein Name ist ein Name).

Ein Name in Python kann sich durchaus zu verschiedenen Zeitpunkten auf verschiedene Objekte beziehen (wie in den meisten Programmiersprachen, wenn auch nicht in allen) - und es gibt keine Einschränkung für den Namen, die besagt, dass er, wenn er sich einmal auf ein Objekt des Typs X bezogen hat, dann für immer nur auf andere Objekte des Typs X verweisen darf. Namen sind nicht Teil des Konzepts der "starken Typisierung", obwohl einige Anhänger der statisch Typisierung (bei Namen tun eingeschränkt werden, und zwar auf statische Art und Weise (auch zur Kompilierzeit), wird der Begriff auf diese Weise missbraucht.

24voto

TM. Punkte 101846

Sie geben keinen Typ an. Die Methode schlägt nur dann (zur Laufzeit) fehl, wenn sie versucht, auf Attribute zuzugreifen, die nicht für die übergebenen Parameter definiert sind.

Also diese einfache Funktion:

def no_op(param1, param2):
    pass

... wird nicht fehlschlagen, egal welche zwei Argumente übergeben werden.

Allerdings ist diese Funktion:

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... wird zur Laufzeit fehlschlagen, wenn param1 y param2 haben nicht beide aufrufbare Attribute namens quack .

18voto

Gergely Papp Punkte 730

Ich habe einen Wrapper implementiert, falls jemand Variablentypen angeben möchte.

import functools

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for i in range(len(args)):
            v = args[i]
            v_name = list(func.__annotations__.keys())[i]
            v_type = list(func.__annotations__.values())[i]
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        v = result
        v_name = 'return'
        v_type = func.__annotations__['return']
        error_msg = 'Variable `' + str(v_name) + '` should be type ('
        error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
        if not isinstance(v, v_type):
                raise TypeError(error_msg)
        return result

    return check

Verwenden Sie es als:

@type_check
def test(name : str) -> float:
    return 3.0

@type_check
def test2(name : str) -> str:
    return 3.0

>> test('asd')
>> 3.0

>> test(42)
>> TypeError: Variable `name` should be type (<class 'str'>) but instead is type (<class 'int'>)

>> test2('asd')
>> TypeError: Variable `return` should be type (<class 'str'>) but instead is type (<class 'float'>)

EDITAR

Der obige Code funktioniert nicht, wenn der Typ eines der Argumente (oder der Rückgabewert) nicht deklariert ist. Die folgende Bearbeitung kann helfen, andererseits funktioniert sie nur für kwargs und prüft nicht args.

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for name, value in kwargs.items():
            v = value
            v_name = name
            if name not in func.__annotations__:
                continue

            v_type = func.__annotations__[name]

            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ') '
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        if 'return' in func.__annotations__:
            v = result
            v_name = 'return'
            v_type = func.__annotations__['return']
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                    raise TypeError(error_msg)
        return result

    return check

14voto

tzot Punkte 86792

In vielen Sprachen gibt es Variablen, die von einem bestimmten Typ sind und einen Wert haben. In Python gibt es keine Variablen, sondern Objekte, und Sie verwenden Namen, um auf diese Objekte zu verweisen.

In anderen Sprachen, wenn Sie sagen:

a = 1

dann ändert eine (typischerweise ganzzahlige) Variable ihren Inhalt auf den Wert 1.

In Python,

a = 1

bedeutet "Benutze den Namen a um sich auf das Objekt 1 ". In einer interaktiven Python-Sitzung können Sie Folgendes tun:

>>> type(1)
<type 'int'>

Die Funktion type wird aufgerufen mit dem Objekt 1 Da jedes Objekt seinen Typ kennt, ist es einfach für type um den besagten Typ herauszufinden und ihn zurückzugeben.

Ebenso wird bei der Definition einer Funktion

def funcname(param1, param2):

erhält die Funktion zwei Objekte und benennt sie param1 y param2 unabhängig von ihrer Art. Wenn Sie sicherstellen wollen, dass die empfangenen Objekte von einem bestimmten Typ sind, codieren Sie Ihre Funktion so, als ob sie von dem/den benötigten Typ(en) wären, und fangen Sie die Ausnahmen ab, die ausgelöst werden, wenn sie es nicht sind. Die ausgelösten Ausnahmen sind typischerweise TypeError (Sie haben eine ungültige Operation verwendet) und AttributeError (Sie haben versucht, auf ein nicht vorhandenes Mitglied zuzugreifen (auch Methoden sind Mitglieder)).

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