Maison  >  Article  >  développement back-end  >  Écrire du code Python comme Typescript

Écrire du code Python comme Typescript

王林
王林original
2024-08-01 20:18:11405parcourir

Je suppose que vous qui voulez lire cet article savez ce qu'est la dactylographie. Le développeur Javascript a créé TypeScript pour rendre Javascript plus sûr. Typesafe rend le code plus lisible et comporte moins de bugs sans écrire de test. La sécurité des types peut-elle être obtenue en python ?.

Pourquoi avons-nous besoin d'une sécurité de type ?

Imaginez cette fonction à l'air innocent

def send_email(sender, receiver, message):
    ...

Je cache intentionnellement l'implémentation du code. Pouvez-vous deviner à quoi sert la fonction et de quel paramètre nous avons besoin pour utiliser cette fonction simplement par son nom de fonction et son paramètre ? Nous savons grâce à son nom de fonction qu'il s'agit d'une fonction d'envoi d'e-mail. Qu'en est-il de son paramètre, que devons-nous mettre pour utiliser cette fonction ?.

Première hypothèse, l'expéditeur est la chaîne de l'e-mail, le destinataire est la chaîne de l'e-mail, le message est la chaîne du corps d'un e-mail.

send_email(sender="john@mail.com", receiver="doe@mail.com", message="Hello doe! How are you?")

Devinette la plus simple. mais ce n'est pas la seule supposition.

Deuxièmement, l'expéditeur est l'int de l'id_utilisateur sur la base de données, le destinataire est l'int de l'id_utilisateur sur la base de données, le message est la chaîne du corps d'un e-mail.

john_user_id = 1
doe_user_id = 2
send_email(sender=1, receiver=2, message="Hello doe! How are you?")

Imaginez travailler sur une application. La plupart des applications utilisent une base de données. L'utilisateur se représente généralement par son identifiant.

La troisième hypothèse est que l'expéditeur est un dictionnaire, le destinataire est un dictionnaire, le message est un dictionnaire.

john = {
    "id": 1,
    "username": "john",
    "email": "john@mail.com"
}
doe = {
    "id": 2,
    "username": "doe",
    "email": "doe@mail.com"
}
message = {
    "title": "Greeting my friend doe",
    "body": "Hello doe! How are you?"
}
send_email(sender=john, receiver=doe, message=message)

Peut-être que send_email a besoin de plus qu'un e-mail et un identifiant utilisateur. Pour ajouter plus de données sur chaque paramètre, une structure de dictionnaire est utilisée. Vous remarquez que le message n'est pas seulement str, il a peut-être besoin d'un titre et d'un corps.

Quatrièmement, l'expéditeur est un utilisateur de classe, le destinataire est un utilisateur de classe, le message est un dictionnaire.

class User():

    def __init__(self, id, username, email):
        self.id = id
        self.username = username
        self.email = email

john = User(id=1, username="john", email="john@mail.com")
doe = User(id=2, username="doe", email="doe@mail.com")
message = {
    "title": "Greeting my friend doe",
    "body": "Hello doe! How are you?"
}
send_email(sender=john, receiver=doe, message=message)

Peut-être que send_email s'intègre à une base de données comme Django ORM ou Sqlalchemy. Afin de faciliter la tâche de l'utilisateur final, la classe ORM est utilisée directement.

Alors, quelle est la bonne réponse ? l'un d'eux peut être une bonne réponse. Peut-être que la bonne réponse peut être une combinaison de deux suppositions. Comme l'expéditeur et le destinataire, c'est la classe User (Quatrième supposition), mais le message est str (Première supposition). Nous ne pouvons pas en être sûrs à moins de lire l'implémentation du code. Ce qui est une perte de temps si vous êtes un utilisateur final. En tant qu'utilisateur final qui utilise cette fonction, nous avons juste besoin de ce que fait la fonction, de quel paramètre elle a besoin et de quelle est la sortie de la fonction.

La solution

Docstring

Python a intégré une documentation fonctionnelle à l'aide de docstring. Voici un exemple de docstring.

def add(x, y):
    """Add two number

    Parameter:\n
    x -- int\n
    y -- int\n

    Return: int
    """
    return x + y

def send_email(sender, receiver, message):
    """Send email from sender to receiver

    Parameter:\n
    sender -- email sender, class User\n
    receiver -- email receiver, class User\n
    message -- body of the email, dictionary (ex: {"title": "some title", "body": "email body"}\n

    Return: None
    """
    ...

Ce qui est cool avec docstring, c'est qu'il est compatible avec un éditeur. Dans vscode, la docstring s'affichera lorsque vous survolerez une fonction. La plupart des bibliothèques en python utilisent docstring pour documenter sa fonction.

Writing Python code like Typescript

Le problème avec docstring est la synchronisation des documents. Comment vous assurer que la docstring est toujours synchronisée avec l'implémentation du code. Vous ne pouvez pas le tester correctement. J'entends dire par une personne aléatoire sur Internet "Avoir une documentation obsolète est pire que n'avoir aucune documentation".

Doctest

Au fait, vous pouvez tester docstring en utilisant un peu doctest. Doctest teste votre docstring en exécutant un exemple sur votre docstring. Doctest est déjà préinstallé en python, vous n'avez donc pas besoin de dépendances externes. voyons cet exemple, créez un nouveau fichier appelé my_math.py puis mettez ce code.

# my_math.py
def add(x, y):
    """Add two integer

    Parameter:\n
    x -- int\n
    y -- int\n

    Return: int
    >>> add(1, 2)
    3
    """
    return x + y


if __name__ == "__main__":
    import doctest

    doctest.testmod()

C'est le même code que l'exemple docstring mais j'ajoute un exemple et un doctest à la dernière ligne du code. Afin de tester la docstring, exécutez simplement le fichier python my_math.py. S'il n'y a pas de résultat, cela signifie que votre exemple réussit le test. Si vous voulez voir le résultat, exécutez-le en mode détaillé python my_math.py -v, vous verrez ce résultat.

Trying:
    add(1, 2)
Expecting:
    3
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.add
1 tests in 2 items.
1 passed and 0 failed.
Test passed

Si vous faites une erreur sur l'exemple de code, une erreur sera renvoyée.

# my_math.py
def add(x, y):
    """Add two integer

    Parameter:\n
    x -- int\n
    y -- int\n

    Return: int
    >>> add(2, 2) # <-- I change it here
    3
    """
    return x + y


if __name__ == "__main__":
    import doctest

    doctest.testmod()

la sortie :

**********************************************************************
File "~/typescript-in-python/my_math.py", line 12, in __main__.add
Failed example:
    add(2, 2) # <-- I change it here
Expected:
    3
Got:
    4
**********************************************************************
1 items had failures:
   1 of   1 in __main__.add
***Test Failed*** 1 failures.

Super ! maintenant je peux tester ma docstring. Mais les mises en garde sont :

  1. doctest vérifie uniquement l'exemple il ne vérifie pas le paramètre de la fonction de commentaire et ne renvoie pas
  2. doctest doit exécuter le code comme les autres outils de test afin de vérifier qu'il est correct ou non. doctest doit exécuter l'exemple de code. Si votre code nécessite des outils externes comme une base de données ou un serveur SMTP (comme l'envoi d'un e-mail), il est difficile de le tester avec doctest.

Saisie Python

Parfois, vous n'avez pas besoin d'exécuter le code pour vérifier si le code est correct ou non. Vous avez juste besoin du type d’entrée et du type de sortie. Comment? Prenons cet exemple.

def add(x, y):
    """Add two integer

    Parameter:\n
    x -- int\n
    y -- int\n

    Return: int
    """
    return x + y

def sub(x, y):
    """Substract two integer

    Parameter:\n
    x -- int\n
    y -- int\n

    Return: int
    """
    return x - y

a = add(2, 1)
b = add(1, 1)
c = sub(a, b)

function add renvoie un int et la fonction sub a besoin de deux int comme paramètre d'entrée. Si j'utilise deux fonctions de retour de la fonction d'ajout, puis les mets sur un sous-paramètre comme dans l'exemple ci-dessus, y aura-t-il une erreur ? bien sûr pas parce que la sous-fonction a besoin d'un int et vous mettez également un int.

Depuis Python 3.5, Python a intégré un type appelé typing. En tapant, vous pouvez ajouter du type sur votre fonction comme dans l'exemple ci-dessous.

def add(x: int, y: int) -> int:
    """Add two integer"""
    return x + y

a = add(1, 2)

Instead put it on your docstring you put it on the function. Typing is supported on many editor. If you use vscode you can hover on variable and it will shown it's type.
Writing Python code like Typescript

Nice now our code will have a type safety. eeehhhh not realy. If I intentionally use function incorrectlly like this.

def add(x: int, y: int) -> int:
    """Add two integer"""
    return x + y

res = add(1, [])
print(res)

It will show error

Traceback (most recent call last):
  File "~/typescript-in-python/main.py", line 5, in <module>
    res = add(1, [])
          ^^^^^^^^^^
  File "~/typescript-in-python/main.py", line 3, in add
    return x + y
           ~~^~~
TypeError: unsupported operand type(s) for +: 'int' and 'list'

But it doesn't show that you put incorrect type. Even worse if you use it like this.

def add(x: int, y: int) -> int:
    """Add two integer"""
    return x + y

res = add("hello", "world")
print(res)

It will succeed. It must be error because you put incorrect type.

helloworld

Why python typing doesn't have type checker by default??. Based on pep-3107 it said

Before launching into a discussion of the precise ins and outs of Python 3.0’s function annotations, let’s first talk broadly about what annotations are and are not:

  1. Function annotations, both for parameters and return values, are completely optional.
  2. Function annotations are nothing more than a way of associating arbitrary Python expressions with various parts of a function at compile-time. By itself, Python does not attach any particular meaning or significance to annotations. Left to its own, Python simply makes these expressions available as described in Accessing Function Annotations below.

The only way that annotations take on meaning is when they are interpreted by third-party libraries. ...

So in python typing is like a decorator in typescript or java it doesn't mean anything. You need third party libraries todo type checking. Let's see some library for typechecking.

Python typing + type checker

Here are libraries for typechecking in python. For example we will typecheck this wrong.py file

def add(x: int, y: int) -> int:
    """Add two integer"""
    return x + y

res = add("hello", "world")
print(res)

1.mypy

The "OG" of python type checker. To install it just using pip pip install mypy. Now let's use mypy to typecheck this file. Run mypy wrong.py. It will shown type error which is nice.

wrong.py:5: error: Argument 1 to "add" has incompatible type "str"; expected "int"  [arg-type]
wrong.py:5: error: Argument 2 to "add" has incompatible type "str"; expected "int"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)

btw you can run mypy on entire project by using mypy ..

2.pyright

Another typechecker is pyright. It created by microsoft. It's same like mypy install through pip pip install pyright. Then run it pyright wrong.py. It will shown this error.

~/typescript-in-python/wrong.py
  ~/typescript-in-python/wrong.py:5:11 - error: Argument of type "Literal['hello']" cannot be assigned to parameter "x" of type "int" in function "add"
    "Literal['hello']" is incompatible with "int" (reportArgumentType)
  ~/typescript-in-python/wrong.py:5:20 - error: Argument of type "Literal['world']" cannot be assigned to parameter "y" of type "int" in function "add"
    "Literal['world']" is incompatible with "int" (reportArgumentType)
2 errors, 0 warnings, 0 informations

It said that it's more faster than mypy but I found that's not much diffrent. Maybe my code base it's to small. Also pyright implement more python standard than mypy you can see on https://microsoft.github.io/pyright/#/mypy-comparison. Personaly I prefer mypy than pyright because the error message were more readable.

3.pylyzer

Speaking of performance and speed another new python typechecker pylyzer. It's written in rust. You can install it through pip pip install pylyzer or through cargo (rust package manager) cargo install pylyzer --locked. Then run it pylyzer wrong.py. It will shown this error.

Start checking: wrong.py
Found 2 errors: wrong.py
Error[#2258]: File wrong.py, line 5, <module>.res

5 | res = add("hello", "world")
  :           -------
  :                 |- expected: Int
  :                 `- but found: {"hello"}

TypeError: the type of add::x (the 1st argument) is mismatched

Error[#2258]: File wrong.py, line 5, <module>.res

5 | res = add("hello", "world")
  :                    -------
  :                          |- expected: Int
  :                          `- but found: {"world"}

TypeError: the type of add::y (the 2nd argument) is mismatched

So far this is the most readable and beautiful error message. It's reminds me of rust compiler error. Speed, performance and most readable error message, I think I will choose to using pylyzer if the package already stable. The problem is at the time I write this blog, pylyzer still in beta. It can only typecheck your code base, it haven't support external depedencies.

Conclusion

Alright we successfully write python code like typescript (kinda). There is more way to using python typing module other than check simple type (str, int, bool etc). Maybe I will cover more advance type it in next blog. Maybe you guys have opinion about this, know better typechecker other then those 3, found other way to do typecheck in python or other. let me know on comment section below. As always Happy Coding.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn