이 글을 읽고 싶은 분들은 typescript가 무엇인지 아실 거라고 생각합니다. Javascript 개발자는 Javascript를 더욱 안전하게 만들기 위해 TypeScript를 만들었습니다. Typesafe는 코드를 더 읽기 쉽게 만들고 테스트를 작성하지 않고도 버그가 적습니다. Python에서 유형 안전성을 달성할 수 있습니까?.
이 청순해 보이는 기능을 상상해 보세요
def send_email(sender, receiver, message): ...
코드 구현을 의도적으로 숨겼습니다. 함수 이름과 매개변수만으로 이 함수를 사용하기 위해 필요한 함수가 무엇인지, 어떤 매개변수가 필요한지 짐작할 수 있나요? 우리는 기능 이름을 통해 이메일을 보내는 기능이라는 것을 알고 있습니다. 매개변수는 어떻습니까? 이 함수를 사용하려면 무엇을 넣어야 할까요?
첫 번째 추측 보낸 사람은 이메일의 문자열, 수신자는 이메일의 문자열, 메시지는 이메일 본문의 문자열입니다.
send_email(sender="john@mail.com", receiver="doe@mail.com", message="Hello doe! How are you?")
가장 간단한 추측입니다. 하지만 이것이 유일한 추측은 아닙니다.
두 번째 추측 발신자는 db의 user_id 정수이고 수신자는 db의 user_id 정수이며 메시지는 이메일 본문의 문자열입니다.
john_user_id = 1 doe_user_id = 2 send_email(sender=1, receiver=2, message="Hello doe! How are you?")
애플리케이션 작업을 상상해 보세요. 대부분의 응용 프로그램은 일부 데이터베이스를 사용합니다. 사용자는 일반적으로 ID로 나타냅니다.
세 번째 추측은 발신자가 사전, 수신자가 사전, 메시지가 사전입니다.
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이 아니라 제목과 본문이 필요할 수도 있다는 것을 알 수 있습니다.
네 번째 추측 발신자는 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 클래스를 직접 사용하였습니다.
그럼 정답은 무엇인가요? 그 중 하나가 정답이 될 수 있습니다. 어쩌면 정답은 두 가지 추측의 조합일 수도 있습니다. 발신자 및 수신자는 User 클래스(4번째 추측)이지만 메시지는 str(첫 번째 추측)입니다. 코드 구현을 읽지 않으면 확신할 수 없습니다. 최종 사용자라면 시간 낭비입니다. 이 기능을 사용하는 최종 사용자로서 우리는 어떤 기능이 수행되고, 어떤 매개변수가 필요한지, 어떤 기능 출력이 무엇인지만 알면 됩니다.
Python은 docstring을 사용하여 fuction 문서를 내장했습니다. 여기에 독스트링 예제가 있습니다.
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을 사용합니다.
Docstring의 문제는 문서 동기화입니다. Docstring이 항상 코드 구현과 동기화되는지 확인하는 방법. 테스트에 바로 넣을 수는 없습니다. 인터넷에서 어떤 사람으로부터 "문서가 오래된 것은 문서가 없는 것보다 더 나쁘다"는 말을 들었습니다.
그런데 doctest를 사용하여 docstring을 테스트할 수 있습니다. Doctest는 귀하의 Docstring에서 example을 실행하여 귀하의 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과 동일한 코드이지만 코드 마지막 줄에 example과 doctest를 추가했습니다. 독스트링을 테스트하려면 python my_math.py 파일을 실행하세요. 출력이 없으면 예제가 테스트를 통과했다는 의미입니다. 출력을 보려면 verbose 모드에서 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.
좋아요! 이제 내 독스트링을 테스트할 수 있습니다. 하지만 주의사항은 다음과 같습니다.
코드가 올바른지 확인하기 위해 코드를 실행할 필요가 없는 경우도 있습니다. 입력 유형과 출력 유형만 있으면 됩니다. 어떻게? 이 예를 고려해보세요.
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를 반환하고 function sub에는 입력 매개변수로 두 개의 int가 필요합니다. 추가 함수에서 두 개의 반환을 사용하고 위의 예와 같이 하위 매개변수에 넣으면 오류가 발생합니까? 물론 하위 함수에 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.
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.
위 내용은 Typescript와 같은 Python 코드 작성의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!