Python 연산 자 를 정확하게 다시 불 러 오 는 방법 예시 상세 설명

머리말
연산 자 를 다시 불 러 오 는 것 은 모두 가 낯 설 지 않다 고 믿 습 니 다.연산 자 를 다시 불 러 오 는 역할 은 사용자 가 정의 하 는 대상 에 게 접두사 연산 자(예 를 들 어+와|)나 일원 연산 자(예 를 들 어-와~)를 사용 하 게 하 는 것 입 니 다.파 이 썬 에서 함수 호출((),속성 접근(.)과 요소 접근/절편([])도 연산 자 입 니 다.
우 리 는 Vector 류 를 위해 몇 개의 연산 자 를 간략하게 실현 했다.add__ 와mul__ 방법 은 특수 한 방법 으로 연산 자 를 다시 싣 는 방법 을 보 여주 기 위해 서 였 으 나,일부 작은 문 제 는 우리 에 게 무시 되 었 다.그 밖 에 우리 가 정의 한Vector2d.__eq__ 방법 은Vector(3, 4) == [3, 4]이 사실(True)이 라 고 생각 하 는데 이것 은 합 리 적 이지 않 을 수 있다.다음은 상세 한 소 개 를 함께 살 펴 보 겠 습 니 다.
연산 자 과부하 기초
어떤 바닥 에 서 는 연산 자 를 다시 싣 는 명성 이 좋 지 않다.이 언어 특성 이 남용 되 어 프로그래머 를 곤 혹 스 럽 게 하고 결함 과 예상 치 못 한 성능 병목 을 초래 할 수 있다.그러나 적절하게 사용 하면 API 가 잘 사용 되 고 코드 가 쉽게 읽 힐 수 있다.Python 은 약간의 제한 을 가 하여 유연성,가용성 과 안전성 방면 의 균형 을 잘 잡 았 다.
  • 내 장 된 형식의 연산 자 를 다시 불 러 올 수 없습니다
  • 연산 자 를 새로 만 들 수 없고 기 존
  • 만 다시 불 러 올 수 있 습 니 다.
  • 일부 연산 자 는 다시 실 을 수 없다―is,and,or 와 not(비트 연산 자
  • &,|와~가능)
  • 앞의 박문 은 Vector 에 접두사 연산 자 를 정의 하 였 습 니 다.즉,==,이 연산 자 는eq__ 방법 지원.개선 하 겠 습 니 다eq__ 방법의 실현 은 Vector 인 스 턴 스 의 조작 수가 아 닙 니 다.그러나 연산 자 과부하 에 있어 서 많은 비교 연산 자(=,!=,>,<,>=,<=,<=)가 있 습 니 다.특례 입 니 다.따라서 우 리 는 먼저 Vector 에서 네 개의 산술 연산 자 를 다시 불 러 올 것 입 니 다.일원 연산 자-와+,그리고 접두사 연산 자+와*.
    일원 연산 자
      -(__neg__)
    일원 취 음산 연산 자.만약 x 가-2 라면-x==2.
      +(__pos__)
    일원 취 정 산술 연산 자.보통 x==+x 이지 만 예외 도 있 습 니 다.궁금 하 다 면'x 와+x 가 언제 같 지 않 습 니까?'란 을 읽 으 세 요.
      ~(__invert__)
    정수 에 대해 비트 에 따라 반 을 취하 고~x==-(x+1)로 정의 합 니 다.만약 x 가 2 라면~x==-3.
    일원 연산 자 를 지원 하 는 것 은 매우 간단 하 며,상응하는 특수 한 방법 만 실현 하면 된다.이 특수 한 방법 들 은 하나의 인자,self 만 있다.그리고 그 유형 에 맞 는 논 리 를 사용 하여 이 루어 진다.그러나 연산 자의 기본 규칙 을 지 켜 야 한다.항상 새로운 대상 으로 돌아 가 야 한다.self 를 수정 할 수 없 으 며,적당 한 형식의 새로운 인 스 턴 스 를 만 들 고 되 돌려 야 한 다 는 것 이다.
    -와+에 있어 서 결 과 는 self 와 같은 인 스 턴 스 일 수 있 습 니 다.대부분의 경우,+self 의 던 전 으로 돌아 가 는 것 이 좋 습 니 다.abs(...)의 결 과 는 스칼라 일 것 입 니 다.그러나~에 있어 서 어떤 결과 가 합 리 적 이 라 고 말 하기 어렵다.왜냐하면 정 수 를 처리 하 는 위치 가 아 닐 수도 있 기 때문이다.예 를 들 어 ORM 에서 SQL WHERE 자 구 는 반 집합 으로 돌아 가 야 한다.
    
    def __abs__(self):
      return math.sqrt(sum(x * x for x in self))
    
     def __neg__(self):
      return Vector(-x for x in self)   #     -v,      Vector   ,  self         
    
     def __pos__(self):
      return Vector(self)      #     +v,      Vector   ,   self      
    x 와+x 는 언제 같 지 않 습 니까?
    모든 사람 은 x==+x,그리고 Python 에서 거의 모든 상황 에서 그렇다 고 생각 합 니 다.하지만 표준 라 이브 러 리 에서 x 두 예 를 찾 았 습 니 다!=+x 의 경우.
    첫 번 째 예 는decimal.Decimal 류 와 관계 가 있다.x 가 Decimal 인 스 턴 스 라면 산술 연산 의 문맥 에서 만 든 다음 에 서로 다른 문맥 에서+x 를 계산 하면 x!=+x。예 를 들 어 x 가 있 는 문맥 은 특정한 정 도 를 사용 하고+x 를 계산 할 때 정밀도 가 변 했다.예 를 들 어 아래 의 것 이다.🌰
    산술 연산 문맥 의 정밀도 변 화 는 x 가+x 와 같 지 않 을 수 있 습 니 다.
    
    >>> import decimal
    >>> ctx = decimal.getcontext()                  #                 
    >>> ctx.prec = 40                          #             40
    >>> one_third = decimal.Decimal('1') / decimal.Decimal('3') #        1/3
    >>> one_third
    Decimal('0.3333333333333333333333333333333333333333')     #    ,     40   
    >>> one_third == +one_third                    #one_third = +one_thied  TRUE
    True
    >>> ctx.prec = 28                          #     28
    >>> one_third == +one_third                    #one_third = +one_thied  FalseFalse >>> +one_third Decimal('0.3333333333333333333333333333')   #  +one_third,    28   
    각+onethird 표현 식 은 모두 one 을 사용 합 니 다.third 의 값 은 새 Decimal 인 스 턴 스 를 만 들 지만 현재 산술 연산 컨 텍스트 의 정 도 를 사용 합 니 다.
    x != +x 의 두 번 째 예 는 collections.counter 문서 에서(https://docs.python.org/3/library/collections.html#collections.Counter)。클래스 는 몇 개의 산술 연산 자 를 실 현 했 습 니 다.예 를 들 어 접두사 연산 자+는 두 개의 Counter 인 스 턴 스 의 계수 기 를 합 친 것 입 니 다.그러나 실 용적 인 측면 에서 Counter 를 더 하면 마이너스 와 0 값 수 는 결과 에서 삭 제 됩 니 다.한편,1 원 연산 자+는 빈 Counter 를 추가 하 는 것 과 같 기 때문에 새로운 Counter 를 만 들 고 0 이상 의 카운터 만 유지 합 니 다.
    🌰  1 원 연산 자+새 Counter 인 스 턴 스 를 얻 었 지만 0 값 과 마이너스 카운터 가 없습니다.
    
    >>> from collections import Counter
    >>> ct = Counter('abracadabra')
    >>> ct['r'] = -3
    >>> ct['d'] = 0
    >>> ct
    Counter({'a': 5, 'r': -3, 'b': 2, 'c': 1, 'd': 0})
    >>> +ct
    Counter({'a': 5, 'b': 2, 'c': 1})
    과부하 벡터 덧셈 연산 자+
    두 개의 유클리드 벡터 를 합치 면 새로운 벡터 를 얻 을 수 있 는데,그것 의 각 분량 은 두 개의 벡터 중 상응하는 분량 의 합 이다.예 를 들 면:
    
    >>> v1 = Vector([3, 4, 5])
    >>> v2 = Vector([6, 7, 8])
    >>> v1 + v2
    Vector([9.0, 11.0, 13.0])
    >>> v1 + v2 == Vector([3+6, 4+7, 5+8])
    True
    이러한 기본 적 인 요 구 를 확인 한 후,add__ 방법의 실현 은 짧 고 간결 하 다.🌰 아래 와 같다
    
     def __add__(self, other):
      pairs = itertools.zip_longest(self, other, fillvalue=0.0)   #      ,a  self,b  other,        ,  fillvalue            
      return Vector(a + b for a, b in pairs)        #          pairs        
    또한 Vector 를 원 그룹 이나 숫자 를 생 성 할 수 있 는 교체 대상 에 추가 할 수 있 습 니 다.
    
    #  Vector     
    
     def __add__(self, other):
      pairs = itertools.zip_longest(self, other, fillvalue=0.0)   #      ,a  self,b  other,        ,  fillvalue            
      return Vector(a + b for a, b in pairs)        #          pairs        
    
     def __radd__(self, other):            #      __add__
      return self + other
    __radd__ 보통 이렇게 간단 합 니 다.적당 한 연산 자 를 직접 호출 합 니 다.여기 서 의뢰add__。교환 가능 한 연산 자 는 모두 이렇게 할 수 있다.숫자 와 벡터 를 처리 할 때+교환 할 수 있 지만 연결 순 서 는 안 됩 니 다.
    적재량 곱셈 연산 자*
    Vector([1,2,3])*x 는 무슨 뜻 입 니까?x 가 숫자 라면 스칼라 적(scalar produt)을 계산 하 는 것 입 니 다.결 과 는 새로운 Vector 인 스 턴 스 이 고 각 분량 은 x-이것 을 요소 급 곱셈(element wise multiplication)이 라 고 합 니 다.
    
    >>> v1 = Vector([1, 2, 3])
    >>> v1 * 10
    Vector([10.0, 20.0, 30.0])
    >>> 11 * v1
    Vector([11.0, 22.0, 33.0])
    Vector 작업 수의 축적 과 관련 된 것 은 두 개의 벡터 의 점 적(dotprodut)이 라 고 합 니 다.벡터 하 나 를 1 로 보면×N 매트릭스,다른 벡터 를 N 으로 봅 니 다.×1 행렬,그러면 행렬 곱셈 이다.NumPy 등 라 이브 러 리 는 현재 이 두 가지 의미 의*를 다시 불 러 오지 않 고*로 스칼라 적 을 계산 하 는 것 입 니 다.예 를 들 어 NumPy 에서 점 적 사용numpy.dot() 함수 로 계산한다.
    스칼라 적 화제 로 돌아가다.우 리 는 여전히 가장 간단 하고 사용 가능 한 을 먼저 실현 한다.mul__ 와rmul__방법:
    
    def __mul__(self, scalar):
      if isinstance(scalar, numbers.Real):
       return Vector(n * scalar for n in self)
      else:
       return NotImplemented
    
     def __rmul__(self, scalar):
      return self * scalar
    이 두 가지 방법 은 확실히 사용 할 수 있 지만,호 환 되 지 않 는 조작 수 를 제공 할 때 문제 가 생 길 수 있다.scalar 매개 변수의 값 이 숫자 라면 부동 소수점 과 곱 한 적 은 다른 부동 소수점 입 니 다(Vector 류 는 내부 에서 부동 소수점 배열 을 사용 하기 때 문 입 니 다).따라서 복 수 를 사용 할 수 없 지만 int,bool(int 의 하위 클래스),심지어fractions.Fraction 인 스 턴 스 등 스칼라 일 수 있 습 니 다.
    점 적 에 필요 한@기 호 를 제공 합 니 다(예 를 들 어 a@b 는 a 와 b 의 점 적 입 니 다).@연산 자 는 특별한 방법 으로matmul__、__rmatmul__ 와imatmul__ 지원 을 제공 합 니 다.이름 은"matrix multiplication"(행렬 곱셈)에서 가 져 옵 니 다.
    
    >>> va = Vector([1, 2, 3])
    >>> vz = Vector([5, 6, 7])
    >>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7
    True
    >>> [10, 20, 30] @ vz
    380.0
    >>> va @ 3
    Traceback (most recent call last):
    ...
    TypeError: unsupported operand type(s) for @: 'Vector' and 'int'
    다음은 해당 하 는 특수 한 방법의 코드 입 니 다.
    
    >>> va = Vector([1, 2, 3])
    >>> vz = Vector([5, 6, 7])
    >>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7
    True
    >>> [10, 20, 30] @ vz
    380.0
    >>> va @ 3
    Traceback (most recent call last):
    ...
    TypeError: unsupported operand type(s) for @: 'Vector' and 'int'
    많은 비교 연산 자
    Python 해석 기 는 여러 비교 연산 자(==,!=,>,<,>=,<=)의 처 리 는 앞의 글 과 유사 하지만 두 가지 측면 에서 중대 한 차이 가 있다.
  • 정방 향 과 역방향 호출 은 같은 일련의 방법 을 사용한다.예 를 들 어==에 있어 서 정방 향 과 역방향 호출 은 모두 이다.eq__ 방법 은 매개 변 수 를 맞 추 었 을 뿐이다.그리고 정방 향gt__ 방법 호출 은 역방향lt__방법,그리고 매개 변 수 를 맞 춥 니 다.
  • 맞아요.반대로 호출 에 실패 하면 Python 은 TypeError 대신 대상 의 ID 를 비교 합 니 다.
  • 많은 비교 연산 자:정방 향 방법 이 NotImplemented 로 돌아 가면 역방향 방법 을 호출 합 니 다.
    패 킷
     
    접두사 연산 자
     
    정방 향 방법 호출
     
    역방향 방법 호출
     
    예비 메커니즘
     
    대등 성
     
    a == b
     
    a.__eq__(b)
     
    b.__eq__(a)
     
    반환 id(a)==id(b)
     
     
    a != b
     
    a.__ne__(b)
     
    b.__ne__(a)
     
    not 로 돌아 가기(a==b)
     
    정렬
     
    a > b
     
    a.__gt__(b)
     
    b.__lt__(a)
     
    TypeError 던 지기
     
     
    a < b
     
    a.__lt__(b)
     
    b.__gt__(a)
     
    TypeError 던 지기
     
     
    a >= b
     
    a.__ge__(b)
     
    b.__le__(a)
     
    TypeError 던 지기
     
     
    a <= b
     
    a.__le__(b)
     
    b.__ge__(a)
     
    T type 오류 던 지기
    아래 거 봐.🌰
    
    from array import array
    import reprlib
    import math
    import numbers
    import functools
    import operator
    import itertools
    
    
    class Vector:
     typecode = 'd'
    
     def __init__(self, components):
      self._components = array(self.typecode, components)
    
     def __iter__(self):
      return iter(self._components)
    
     def __repr__(self):
      components = reprlib.repr(self._components)
      components = components[components.find('['):-1]
      return 'Vector({})'.format(components)
    
     def __str__(self):
      return str(tuple(self))
    
     def __bytes__(self):
      return (bytes([ord(self.typecode)]) + bytes(self._components))
    
     def __eq__(self, other):
      return (len(self) == len(other) and all(a == b for a, b in zip(self, other)))
    
     def __hash__(self):
      hashes = map(hash, self._components)
      return functools.reduce(operator.xor, hashes, 0)
    
     def __add__(self, other):
      pairs = itertools.zip_longest(self, other, fillvalue=0.0)   #      ,a  self,b  other,        ,  fillvalue            
      return Vector(a + b for a, b in pairs)        #          pairs        
    
     def __radd__(self, other):            #      __add__
      return self + other
    
     def __mul__(self, scalar):
      if isinstance(scalar, numbers.Real):
       return Vector(n * scalar for n in self)
      else:
       return NotImplemented
    
     def __rmul__(self, scalar):
      return self * scalar
    
     def __matmul__(self, other):
      try:
       return sum(a * b for a, b in zip(self, other))
      except TypeError:
       return NotImplemented
    
     def __rmatmul__(self, other):
      return self @ other
    
     def __abs__(self):
      return math.sqrt(sum(x * x for x in self))
    
     def __neg__(self):
      return Vector(-x for x in self)   #     -v,      Vector   ,  self         
    
     def __pos__(self):
      return Vector(self)       #     +v,      Vector   ,   self      
    
     def __bool__(self):
      return bool(abs(self))
    
     def __len__(self):
      return len(self._components)
    
     def __getitem__(self, index):
      cls = type(self)
    
      if isinstance(index, slice):
       return cls(self._components[index])
      elif isinstance(index, numbers.Integral):
       return self._components[index]
      else:
       msg = '{.__name__} indices must be integers'
       raise TypeError(msg.format(cls))
    
     shorcut_names = 'xyzt'
    
     def __getattr__(self, name):
      cls = type(self)
    
      if len(name) == 1:
       pos = cls.shorcut_names.find(name)
       if 0 <= pos < len(self._components):
        return self._components[pos]
      msg = '{.__name__!r} object has no attribute {!r}'
      raise AttributeError(msg.format(cls, name))
    
     def angle(self, n):
      r = math.sqrt(sum(x * x for x in self[n:]))
      a = math.atan2(r, self[n-1])
      if (n == len(self) - 1 ) and (self[-1] < 0):
       return math.pi * 2 - a
      else:
       return a
    
     def angles(self):
      return (self.angle(n) for n in range(1, len(self)))
    
     def __format__(self, fmt_spec=''):
      if fmt_spec.endswith('h'):
       fmt_spec = fmt_spec[:-1]
       coords = itertools.chain([abs(self)], self.angles())
       outer_fmt = '<{}>'
      else:
       coords = self
       outer_fmt = '({})'
      components = (format(c, fmt_spec) for c in coords)
      return outer_fmt.format(', '.join(components))
    
     @classmethod
     def frombytes(cls, octets):
      typecode = chr(octets[0])
      memv = memoryview(octets[1:]).cast(typecode)
      return cls(memv)
    
    va = Vector([1.0, 2.0, 3.0])
    vb = Vector(range(1, 4))
    print('va == vb:', va == vb)     #            Vector       
    t3 = (1, 2, 3)
    print('va == t3:', va == t3)
    
    print('[1, 2] == (1, 2):', [1, 2] == (1, 2))
    위의 코드 가 실 행 된 결 과 는 다음 과 같 습 니 다.
    
    va == vb: True
    va == t3: True
    [1, 2] == (1, 2): False
    Python 자체 에서 단 서 를 찾 아 보 니[1,2] == (1, 2) 결 과 는 False 였 다.그래서 우 리 는 보수 적 으로 유형 검 사 를 해 야 한다.두 번 째 조작 수가 Vector 인 스 턴 스(또는 Vector 하위 클래스 의 인 스 턴 스)라면eq__ 방법의 현재 논리.그렇지 않 으 면 NotImplemented 로 돌아 가 Python 에 게 처리 하도록 합 니 다.
    🌰 vector_v8.py:Vector 류 의 개선eq__ 방법.
    
      def __eq__(self, other):
       if isinstance(other, Vector):          #        Vector      
        return (len(self) == len(other) and all(a == b for a, b in zip(self, other)))
       else:
        return NotImplemented           #  ,  NotImplemented
    개 선 된 코드 실행 결과:
    
    >>> va = Vector([1.0, 2.0, 3.0])
    >>> vb = Vector(range(1, 4))
    >>> va == vb 
    True
    >>> t3 = (1, 2, 3)
    >>> va == t3
    False
    증분 할당 연산 자
    Vector 클래스 는 증분 할당 연산 자+=과*=을 지원 합 니 다.예 는 다음 과 같 습 니 다.
    🌰  증분 할당 은 변경 할 수 없 는 목 표를 수정 하지 않 고 새 인 스 턴 스 를 만 든 다음 다시 연결 합 니 다.
    
    >>> v1 = Vector([1, 2, 3])
    >>> v1_alias = v1             #     ,     Vector([1, 2, 3])  
    >>> id(v1)                 #         v1 Vector   ID
    >>> v1 += Vector([4, 5, 6])       #       
    >>> v1                    #        
    Vector([5.0, 7.0, 9.0])
    >>> id(v1)                 #        Vector  
    >>> v1_alias                #   v1_alias,     Vector      
    Vector([1.0, 2.0, 3.0])
    >>> v1 *= 11                #       
    >>> v1                   #   ,       ,       Vector  
    Vector([55.0, 77.0, 99.0])
    >>> id(v1)
    전체 코드:
    
    from array import array
    import reprlib
    import math
    import numbers
    import functools
    import operator
    import itertools
    
    
    class Vector:
     typecode = 'd'
    
     def __init__(self, components):
      self._components = array(self.typecode, components)
    
     def __iter__(self):
      return iter(self._components)
    
     def __repr__(self):
      components = reprlib.repr(self._components)
      components = components[components.find('['):-1]
      return 'Vector({})'.format(components)
    
     def __str__(self):
      return str(tuple(self))
    
     def __bytes__(self):
      return (bytes([ord(self.typecode)]) + bytes(self._components))
    
     def __eq__(self, other):
      if isinstance(other, Vector):          
       return (len(self) == len(other) and all(a == b for a, b in zip(self, other)))
      else:
       return NotImplemented          
    
     def __hash__(self):
      hashes = map(hash, self._components)
      return functools.reduce(operator.xor, hashes, 0)
    
     def __add__(self, other):
      pairs = itertools.zip_longest(self, other, fillvalue=0.0)   
      return Vector(a + b for a, b in pairs)        
    
     def __radd__(self, other):            
      return self + other
    
     def __mul__(self, scalar):
      if isinstance(scalar, numbers.Real):
       return Vector(n * scalar for n in self)
      else:
       return NotImplemented
    
     def __rmul__(self, scalar):
      return self * scalar
    
     def __matmul__(self, other):
      try:
       return sum(a * b for a, b in zip(self, other))
      except TypeError:
       return NotImplemented
    
     def __rmatmul__(self, other):
      return self @ other
    
     def __abs__(self):
      return math.sqrt(sum(x * x for x in self))
    
     def __neg__(self):
      return Vector(-x for x in self)   
    
     def __pos__(self):
      return Vector(self)       
    
     def __bool__(self):
      return bool(abs(self))
    
     def __len__(self):
      return len(self._components)
    
     def __getitem__(self, index):
      cls = type(self)
    
      if isinstance(index, slice):
       return cls(self._components[index])
      elif isinstance(index, numbers.Integral):
       return self._components[index]
      else:
       msg = '{.__name__} indices must be integers'
       raise TypeError(msg.format(cls))
    
     shorcut_names = 'xyzt'
    
     def __getattr__(self, name):
      cls = type(self)
    
      if len(name) == 1:
       pos = cls.shorcut_names.find(name)
       if 0 <= pos < len(self._components):
        return self._components[pos]
      msg = '{.__name__!r} object has no attribute {!r}'
      raise AttributeError(msg.format(cls, name))
    
     def angle(self, n):
      r = math.sqrt(sum(x * x for x in self[n:]))
      a = math.atan2(r, self[n-1])
      if (n == len(self) - 1 ) and (self[-1] < 0):
       return math.pi * 2 - a
      else:
       return a
    
     def angles(self):
      return (self.angle(n) for n in range(1, len(self)))
    
     def __format__(self, fmt_spec=''):
      if fmt_spec.endswith('h'):
       fmt_spec = fmt_spec[:-1]
       coords = itertools.chain([abs(self)], self.angles())
       outer_fmt = '<{}>'
      else:
       coords = self
       outer_fmt = '({})'
      components = (format(c, fmt_spec) for c in coords)
      return outer_fmt.format(', '.join(components))
    
     @classmethod
     def frombytes(cls, octets):
      typecode = chr(octets[0])
      memv = memoryview(octets[1:]).cast(typecode)
      return cls(memv)
    총결산
    이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

    좋은 웹페이지 즐겨찾기