Maison >développement back-end >Tutoriel Python >Écrire du code Python comme Typescript
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 ?.
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.
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.
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".
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 :
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.
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:
- Function annotations, both for parameters and return values, are completely optional.
- 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.
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.
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!