Python 유형 힌트: 반공변, 공변, 불변

9976 단어 pythontypehintgeneric
우리는 모두 Liskov 치환 원리를 알고 있습니다. 유형을 손상시키지 않고 하위 유형으로 대체할 수 있습니다. 그러나 제네릭 유형인 C[subtype]과 C[type]의 관계는 어떻습니까?

그렇다면 공변량이란 무엇입니까?



A<: B이면 어디에서나 B를 A로 바꿀 수 있습니다.
여기서 B에 결합된 T는 C[T]라고 합니다.
  • Contravariant, when A <: B => C[A] :> C[B]어디에서나 C[B]를 C[A]로 바꿀 수 있습니다.
  • 공변, 때 A <: B => C[A] <: C[B]어디에서나 C[A]를 C[B]로 바꿀 수 있습니다. 그러나이 상황을 일으키는 방법은 무엇입니까? 아래 예제 코드에서 설명하겠습니다.
  • 불변, 두 상황 모두 적합하지 않은 경우. C[A]는 C[B]와 교환할 수 없으며 그 반대의 경우도 마찬가지입니다.

  • Sink / Source와 개념을 결합하십시오.



    그들의 행동에 따라 우리는 두 종류의 객체를 정의했습니다.
  • 소스[T]: T를 생성하고 T에 공변합니다.
  • 싱크[T]: T를 소비하고 T와 반공변입니다.

  • 매우 추상적이므로 아래 코드로 바로 넘어갑니다. 의견에 따라 수정하여 실험해 보십시오.

    import abc
    
    from typing import Generic, TypeVar
    
    class Base:
        def foo(self):
            print("foo")
    
    class Derived(Base):
        def bar(self):
            print("bar")
    


    먼저 BaseDerived. DerivedBase,에 내재되어 있으므로 Derived <: Base.
    T_co = TypeVar('T_co', bound='Base', covariant=True)
    
    class Source(Generic[T_co]):
        @abc.abstractmethod
        def generate(self) -> T_co: # Produce T_co!
            pass
    
    class SourceBase(Source[Base]):
        def generate(self) -> Derived: # Produce T_co!
            return Derived() 
    
    class SourceDerived(Source[Derived]):
        def generate(self) -> Derived:
            return Derived() 
    
    source: Source[Base] = SourceDerived()
    source.generate()
    
    #Try to uncomment lines below.
    #source_derived: Source[Derived] = SourceBase()
    #source_derived.generate()
    


    이제 SourceDerived <: SourceBase 가 있습니다. covariant=True 를 제거하면 다음 경고가 표시됩니다.

    [Pyright reportGeneralTypeIssues] [E] Expression of type > "SourceDerived" cannot be assigned to declared type "Source[Base]"
      TypeVar "T_co@Source" is invariant
        "Derived" is incompatible with "Base"


    covariantcontravariant,로 수정하면 이렇게 됩니다. covariant 검사기 SourceDerived는 SourceBase를 사용하는 모든 곳에서 안전하게 사용할 수 있음을 알려줍니다.

        def generate(self) -> T_co: <- warining
            pass
    


    warining: [Pyright reportGeneralTypeIssues] [E] Contravariant type variable cannot be used in return type
      TypeVar "T_co@Source" is contravariant


    covariant 처럼 C[T_co]를 사용하는 곳을 확인하는 것 뿐만 아니라 C[T_co]에서 T_co를 반환하는 메서드를 확인하는 방법도 있습니다.

    다음으로 contravariant 예제를 살펴보겠습니다.

    T_contra = TypeVar('T_contra', bound='Base', contravariant=True)
    
    class Sink(Generic[T_contra]):
        @abc.abstractmethod
        def consume(self, value: T_contra):
            pass
    
    class SinkBase(Sink[Base]):
        def consume(self, value: Base):
            value.foo()
    
    class SinkDerived(Sink[Derived]):
        def consume(self, value: Derived):
            value.bar()
    
        def other_func(self):
            pass
    
    base = Base()
    derived = Derived()
    sink_derived: Sink[Derived] = SinkBase()
    #we can safely consumer
    sink_derived.consume(base)
    sink_derived.consume(derived)
    #Try to uncomment this line.
    #sink_derived.other_func()
    


    여기에 SinkDerive <: SinkBase가 있습니다. 제거contravariant=True하면 경고가 표시됩니다.

    [Pyright reportGeneralTypeIssues] [E] Expression of type "SinkBase" cannot be assigned to declared type > "Sink[Derived]"
       TypeVar "T_contra@Sink" is invariant
        "Base" is incompatible with "Derived"


    contravariant=True 정적 검사기에 Base를 Base 또는 Derive 유형으로 안전하게 사용할 수 있음을 알려줍니다. T_contra가 반공변이라는 주석을 달았지만 예를 들어 Sink[Derived]sink_derived.other_func() 메서드를 호출하면 오류가 발생합니다. 그럼에도 불구하고 contravariantcovariant.와 반대라고 가정하는 것이 일반적입니다.

    대부분의 상황에서 contravariant 또는 covariant를 바로 추가할 필요가 없다고 생각합니다. 검사기가 불평할 때만 적절하게 사용되는 경우 이러한 제네릭 유형의 관계를 자세히 살펴봅니다. 그렇다면 이러한 힌트를 추가하는 것을 고려합니다.

    좋은 웹페이지 즐겨찾기