Python例外はアンチパターンと見なされるようになりました

例外とは何ですか? 名前から明らかです-それらはプログラムで例外が発生したときに発生します。 なぜ例外がアンチパターンであるのか、そしてそれらはタイピングにどのように関係するのかと尋ねるかもしれません。 私はそれを理解しようとしました 、そして今、私はあなたとこれについて議論したい、ハラジテリ。



例外の問題



毎日直面している問題を見つけるのは難しいです。 習慣と盲目はバグを機能に変えますが、例外をオープンマインドで見てみましょう。



例外を見つけるのは難しい



: «» 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()

      
      





. :





, . , , .



?



, , , , . , .





, 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"

      
      





?



:





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

      
      





, :





— . , :







, . . - , , .



? Moscow Python Conf++ 5 , ! – dry-python core- Django Channels. dry-python -.



All Articles