例外の問題
毎日直面している問題を見つけるのは難しいです。 習慣と盲目はバグを機能に変えますが、例外をオープンマインドで見てみましょう。
例外を見つけるのは難しい
: «»
raise
, ; «» , , .
, «» . :
def divide(first: float, second: float) -> float:
return first / second
,
float
. - :
result = divide(1, 0)
print('x / y = ', result)
?
print
, 1 0 – ,
ZeroDivisionError
. , , .
, . Python : , ,
int
,
str
, , , .
raise something()
. , . .
.
ZeroDivisionError
, .
def divide(first: float, second: float) -> float:
try:
return first / second
except ZeroDivisionError:
return 0.0
. 0? 1
None
? , ,
None
( ), , - .
? , - ? . , , .
: — , .
,
ZeroDivisionError
. - .
, , ? , — - . .
, , - , , . , , 0.
divide
.
, . , , ? ? , , .
, .
except
, . , , .
, : , , , . ?
« ».

,
goto
, .
: HTTP API:
import requests
def fetch_user_profile(user_id: int) -> 'UserProfile':
"""Fetches UserProfile dict from foreign API."""
response = requests.get('/api/users/{0}'.format(user_id))
response.raise_for_status()
return response.json()
. :
- , .
- .
- , .
- .
- API URL.
- .
- .
- -
- .
- JSON, .
, . , , .
?
, , , , . , .
-
except Exception: pass
. . . -
None
. .if something is not None:
,TypeError
. . - . ,
User
UserNotFound
MissingUser
. ,AnonymousUser
Django, . , . - , .
@dry-python/return
. - , .
, 0. , , ?
from returns.result import Result, Success, Failure
def divide(first: float, second: float) -> Result[float, ZeroDivisionError]:
try:
return Success(first / second)
except ZeroDivisionError as exc:
return Failure(exc)
:
Success
Failure
.
Result
. , ,
Result[float, ZeroDivisionError]
Success[float]
,
Failure[ZeroDivisionError]
.
? , .
Failure
: .
1 + divide(1, 0)
# => mypy error: Unsupported operand types for + ("int" and "Result[float, ZeroDivisionError]")
.
Result
, . .
, PEP561. mypy , -, .
from returns.result import Result, Success, Failure
def divide(first: float, second: float) -> Result[float, ZeroDivisionError]:
try:
return Success('Done')
# => error: incompatible type "str"; expected "float"
except ZeroDivisionError as exc:
return Failure(0)
# => error: incompatible type "int"; expected "ZeroDivisionError"
?
:
map
, ;bind
, .
Success(4).bind(lambda number: Success(number / 2))
# => Success(2)
Success(4).map(lambda number: number + 1)
# => Success(5)
, ,
.bind
.map
c
Failure
:
Failure(4).bind(lambda number: Success(number / 2))
# => Failure(4)
Failure(4).map(lambda number: number / 2)
# => Failure(4)
, . , , .
Failure(4).rescue(lambda number: Success(number + 1))
# => Success(5)
Failure(4).fix(lambda number: number / 2)
# => Success(2)
« », « ». , !
?
, , , .
.unwrap()
.value_or()
:
Success(1).unwrap()
# => 1
Success(0).value_or(None)
# => 0
Failure(0).value_or(None)
# => None
Failure(1).unwrap()
# => Raises UnwrapFailedError()
, , ,
.unwrap()
?
UnwrapFailedErrors?
, , . : . ,
Result
:
from returns.result import Result, Success, Failure
class CreateAccountAndUser(object):
"""Creates new Account-User pair."""
# TODO: we need to create a pipeline of these methods somehow...
def _validate_user(
self, username: str, email: str,
) -> Result['UserSchema', str]:
"""Returns an UserSchema for valid input, otherwise a Failure."""
def _create_account(
self, user_schema: 'UserSchema',
) -> Result['Account', str]:
"""Creates an Account for valid UserSchema's. Or returns a Failure."""
def _create_user(
self, account: 'Account',
) -> Result['User', str]:
"""Create an User instance. If user already exists returns Failure."""
-, -:
class CreateAccountAndUser(object):
"""Creates new Account-User pair."""
def __call__(self, username: str, email: str) -> Result['User', str]:
"""Can return a Success(user) or Failure(str_reason)."""
return self._validate_user(
username, email,
).bind(
self._create_account,
).bind(
self._create_user,
)
# ...
- , ,
.unwrap()
. ? . ?
@pipeline
:
from result.functions import pipeline
class CreateAccountAndUser(object):
"""Creates new Account-User pair."""
@pipeline
def __call__(self, username: str, email: str) -> Result['User', str]:
"""Can return a Success(user) or Failure(str_reason)."""
user_schema = self._validate_user(username, email).unwrap()
account = self._create_account(user_schema).unwrap()
return self._create_user(account)
# ...
.
.unwrap()
@pipeline
: , -
.unwrap()
Failure[str]
,
@pipeline
Failure[str]
. .
, HTTP API. , ?
Result
. @safe, . , :
from returns.functions import safe
@safe
def divide(first: float, second: float) -> float:
return first / second
# is the same as:
def divide(first: float, second: float) -> Result[float, ZeroDivisionError]:
try:
return Success(first / second)
except ZeroDivisionError as exc:
return Failure(exc)
,
@safe
, .
, API –
@safe
. :
import requests
from returns.functions import pipeline, safe
from returns.result import Result
class FetchUserProfile(object):
"""Single responsibility callable object that fetches user profile."""
#: You can later use dependency injection to replace `requests`
#: with any other http library (or even a custom service).
_http = requests
@pipeline
def __call__(self, user_id: int) -> Result['UserProfile', Exception]:
"""Fetches UserProfile dict from foreign API."""
response = self._make_request(user_id).unwrap()
return self._parse_json(response)
@safe
def _make_request(self, user_id: int) -> requests.Response:
response = self._http.get('/api/users/{0}'.format(user_id))
response.raise_for_status()
return response
@safe
def _parse_json(self, response: requests.Response) -> 'UserProfile':
return response.json()
, :
-
@safe
, .Result[OldReturnType, Exception]
. -
Result
, . -
.unwrap()
, . -
@pipeline
,.unwrap
.
— . , :
- « ».
Result
, . - « ». .
.fix()
.rescue()
. - « ». -. .
- « ». ! , - .
, . . - , , .
? Moscow Python Conf++ 5 , ! – dry-python core- Django Channels. dry-python -.