>  기사  >  백엔드 개발  >  Python의 이진 산술 연산에 대한 자세한 설명

Python의 이진 산술 연산에 대한 자세한 설명

coldplay.xixi
coldplay.xixi앞으로
2020-09-10 16:41:212503검색

ㅋㅋㅋ 이 기사에서는 이진 산술 연산에 대해 이야기하고 싶습니다.

Python의 이진 산술 연산에 대한 자세한 설명구체적으로 뺄셈이 어떻게 작동하는지 설명하고 싶습니다: a - b. 가환적이지 않기 때문에 일부러 뺄셈을 선택했습니다. 이는 구현 중에 실수로 a와 b를 뒤집어도 여전히 동일한 결과를 얻을 수 있는 추가 연산과 비교하여 연산 순서의 중요성을 강조합니다.

C 코드 보기

평소와 마찬가지로 CPython 인터프리터가 컴파일한 바이트코드를 살펴보는 것부터 시작합니다.

>>> def sub(): a - b... >>> import dis>>> dis.dis(sub)  1           0 LOAD_GLOBAL              0 (a)              2 LOAD_GLOBAL              1 (b)              4 BINARY_SUBTRACT              6 POP_TOP              8 LOAD_CONST               0 (None)             10 RETURN_VALUE复制代码
BINARY_SUBTRACT opcode를 자세히 조사해야 할 것 같습니다. Python/ceval.c 파일을 확인해보면 이 opcode를 구현한 C 코드는 다음과 같은 것을 알 수 있습니다.
case TARGET(BINARY_SUBTRACT): {
    PyObject *right = POP();
    PyObject *left = TOP();
    PyObject *diff = PyNumber_Subtract(left, right);
    Py_DECREF(right);
    Py_DECREF(left);
    SET_TOP(diff);    if (diff == NULL)    goto error;
    DISPATCH();
}复制代码
출처: github.com/python/cpyt…

여기서 핵심 코드는 PyNumber_Subtract()를 구현하는 것입니다. 실제 빼기 의미론. 이 함수의 일부 매크로를 계속 살펴보면 Binary_op1() 함수를 찾을 수 있습니다. 이진 작업을 관리하는 일반적인 방법을 제공합니다.

그러나 구현을 위한 참조로 사용하지 않고 Python의 데이터 모델을 사용합니다. 공식 문서는 매우 훌륭하고 뺄셈에 사용되는 의미를 명확하게 소개합니다. a - b。我故意选择了减法,因为它是不可交换的。这可以强调出操作顺序的重要性,与加法操作相比,你可能会在实现时误将 a 和 b 翻转,但还是得到相同的结果。

查看 C 代码

按照惯例,我们从查看 CPython 解释器编译的字节码开始。

# 通过调用__sub__()实现减法 def sub(lhs: Any, rhs: Any, /) -> Any:
    """Implement the binary operation `a - b`."""
    lhs_type = type(lhs)    try:
        subtract = _mro_getattr(lhs_type, "__sub__")    except AttributeError:
        msg = f"unsupported operand type(s) for -: {lhs_type!r} and {type(rhs)!r}"
        raise TypeError(msg)    else:        return subtract(lhs, rhs)复制代码

看起来我们需要深入研究 BINARY_SUBTRACT  操作码。翻查 Python/ceval.c 文件,可以看到实现该操作码的 C 代码如下:

# 减法的实现,其中表达式的左侧和右侧均可参与运算_MISSING = object()def sub(lhs: Any, rhs: Any, /) -> Any:
        # lhs.__sub__
        lhs_type = type(lhs)        try:
            lhs_method = debuiltins._mro_getattr(lhs_type, "__sub__")        except AttributeError:
            lhs_method = _MISSING        # lhs.__rsub__ (for knowing if rhs.__rub__ should be called first)
        try:
            lhs_rmethod = debuiltins._mro_getattr(lhs_type, "__rsub__")        except AttributeError:
            lhs_rmethod = _MISSING        # rhs.__rsub__
        rhs_type = type(rhs)        try:
            rhs_method = debuiltins._mro_getattr(rhs_type, "__rsub__")        except AttributeError:
            rhs_method = _MISSING

        call_lhs = lhs, lhs_method, rhs
        call_rhs = rhs, rhs_method, lhs        if lhs_type is not rhs_type:
            calls = call_lhs, call_rhs        else:
            calls = (call_lhs,)        for first_obj, meth, second_obj in calls:            if meth is _MISSING:                continue
            value = meth(first_obj, second_obj)            if value is not NotImplemented:                return value        else:            raise TypeError(                f"unsupported operand type(s) for -: {lhs_type!r} and {rhs_type!r}"
            )复制代码

来源:github.com/python/cpyt…

这里的关键代码是PyNumber_Subtract(),实现了减法的实际语义。继续查看该函数的一些宏,可以找到binary_op1() 函数。它提供了一种管理二元操作的通用方法。

不过,我们不把它作为实现的参考,而是要用Python的数据模型,官方文档很好,清楚介绍了减法所使用的语义。

从数据模型中学习

通读数据模型的文档,你会发现在实现减法时,有两个方法起到了关键作用:__sub__ 和 __rsub__。

1、__sub__()方法

当执行a - b

데이터 모델에서 배우기

데이터 모델 문서를 읽어보면 빼기 구현에 중요한 역할을 하는 두 가지 방법이 있다는 것을 알 수 있습니다. __sub__ 그리고 __rsub__ .

1. __sub__() 메서드

a - b를 실행하면 __sub__()는 a 유형으로 검색되고 b는 다음과 같이 검색됩니다. 그 매개변수. 이는 속성 액세스에 대한 내 기사의 __getattribute__()와 매우 유사합니다. 특수/마법 메서드는 아래 예제 코드에서 성능 목적으로 객체 자체가 아닌 객체 유형을 기반으로 구문 분석됩니다. 저는 _ mro_getattr()을 사용합니다. 이 과정.

따라서 __sub__()가 정의되면 type(a).__sub__(a,b)가 뺄셈에 사용됩니다. (주석: 매직 메소드는 객체가 아닌 객체의 유형에 속합니다.)

즉, 뺄셈은 본질적으로 메소드 호출일 뿐이라는 뜻입니다! 표준 라이브러리의 Operator.sub() 함수로 생각할 수도 있습니다.

샘플 코드를 더 쉽게 이해할 수 있도록 a-b의 왼쪽과 오른쪽을 각각 나타내는 두 개의 이름 lhs와 rhs를 사용하여 이 함수를 모방하여 자체 모델을 구현하겠습니다.

# Python中减法的完整实现_MISSING = object()def sub(lhs: Any, rhs: Any, /) -> Any:
        # lhs.__sub__
        lhs_type = type(lhs)        try:
            lhs_method = debuiltins._mro_getattr(lhs_type, "__sub__")        except AttributeError:
            lhs_method = _MISSING        # lhs.__rsub__ (for knowing if rhs.__rub__ should be called first)
        try:
            lhs_rmethod = debuiltins._mro_getattr(lhs_type, "__rsub__")        except AttributeError:
            lhs_rmethod = _MISSING        # rhs.__rsub__
        rhs_type = type(rhs)        try:
            rhs_method = debuiltins._mro_getattr(rhs_type, "__rsub__")        except AttributeError:
            rhs_method = _MISSING

        call_lhs = lhs, lhs_method, rhs
        call_rhs = rhs, rhs_method, lhs        if (
            rhs_type is not _MISSING  # Do we care?
            and rhs_type is not lhs_type  # Could RHS be a subclass?
            and issubclass(rhs_type, lhs_type)  # RHS is a subclass!
            and lhs_rmethod is not rhs_method  # Is __r*__ actually different?
        ):
            calls = call_rhs, call_lhs        elif lhs_type is not rhs_type:
            calls = call_lhs, call_rhs        else:
            calls = (call_lhs,)        for first_obj, meth, second_obj in calls:            if meth is _MISSING:                continue
            value = meth(first_obj, second_obj)            if value is not NotImplemented:                return value        else:            raise TypeError(                f"unsupported operand type(s) for -: {lhs_type!r} and {rhs_type!r}"
            )复制代码

2. 오른쪽이 __rsub__()를 사용하도록 하세요

하지만 a가 __sub__()를 구현하지 않으면 어떻게 될까요? a와 b가 다른 유형이면 b의 __rsub__()를 호출하려고 시도합니다(__rsub__의 "r"은 "오른쪽"을 의미하며 연산자의 오른쪽을 의미합니다).

연산의 양쪽 유형이 서로 다른 경우 둘 다 표현식을 유효하게 만들 수 있는 기회를 갖게 됩니다. 동일할 경우 __sub__()가 이를 처리할 것이라고 가정합니다. 그러나 두 구현이 모두 동일하더라도 객체 중 하나가 다른 객체의 (하위)클래스인 경우에는 여전히 __rsub__()를 호출해야 합니다.

3. 종류는 신경쓰지 마세요

이제 표정은 양쪽 모두 참여 가능해요! 그러나 어떤 이유로 객체 유형이 뺄셈을 지원하지 않으면 어떻게 될까요(예: 4 - "stuff"가 지원되지 않음)? 이 경우 __sub__ 또는 __rsub__가 할 수 있는 일은 NotImplemented를 반환하는 것뿐입니다.

이것은 다음 작업으로 이동하여 코드가 정상적으로 실행되도록 시도해야 한다는 Python에 반환된 신호입니다. 우리 코드의 경우 이는 메서드가 작동한다고 가정하기 전에 메서드의 반환 값을 확인해야 함을 의미합니다.

# 一个创建闭包的函数,实现了二元运算的逻辑_MISSING = object()def _create_binary_op(name: str, operator: str) -> Any:
    """Create a binary operation function.

    The `name` parameter specifies the name of the special method used for the
    binary operation (e.g. `sub` for `__sub__`). The `operator` name is the
    token representing the binary operation (e.g. `-` for subtraction).

    """

    lhs_method_name = f"__{name}__"

    def binary_op(lhs: Any, rhs: Any, /) -> Any:
        """A closure implementing a binary operation in Python."""
        rhs_method_name = f"__r{name}__"

        # lhs.__*__
        lhs_type = type(lhs)        try:
            lhs_method = debuiltins._mro_getattr(lhs_type, lhs_method_name)        except AttributeError:
            lhs_method = _MISSING        # lhs.__r*__ (for knowing if rhs.__r*__ should be called first)
        try:
            lhs_rmethod = debuiltins._mro_getattr(lhs_type, rhs_method_name)        except AttributeError:
            lhs_rmethod = _MISSING        # rhs.__r*__
        rhs_type = type(rhs)        try:
            rhs_method = debuiltins._mro_getattr(rhs_type, rhs_method_name)        except AttributeError:
            rhs_method = _MISSING

        call_lhs = lhs, lhs_method, rhs
        call_rhs = rhs, rhs_method, lhs        if (
            rhs_type is not _MISSING  # Do we care?
            and rhs_type is not lhs_type  # Could RHS be a subclass?
            and issubclass(rhs_type, lhs_type)  # RHS is a subclass!
            and lhs_rmethod is not rhs_method  # Is __r*__ actually different?
        ):
            calls = call_rhs, call_lhs        elif lhs_type is not rhs_type:
            calls = call_lhs, call_rhs        else:
            calls = (call_lhs,)        for first_obj, meth, second_obj in calls:            if meth is _MISSING:                continue
            value = meth(first_obj, second_obj)            if value is not NotImplemented:                return value        else:
            exc = TypeError(                f"unsupported operand type(s) for {operator}: {lhs_type!r} and {rhs_type!r}"
            )
            exc._binary_op = operator            raise exc复制代码

4. 하위 클래스는 상위 클래스보다 우선합니다

__rsub__()에 대한 문서를 보면 주석이 표시됩니다. 뺄셈 표현식의 오른쪽이 왼쪽의 하위 클래스이고(진정한 하위 클래스, 동일한 클래스는 포함되지 않음) 두 개체의 __rsub__() 메서드가 다른 경우 __sub__()는 다음과 같습니다. __rsub__()를 먼저 호출하기 전에 호출됩니다. 즉, b가 a의 하위 클래스인 경우 호출 순서가 반대가 됩니다.

이상한 예외처럼 보일 수도 있지만 여기에는 이유가 있습니다. 하위 클래스를 생성한다는 것은 상위 클래스에서 제공하는 작업 위에 새로운 논리를 주입한다는 의미입니다. 이러한 종류의 논리는 상위 클래스에 추가할 필요가 없습니다. 그렇지 않으면 상위 클래스는 하위 클래스에서 작업할 때 하위 클래스가 구현하려는 작업을 쉽게 재정의합니다.

🎜구체적으로 Spam이라는 클래스가 있다고 가정해 보겠습니다. Spam() - Spam()을 실행하면 LessSpam 인스턴스가 생성됩니다. 그런 다음 Bacon이라는 스팸의 하위 클래스를 만들어 스팸에서 베이컨을 빼면 VeggieSpam이 됩니다. 🎜🎜위의 규칙이 없으면 Spam() - Bacon()은 LessSpam을 제공합니다. 스팸은 Bacon을 빼면 VeggieSpam이 제공되어야 한다는 것을 모르기 때문입니다. 🎜

但是,有了上述规则,就会得到预期的结果 VeggieSpam,因为 Bacon.__rsub__() 首先会在表达式中被调用(如果计算的是 Bacon() - Spam(),那么也会得到正确的结果,因为首先会调用 Bacon.__sub__(),因此,规则里才会说两个类的不同的方法需有区别,而不仅仅是一个由 issubclass() 判断出的子类。)

# Python中减法的完整实现_MISSING = object()def sub(lhs: Any, rhs: Any, /) -> Any:
        # lhs.__sub__
        lhs_type = type(lhs)        try:
            lhs_method = debuiltins._mro_getattr(lhs_type, "__sub__")        except AttributeError:
            lhs_method = _MISSING        # lhs.__rsub__ (for knowing if rhs.__rub__ should be called first)
        try:
            lhs_rmethod = debuiltins._mro_getattr(lhs_type, "__rsub__")        except AttributeError:
            lhs_rmethod = _MISSING        # rhs.__rsub__
        rhs_type = type(rhs)        try:
            rhs_method = debuiltins._mro_getattr(rhs_type, "__rsub__")        except AttributeError:
            rhs_method = _MISSING

        call_lhs = lhs, lhs_method, rhs
        call_rhs = rhs, rhs_method, lhs        if (
            rhs_type is not _MISSING  # Do we care?
            and rhs_type is not lhs_type  # Could RHS be a subclass?
            and issubclass(rhs_type, lhs_type)  # RHS is a subclass!
            and lhs_rmethod is not rhs_method  # Is __r*__ actually different?
        ):
            calls = call_rhs, call_lhs        elif lhs_type is not rhs_type:
            calls = call_lhs, call_rhs        else:
            calls = (call_lhs,)        for first_obj, meth, second_obj in calls:            if meth is _MISSING:                continue
            value = meth(first_obj, second_obj)            if value is not NotImplemented:                return value        else:            raise TypeError(                f"unsupported operand type(s) for -: {lhs_type!r} and {rhs_type!r}"
            )复制代码

推广到其它二元运算

解决掉了减法运算,那么其它二元运算又如何呢?好吧,事实证明它们的操作相同,只是碰巧使用了不同的特殊/魔术方法名称。

所以,如果我们可以推广这种方法,那么我们就可以实现 13 种操作的语义:+ 、-、*、@、/、//、%、**、<<、>>、&、^、和 |。

由于闭包和 Python 在对象自省上的灵活性,我们可以提炼出 operator 函数的创建。

# 一个创建闭包的函数,实现了二元运算的逻辑_MISSING = object()def _create_binary_op(name: str, operator: str) -> Any:
    """Create a binary operation function.

    The `name` parameter specifies the name of the special method used for the
    binary operation (e.g. `sub` for `__sub__`). The `operator` name is the
    token representing the binary operation (e.g. `-` for subtraction).

    """

    lhs_method_name = f"__{name}__"

    def binary_op(lhs: Any, rhs: Any, /) -> Any:
        """A closure implementing a binary operation in Python."""
        rhs_method_name = f"__r{name}__"

        # lhs.__*__
        lhs_type = type(lhs)        try:
            lhs_method = debuiltins._mro_getattr(lhs_type, lhs_method_name)        except AttributeError:
            lhs_method = _MISSING        # lhs.__r*__ (for knowing if rhs.__r*__ should be called first)
        try:
            lhs_rmethod = debuiltins._mro_getattr(lhs_type, rhs_method_name)        except AttributeError:
            lhs_rmethod = _MISSING        # rhs.__r*__
        rhs_type = type(rhs)        try:
            rhs_method = debuiltins._mro_getattr(rhs_type, rhs_method_name)        except AttributeError:
            rhs_method = _MISSING

        call_lhs = lhs, lhs_method, rhs
        call_rhs = rhs, rhs_method, lhs        if (
            rhs_type is not _MISSING  # Do we care?
            and rhs_type is not lhs_type  # Could RHS be a subclass?
            and issubclass(rhs_type, lhs_type)  # RHS is a subclass!
            and lhs_rmethod is not rhs_method  # Is __r*__ actually different?
        ):
            calls = call_rhs, call_lhs        elif lhs_type is not rhs_type:
            calls = call_lhs, call_rhs        else:
            calls = (call_lhs,)        for first_obj, meth, second_obj in calls:            if meth is _MISSING:                continue
            value = meth(first_obj, second_obj)            if value is not NotImplemented:                return value        else:
            exc = TypeError(                f"unsupported operand type(s) for {operator}: {lhs_type!r} and {rhs_type!r}"
            )
            exc._binary_op = operator            raise exc复制代码

有了这段代码,你可以将减法运算定义为 _create_binary_op(“sub”, “-”),然后根据需要重复定义出其它运算。

想了解更多编程学习,敬请关注php培训栏目!

위 내용은 Python의 이진 산술 연산에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제