ホームページ  >  記事  >  バックエンド開発  >  Typescript のような Python コードを書く

Typescript のような Python コードを書く

王林
王林オリジナル
2024-08-01 20:18:11406ブラウズ

この記事を読みたい皆さんは、typescript が何であるかを知っていると思います。 JavaScript 開発者は、JavaScript をよりタイプセーフにするために typescript を作成しました。タイプセーフを使用すると、テストを書かなくてもコードが読みやすくなり、バグが少なくなります。 Python でタイプセーフティを実現できますか?

なぜタイプセーフティが必要なのでしょうか?

この無邪気な機能を想像してみてください

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

コードの実装を意図的に隠しています。関数名とパラメータだけで、この関数が何のためのもので、この関数を使用するにはどのパラメータが必要か推測できますか?関数名から、メールを送信するための関数であることがわかります。この関数を使用するにはパラメータについてはどうすればよいですか?

最初に、送信者は電子メールの str 、受信者は電子メールの str 、メッセージは電子メールの本文の str であると推測します。

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

最も単純な推測です。しかし、それが唯一の推測ではありません。

2 番目に推測される送信者はデータベースの user_id の int、受信者はデータベースの user_id の int、メッセージはメール本文の str です。

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

アプリケーションで作業しているところを想像してみてください。ほとんどのアプリケーションは何らかのデータベースを使用します。ユーザーは通常、ID で表されます。

3 番目の推定送信者は辞書、受信者は辞書、メッセージは辞書です。

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)

send_email には電子メールとユーザー ID 以外のものが必要になる可能性があります。各パラメータにさらにデータを追加するには、辞書構造が使用されます。このメッセージは単なる str ではなく、おそらくタイトルと本文が必要であることに気づきました。

4 番目の推測では、送信者はクラス User、受信者はクラス User、メッセージは辞書です。

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)

send_email は、Django ORM や Sqlalchemy などのデータベース Orm と統合される可能性があります。エンドユーザーにとって使いやすいように、ORM クラスを直接使用しています。

それで、正しい答えはどれですか?そのうちの 1 つが正解になる可能性があります。おそらく、正解は 2 つの推測の組み合わせである可能性があります。送信者と受信者はクラス User (4 番目の推測) ですが、メッセージと同様に str (最初の推測) です。コード実装を読まないとわかりません。あなたがエンドユーザーであれば、これは時間の無駄です。この関数を使用するエンドユーザーとして必要なのは、関数が何を行うのか、必要なパラメーターと関数の出力が何かだけです。

解決策

ドキュメント文字列

Python には、docstring を使用した関数ドキュメントが組み込まれています。ここに 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
    """
    ...

docstring の優れた点は、エディターと互換性があることです。 vscode では、関数の上にマウスを置くと docstring が表示されます。 Python のほとんどのライブラリは、関数を文書化するために docstring を使用します。

Writing Python code like Typescript

docstring の問題はドキュメントの同期です。 docstring がコード実装と常に同期していることを確認する方法。正しくテストすることはできません。インターネット上のランダムな人から、「古いドキュメントがあることは、ドキュメントがないことよりも悪いです。」

ドクターテスト

ところで、doctest を使用して docstring をテストできます。 doctest は、docstring でサンプルを実行して、docstring をテストします。 Doctest はすでに Python にプリインストールされているため、外部の依存関係は必要ありません。この例を見てみましょう。my_math.py という名前の新しいファイルを作成し、このコードを配置します。

# 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()

これは docstring のサンプルと同じコードですが、コードの最後の行に example と doctest を追加しています。 docstring をテストするには、ファイル python my_math.py を実行するだけです。出力がない場合は、例がテストに合格したことを意味します。出力を確認したい場合は、冗長モード python my_math.py -v で実行すると、この出力が表示されます。

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

コード例を間違えるとエラーが返されます。

# 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()

出力:

**********************************************************************
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.

すごいですね!これで、docstring をテストできるようになりました。ただし、注意点は次のとおりです:

  1. doctest はサンプルのみをチェックします コメント関数のパラメータはチェックせず、戻り値を返します
  2. doctest は他のテスト ツールと同様にコードを実行する必要があります。それが正しいかどうかを確認するには、doctest はコード例を実行する必要があります。コードにデータベースや SMTP サーバー (電子メールの送信など) などの外部ツールが必要な場合、doctest を使用してテストするのは困難です。

Python の入力

コードが正しいかどうかを確認するためにコードを実行する必要がない場合もあります。必要なのは入力タイプと出力タイプだけです。どうやって?この例を考えてみましょう。

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)

関数 add は int を返し、関数 sub は入力パラメータとして 2 つの int を必要とします。追加関数からの戻り値を 2 つ使用して、上記の例のようにサブパラメータに置くとエラーになりますか?もちろん、サブ関数には int が必要であり、int も入力するためではありません。

Python 3.5 以降、Python にはタイピングと呼ばれる型が組み込まれています。入力すると、以下の例のように関数に型を追加できます。

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.

以上がTypescript のような Python コードを書くの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。