ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 1.13. Object-Oriented Programming in Python: Defining Classes
    번역/Problem Solving with Algorithms and Data 2017. 10. 8. 21:17
    반응형

    이 문서는 영문으로된 내용을 구글 번역기를 활용하여 번역한 내용입니다. 
    개인적인 공부 및 추후 다시 볼 수 있도록 하기 위해 개인 블로그에 번역 내용을 옮겨 놓았습니다.
    원문과 내용이 다를시 책임지지 않으며, 저작권 문제가 발생시 언제든 삭제 될 수 있습니다. 


    Problem Solving with Algorithms and Data Structures using Python by Bradley N. Miller, David L. Ranum is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License


    앞에서 파이썬은 객체 지향 프로그래밍 언어라고 언급했습니다. 지금까지는 데이터와 컨트롤 구조의 예제를 보여주기 위해 여러 내장 클래스를 사용했습니다. 객체 지향 프로그래밍 언어에서 가장 강력한 기능 중 하나는 프로그래머 (문제 해결사)가 문제를 해결하는 데 필요한 데이터를 모델링하는 새로운 클래스를 만들 수 있도록하는 기능입니다.
    추상적 인 데이터 유형을 사용하여 데이터 객체의 모양(상태)과 수행 할 수있는 작업(메소드)에 대한 논리적 설명을 제공한다는 점을 기억하십시오. 추상 데이터 유형을 구현하는 클래스를 작성함으로써 프로그래머는 추상화 프로세스를 활용 할 수 있으며 동시에 프로그램에서 추상화를 실제로 사용하는 데 필요한 세부 사항을 제공 할 수 있습니다. 추상적인 데이터 유형을 구현하고자 할 때마다 새로운 클래스를 사용합니다.

    1.13.1. A Fraction Class

    사용자 정의 클래스를 구현하는 세부 정보를 표시하는 가장 일반적인 예는 추상 데이터 형식 Fraction을 구현하는 클래스를 생성하는 것입니다. 우리는 이미 파이썬이 우리가 사용할 수 있는 숫자 클래스를 제공한다는 것을 보았습니다. 그러나 분수처럼 보이는 데이터 객체를 생성하는 것이 가장 적절할 때가 있습니다.
    3/5와 같은 부분은 두 부분으로 구성됩니다. 분자라고하는 최상위 값은 정수가 될 수 있습니다. 분모라고 부르는 맨 아래 값은 0보다 큰 정수일 수 있습니다 (음의 분수에는 음의 분자가 있음). 모든 분수에 대해 부동 소수점 근사를 만들 수 있지만,이 경우 분수를 정확한 값으로 나타내려고합니다.
    Fraction 유형에 대한 작업을 수행하면 Fraction 데이터 객체가 다른 숫자 값 처럼 동작 할 수 있습니다. 우리는 분수를 더하고, 빼고, 곱하고, 나눌 수 있어야 합니다. 표준 "슬래시"형식 (예 : 3/5)을 사용하여 분수를 표시 할 수 있기를 원합니다. 또한 모든 분수 메소드는 결과가 가장 낮은 조건으로 반환 되어야 하므로 어떤 계산이 수행 되더라도 항상 가장 일반적인 형태로 끝납니다.
    파이썬에서는 함수 정의와 구문 적으로 유사한 이름과 메소드 정의 세트를 제공하여 새로운 클래스를 정의합니다. 이 예제의 경우,
    class Fraction:

       #the methods go here

    메소드를 정의 할 수있는 프레임 워크를 제공합니다. 모든 클래스가 제공해야하는 첫 번째 메소드는 생성자입니다. 생성자는 데이터 객체를 만드는 방법을 정의합니다. Fraction 객체를 만들려면 분자와 분모의 두 가지 데이터를 제공해야합니다. 파이썬에서 생성자 메소드는 항상 __init__ (init의 앞뒤에 두 개의 밑줄)로 불려지며 Listing 2와 같습니다.
    Listing 2
    class Fraction:

        def __init__(self,top,bottom):

            self.num = top
            self.den = bottom

    형식 매개 변수 목록에는 세 항목 (self, top, bottom)이 들어 있습니다. self는 객체 자체에 대한 참조로 항상 사용되는 특수 매개 변수입니다. 항상 첫 번째 형식 매개 변수 여야합니다. 그러나 호출시 실제 매개 변수 값이 제공되지 않습니다. 앞에서 설명한 것처럼 분수에는 분자와 분모의 두 가지 상태 데이터가 필요합니다. 생성자의 self.num이라는 표기법은 fraction 객체가 num이라는 내부 데이터 객체를 상태의 일부로 정의하도록 정의합니다. 마찬가지로 self.den은 분모를 만듭니다. 두 개의 형식 매개 변수의 값은 처음에 상태에 할당되어 새 fraction 객체가 시작 값을 알 수 있게 합니다.
    Fraction 클래스의 인스턴스를 만들려면 생성자를 호출해야 합니다. 이것은 클래스의 이름을 사용하고 필요한 상태에 대한 실제 값을 전달함으로써 발생합니다 (우리가 직접 __init__을 호출하지 않는다는 것에 유의하십시오). 예를 들어,
    myfraction = Fraction(3,5)

    myfraction이라는 3/5 분수를 나타내는 객체를 만듭니다. 그림 5는 현재 구현 된이 객체를 보여줍니다.
    다음으로해야 할 일은 추상적인 데이터 유형에 필요한 동작을 구현하는 것입니다. 먼저 Fraction 객체를 인쇄하려고 할 때 어떤 일이 일어나는지 생각해보십시오.
    >>> myf = Fraction(3,5)
    >>> print(myf)
    <__main__.Fraction instance at 0x409b1acc>

    fraction 객체 인 myf는 이 요청 인쇄에 응답하는 방법을 알지 못합니다. print 함수는 문자열이 출력에 기록 될 수 있도록 객체가 문자열로 변환되도록 요구합니다. myf가 갖는 유일한 선택은 변수(주소 자체)에 저장된 실제 참조를 표시하는 것입니다. 이것은 우리가 원하는 것이 아닙니다.
    이 문제를 해결할 수 있는 방법은 두 가지가 있습니다. 하나는 show라는 메소드를 정의하는 것으로 Fraction 객체 자체를 문자열로 인쇄 할 수 있습니다. Listing 3처럼이 메소드를 구현할 수 있습니다. 이전과 같이 Fraction 객체를 생성하면 자체를 표시하도록 요청할 수 있습니다. 즉, 적절한 형식으로 자체를 인쇄 할 수 있습니다. 불행히도, 이것은 일반적으로 작동하지 않습니다. 인쇄가 제대로 작동하려면 Fraction 클래스에 문자열로 변환하는 방법을 알려줘야합니다. print 함수가 그 일을 하기 위해 필요할 것입니다.
    Listing 3
    def show(self):
         print(self.num,"/",self.den)

    >>> myf = Fraction(3,5)
    >>> myf.show()
    3 / 5
    >>> print(myf)
    <__main__.Fraction instance at 0x40bce9ac>
    >>>

    파이썬에서 모든 클래스는 제공되는 표준 메소드를 가지고 있지만 제대로 작동하지 않을 수 있습니다. 이 중 하나 인 __str__은 객체를 문자열로 변환하는 메서드입니다. 이 메소드의 기본 구현은 이미 본 것처럼 인스턴스 주소 문자열을 반환하는 것입니다. 우리가 해야 할 일은 이 방법에 대한 "더 나은"구현을 제공하는 것입니다. 이 구현은 이전 구현을 재정의하거나 메소드의 동작을 재정의 한다고 말할 것입니다.
    이를 위해 __str__이라는 이름의 메소드를 정의하고 Listing 4와 같이 새로운 구현을 제공합니다. 이 정의에는 특수 매개 변수 self를 제외한 다른 정보는 필요하지 않습니다. 이 메서드는 각 내부 상태 데이터를 문자열로 변환 한 다음 문자열 연결을 사용하여 문자열 사이에 / 문자를 배치하여 문자열 표현을 작성합니다. 결과 문자열은 Fraction 객체가 자신을 문자열로 변환하라는 요청을 받을 때마다 반환됩니다. 이 함수가 사용되는 다양한 방법에 주목하십시오.
    Listing 4
    def __str__(self):
        return str(self.num)+"/"+str(self.den)

    >>> myf = Fraction(3,5)
    >>> print(myf)
    3/5
    >>> print("I ate", myf, "of the pizza")
    I ate 3/5 of the pizza
    >>> myf.__str__()
    '3/5'
    >>> str(myf)
    '3/5'
    >>>

    우리는 새로운 Fraction 클래스에 대해 다른 많은 메서드를 재정의 할 수 있습니다. 이것들 중 가장 중요한 것은 기본적인 산술 연산입니다. 두 개의 Fraction 객체를 만들고 표준 "+"표기법을 사용하여 함께 추가 할 수 있기를 원합니다. 이 시점에서 두 개의 분수를 더하면 다음과 같이 됩니다.
    >>> f1 = Fraction(1,4)
    >>> f2 = Fraction(1,2)
    >>> f1+f2

    Traceback (most recent call last):
      File "<pyshell#173>", line 1, in -toplevel-
        f1+f2
    TypeError: unsupported operand type(s) for +:
              'instance' and 'instance'
    >>>

    오류를 면밀히 살펴보면 문제는 "+"연산자가 Fraction 피연산자를 이해하지 못한다는 것을 알 수 있습니다.
    Fraction 클래스에 추가 메서드를 재정의하는 메서드를 제공하여이 문제를 해결할 수 있습니다. 파이썬에서는 이 메소드를 __add__라고하며 두 개의 매개 변수가 필요합니다. 첫 번째 self는 항상 필요하며 두 번째 표현식은 표현식의 다른 피연산자를 나타냅니다. 예를 들어,
    f1.__add__(f2)

    Fraction 객체 f1 Fraction 객체 f2를 추가하도록 요청합니다. 표준 표기법 f1+f2로 작성할 수 있습니다.
    2 개의 분수에는 동일한 분모가 추가되어야합니다. 동일한 분모를 갖는 가장 쉬운 방법은 두 분모의 곱을 공통 분모로 사용하여 구현을 Listing 5에 표시하는 것입니다. 더하기 함수는 합계의 분자와 분모를 가진 새로운 Fraction 객체를 반환합니다. 분수가 포함된 표준 산술 표현식을 작성하고 추가 결과를 할당한 다음 결과를 인쇄하여 이 방법을 사용할 수 있습니다.
    Listing 5
    def __add__(self,otherfraction):

         newnum = self.num*otherfraction.den + self.den*otherfraction.num
         newden = self.den * otherfraction.den

         return Fraction(newnum,newden)

    >>> f1=Fraction(1,4)
    >>> f2=Fraction(1,2)
    >>> f3=f1+f2
    >>> print(f3)
    6/8
    >>>

    추가 방법은 우리가 원하는대로 작동하지만 한 가지 더 좋을 수 있습니다. 6/8이 올바른 결과()이지만 "가장 낮은 용어"표현이 아님에 유의하십시오. 가장 좋은 표현은 3/4입니다. 우리의 결과가 항상 최하위라는 것을 확실히하기 위해서는 분수를 줄이는 방법을 알고 있는 도우미 함수가 필요합니다. 이 함수는 가장 큰 공약수 또는 GCD를 찾아야합니다. 그러면 분자와 분모를 GCD로 나눌 수 있고 그 결과는 가장 낮은 항으로 감소 할 것입니다.
    가장 큰 공약수를 찾는 가장 잘 알려진 알고리즘은 Euclid 's Algorithm입니다. 자세한 내용은 8 장에서 설명합니다. 유클리드 알고리즘 (Euclid 's Algorithm)은 n이 m을 균등하게 나눈다면 두 정수 m과 n의 최대 공약수는 n이라고 말합니다. 그러나 n이 m을 균등하게 나눌 수 없다면 답은 n의 가장 큰 공약수이며 나머지를 m으로 나눈 것입니다. 우리는 여기서 반복 구현을 제공 할 것입니다 (ActiveCode 1 참조). 분모가 양수 일 때만 GCD 알고리즘을 구현할 수 있습니다. 음의 분수는 음의 분자로 표시 될 것이므로 분수 클래스에서는이 값을 사용할 수 있습니다.
    RunLoad HistoryShow CodeLens
    1
    def gcd(m,n):
    2
        while m%n != 0:
    3
            oldm = m
    4
            oldn = n
    5
    6
            m = oldn
    7
            n = oldm%oldn
    8
        return n
    9
    10
    print(gcd(20,10))
    11
    The Greatest Common Divisor Function (gcd_cl)
    이제 이 함수를 사용하여 분수를 줄일 수 있습니다. 분율을 가장 낮은 항으로 놓기 위해 분자와 분모를 최대 공약수로 나눕니다. 따라서 분수 6/8의 경우 최대 공약수는 2입니다. 상단과 하단을 2로 나누면 새로운 분수 인 3/4가 작성됩니다 (Listing 6 참조).
    Listing 6
    def __add__(self,otherfraction):
        newnum = self.num*otherfraction.den + self.den*otherfraction.num
        newden = self.den * otherfraction.den
        common = gcd(newnum,newden)
        return Fraction(newnum//common,newden//common)

    >>> f1=Fraction(1,4)
    >>> f2=Fraction(1,2)
    >>> f3=f1+f2
    >>> print(f3)
    3/4
    >>>

    우리의 Fraction 객체는 매우 유용한 두 가지 메소드를 가지고 있으며 그림 6처럼 보입니다. 예제 Fraction 클래스에 포함 해야하는 추가 메서드 그룹을 사용하면 두 분수를 서로 비교할 수 있습니다. 두 개의 Fraction 객체인 f1 f2가 있다고 가정합니다. f1==f2는 동일한 객체에 대한 참조 인 경우에만 True가됩니다. 동일한 분자와 분모를 가진 두 개의 다른 객체는이 구현에서 동일하지 않습니다. 이를 얕은 평등이라고합니다 (그림 7 참조).
    우리는 __eq__ 메소드를 오버라이드 (override)함으로써 깊은 평등 (그림 7 참조)동일한 참조가 아닌 동일한 값으로을 만들 수 있습니다. __eq__ 메소드는 모든 클래스에서 사용할 수있는 또 다른 표준 메소드입니다. __eq__ 메서드는 두 개체를 비교하여 값이 같으면 True를 반환하고, 그렇지 않으면 False를 반환합니다.
    Fraction 클래스에서 두 분수를 다시 일반화하고 분자를 비교하여 __eq__ 메쏘드를 구현할 수 있습니다 (Listing 7 참조). 오버라이드 될 수 있는 다른 관계 연산자가 있음을 알아 두는 것이 중요합니다. 예를 들어 __le__ 메서드는 보다 작거나 같은 기능을 제공합니다.
    Listing 7
    def __eq__(self, other):
        firstnum = self.num * other.den
        secondnum = other.num * self.den

        return firstnum == secondnum

    지금까지 완전한 Fraction 클래스는 ActiveCode 2에 표시됩니다. 나머지 산술 및 관계형 메서드는 연습으로 남겨 둡니다.
    RunLoad HistoryShow CodeLens
    1
    def gcd(m,n):
    2
        while m%n != 0:
    3
            oldm = m
    4
            oldn = n
    5
    6
            m = oldn
    7
            n = oldm%oldn
    8
        return n
    9
    10
    class Fraction:
    11
         def __init__(self,top,bottom):
    12
             self.num = top
    13
             self.den = bottom
    14
    15
         def __str__(self):
    16
             return str(self.num)+"/"+str(self.den)
    17
    18
         def show(self):
    19
             print(self.num,"/",self.den)
    20
    21
         def __add__(self,otherfraction):
    22
             newnum = self.num*otherfraction.den + \
    23
                          self.den*otherfraction.num
    24
             newden = self.den * otherfraction.den
    25
             common = gcd(newnum,newden)
    26
             return Fraction(newnum//common,newden//common)
    27
    28
         def __eq__(self, other):
    29
             firstnum = self.num * other.den
    30
             secondnum = other.num * self.den
    31
    32
             return firstnum == secondnum
    33
    34
    x = Fraction(1,2)
    35
    y = Fraction(2,3)
    36
    print(x+y)
    37
    print(x == y)
    38
    The Fraction Class (fraction_class)
    Self Check
    To make sure you understand how operators are implemented in Python classes, and how to properly write methods, write some methods to implement *, /, and - . Also implement comparison operators > and <
    RunShow FeedbackShow CodeShow CodeLensCode Coach
    (self_check_4)

    1.13.2. Inheritance: Logic Gates and Circuits

    마지막 섹션에서는 객체 지향 프로그래밍의 또 다른 중요한 측면을 소개합니다. 상속은 한 클래스가 다른 클래스와 관련이있는 것과 동일한 방식으로 사람들이 서로 관련 될 수있는 능력입니다. 자녀는 부모로부터 특성을 상속받습니다. 마찬가지로 Python 하위 클래스는 부모 클래스에서 특성 데이터와 동작을 상속받을 수 있습니다. 이러한 클래스는 종종 하위 클래스상위 클래스라고합니다.
    그림 8은 파이썬에 내장된 콜렉션과 다른 것들과의 관계를 보여줍니다. 우리는 이것과 같은 관계 구조를 상속 계층 구조라고 부릅니다. 예를 들어, 목록은 순차 수집의 하위 항목입니다. 이 경우 목록을 자식이라고하고 시퀀스를 부모 (또는 하위 클래스 목록 및 수퍼 클래스 시퀀스)라고합니다. 이것은 종종 IS-A Relationship (IS-A 순차 수집 목록)라고합니다. 즉, 목록은 시퀀스에서 중요한 특성, 즉 기본 데이터의 순서 지정과 연결, 반복 및 인덱싱과 같은 작업을 상속 받음을 의미합니다.
    목록, 튜플 및 문자열은 모든 유형의 순차적 컬렉션입니다. 이들은 모두 공통된 데이터 구성 및 운영을 상속합니다. 그러나 이들 각각은 데이터가 동 질적인지 여부와 컬렉션이 불변인지에 따라 구분됩니다. 아이들은 모두 부모로부터 얻지만 추가 특성을 추가하여 자신을 구별합니다.
    이 계층 적 방식으로 클래스를 구성함으로써 객체 지향 프로그래밍 언어는 이전에 작성된 코드를 새로운 상황의 요구를 충족시키기 위해 확장 할 수 있게 합니다. 또한이 계층적 방식으로 데이터를 구성함으로써 존재하는 관계를 더 잘 이해할 수 있습니다. 우리는 추상적인 표현을 보다 효율적으로 만들 수 있습니다.
    이 아이디어를 더 깊이 탐구하기 위해 디지털 회로를 시뮬레이션하는 시뮬레이션을 구성 할 것입니다. 이 시뮬레이션의 기본 빌딩 블록은 로직 게이트 입니다. 이 전자 스위치는 입력과 출력 간의 부울 대수 관계를 나타냅니다. 일반적으로 게이트에는 단일 출력 라인이 있습니다. 출력의 값은 입력 행에 주어진 값에 따라 다릅니다.
    AND 게이트는 두 개의 입력 라인을 가지며, 각 입력 라인은 0 또는 1 (각각 False 또는 True을 나타냄) 일 수 있습니다. 두 입력 행 모두 값 1 인 경우 결과 출력은 1입니다. 그러나 입력 행 중 하나 또는 모두가 0이면 결과는 0입니다. OR 게이트는 또한 2 개의 입력 라인을 가지며 입력 값 중 하나 또는 둘 모두가 1이면 1을 생성합니다. 두 입력 라인이 모두 0 인 경우 결과는 0입니다.
    NOT 게이트는 단일 입력 라인 만 가지고 있다는 점에서 다른 두 게이트와 다릅니다. 출력 값은 단순히 입력 값의 반대입니다. 입력에 0이 나타나면 출력에서 1이 생성됩니다. 마찬가지로 1은 0을 생성합니다. 그림 9는 각 게이트가 일반적으로 어떻게 표시되는지 보여줍니다. 각 게이트에는 게이트에서 수행되는 입력 - 출력 매핑을 보여주는 값의 진리표도 있습니다.
    이러한 게이트를 다양한 패턴으로 결합한 다음 일련의 입력 값을 적용함으로써 논리 기능을 가진 회로를 구축 할 수 있습니다. 그림 10은 두 개의 AND 게이트, 하나의 OR 게이트 및 하나의 NOT 게이트로 구성된 회로를 보여줍니다. 2 개의 AND 게이트로부터의 출력 라인은 OR 게이트로 직접 공급되고, OR 게이트로부터의 결과 출력은 NOT 게이트에 주어집니다. 네 개의 입력 라인 (각 AND 게이트에 대해 두 개)에 입력 값 세트를 적용하면 값이 처리되고 결과가 NOT 게이트의 출력에 나타납니다. 그림 10은 값이 있는 예를 보여줍니다.
    회로를 구현하기 위해 먼저 논리 게이트를 표현할 것입니다. 로직 게이트는 클래스 상속 계층 구조로 쉽게 구성됩니다 (그림 11 참조). 계층의 맨 위에있는 LogicGate 클래스는 논리 게이트의 가장 일반적인 특성, 즉 게이트 레이블과 출력 라인을 나타냅니다. 다음 레벨의 서브 클래스는 논리 게이트를 두 개의 패밀리, 즉 하나의 입력 행과 두 개의 패밀리로 나눕니다. 그 아래에는 각각의 특정 논리 기능이 나타납니다.
    이제 가장 일반적인 LogicGate부터 시작하여 클래스를 구현할 수 있습니다. 앞에서 언급했듯이 각 게이트에는 식별용 레이블과 단일 출력 라인이 있습니다. 또한 게이트 사용자가 레이블에 게이트를 요청할 수있는 방법이 필요합니다.
    모든 LogicGate가 필요로하는 다른 동작은 출력 값을 알 수있는 기능입니다. 이를 위해서는 게이트가 현재 입력을 기반으로 적절한 로직을 수행해야합니다. 출력을 생성하려면 게이트가 그 논리가 무엇인지 구체적으로 알아야합니다. 이것은 논리 계산을 수행하는 메소드를 호출하는 것을 의미합니다. 전체 클래스는 Listing 8과 같습니다.
    Listing 8
    class LogicGate:

        def __init__(self,n):
            self.label = n
            self.output = None

        def getLabel(self):
            return self.label

        def getOutput(self):
            self.output = self.performGateLogic()
            return self.output

    이 시점에서 우리는 performGateLogic 함수를 구현하지 않을 것입니다. 그 이유는 각 게이트가 자체 논리 연산을 수행하는 방법을 알지 못하기 때문입니다. 세부 정보는 계층에 추가 된 개별 게이트에 포함됩니다. 이것은 객체 지향 프로그래밍에서 매우 강력한 아이디어입니다. 우리는 아직 존재하지 않는 코드를 사용할 메소드를 작성하고 있습니다. 매개 변수 self는 메소드를 호출하는 실제 게이트 객체에 대한 참조입니다. 계층에 추가되는 모든 새로운 논리 게이트는 단순히 performGateLogic 함수를 구현해야하며 적절한 시간에 사용됩니다. 완료되면 게이트는 출력 값을 제공 할 수 있습니다. 현재 존재하는 계층 구조를 확장하고 계층 구조가 새 클래스를 사용해야하는 특정 기능을 제공하는이 기능은 기존 코드를 재사용하는 데 매우 중요합니다.
    우리는 입력 라인의 수에 따라 논리 게이트를 분류했습니다. AND 게이트에는 두 개의 입력 라인이 있습니다. 또한 OR 게이트에는 2 개의 입력 라인이 있습니다. NOT 게이트에는 하나의 입력 라인이 있습니다. BinaryGate 클래스는 LogicGate의 하위 클래스이며 두 개의 입력 행을 추가합니다. UnaryGate 클래스는 LogicGate의 하위 클래스이지만 단일 입력 행만 갖습니다. 컴퓨터 회로 설계에서 이러한 라인을 때때로 "핀"이라고 부르므로 여기서는이 용어를 사용합니다.
    Listing 9
    class BinaryGate(LogicGate):

        def __init__(self,n):
            LogicGate.__init__(self,n)

            self.pinA = None
            self.pinB = None

        def getPinA(self):
            return int(input("Enter Pin A input for gate "+ self.getLabel()+"-->"))

        def getPinB(self):
            return int(input("Enter Pin B input for gate "+ self.getLabel()+"-->"))

    Listing 10
    class UnaryGate(LogicGate):

        def __init__(self,n):
            LogicGate.__init__(self,n)

            self.pin = None

        def getPin(self):
            return int(input("Enter Pin input for gate "+ self.getLabel()+"-->"))

    Listing 9와 Listing 10에서는 이 두 클래스를 구현합니다. 이 두 클래스의 생성자는 부모의 __init__ 메소드를 사용하여 부모 클래스의 생성자에 대한 명시 적 호출로 시작합니다. BinaryGate 클래스의 인스턴스를 만들 때 먼저 LogicGate 게이트에서 상속된 모든 데이터 항목을 초기화 하려고 합니다. 이 경우 게이트의 레이블을 의미합니다. 그런 다음 생성자는 두 개의 입력 행 (pinA pinB)을 추가합니다. 이것은 클래스 계층 구조를 만들 때 항상 사용해야하는 매우 일반적인 패턴입니다. 자식 클래스 생성자는 부모 클래스 생성자를 호출 한 다음 고유 한 구분 데이터로 이동해야합니다.
    파이썬에는 부모 클래스에 명시 적으로 이름을 지정하는 대신 사용할 수있는 super라는 함수가 있습니다. 이것은보다 일반적인 메커니즘이며, 특히 클래스에 부모가 두 개 이상있는 경우에 널리 사용됩니다. 그러나이 내용은이 소개에서 논의 할 내용이 아닙니다. 예를 들어 위의 LogicGate의 예에서 볼 수 있습니다. __init__(self,n)super(UnaryGate,self).__init__(n)로 대체 될 수 있습니다.
    BinaryGate 클래스가 추가하는 유일한 동작은 두 입력 행에서 값을 가져 오는 기능입니다. 이 값은 외부 장소에서 가져온 값이므로 사용자에게 입력 문을 통해 입력하도록 요청할 것입니다. 단 하나의 입력 행이 있다는 것을 제외하고는 동일한 구현이 UnaryGate 클래스에 대해 발생합니다.
    입력 라인의 수에 따라 게이트에 대한 일반 클래스가 생겼으므로 고유 한 동작을하는 특정 게이트를 만들 수 있습니다. 예를 들어 AndGate 클래스는 BinaryGate의 하위 클래스이며 AND 게이트에는 두 개의 입력 선이 있기 때문입니다. 이전과 마찬가지로 생성자의 첫 번째 행은 부모 클래스 생성자 (BinaryGate)를 호출하고 부모 클래스 생성자 (LogicGate)를 호출합니다. AndGate 클래스는 두 개의 입력 행, 출력 행 및 레이블을 상속하므로 새 데이터를 제공하지 않습니다.
    Listing 11
    class AndGate(BinaryGate):

        def __init__(self,n):
            BinaryGate.__init__(self,n)

        def performGateLogic(self):

            a = self.getPinA()
            b = self.getPinB()
            if a==1 and b==1:
                return 1
            else:
                return 0

    AndGate가 추가해야하는 유일한 것은 앞에서 설명한 부울 연산을 수행하는 특정 동작입니다. 여기서는 performGateLogic 메소드를 제공 할 수있는 곳입니다. AND 게이트의 경우이 메서드는 먼저 두 입력 값을 가져와야하며 두 입력 값이 모두 1이면 1을 반환합니다. 전체 클래스는 Listing 11과 같습니다.
    인스턴스를 생성하고 인스턴스의 출력을 계산하도록 요구함으로써 AndGate 클래스를 보여줄 수 있습니다. 다음 세션에서는 내부 레이블이 "G1"AndGate 개체 g1을 보여줍니다. getOutput 메소드를 호출 할 때 객체는 먼저 performGateLogic 메소드를 호출해야하며,이 메소드는 차례로 두 입력 행을 조회합니다. 일단 값이 제공되면 올바른 출력이 표시됩니다.
    >>> g1 = AndGate("G1")
    >>> g1.getOutput()
    Enter Pin A input for gate G1-->1
    Enter Pin B input for gate G1-->0
    0

    OR 게이트와 NOT 게이트에 동일한 개발이 가능합니다. OrGate 클래스는 BinaryGate의 하위 클래스이기도하고 NotGate 클래스는 UnaryGate 클래스를 확장합니다. 이 두 클래스 모두 고유 한 performGateLogic 기능을 제공해야합니다.
    먼저 게이트 클래스 중 하나의 인스턴스를 생성 한 다음 게이트에 출력을 요청하여 단일 게이트를 사용할 수 있습니다 (차례대로 입력을 제공해야 함). 예를들면 :
    >>> g2 = OrGate("G2")
    >>> g2.getOutput()
    Enter Pin A input for gate G2-->1
    Enter Pin B input for gate G2-->1
    1
    >>> g2.getOutput()
    Enter Pin A input for gate G2-->0
    Enter Pin B input for gate G2-->0
    0
    >>> g3 = NotGate("G3")
    >>> g3.getOutput()
    Enter Pin input for gate G3-->0
    1

    기본 게이트가 작동 했으므로 회로 만들기에 관심을 돌릴 수 있습니다. 회로를 만들기 위해서는 게이트를 서로 연결해야하며, 하나의 출력은 다른 게이트의 입력으로 흐르게됩니다. 이를 위해 우리는 Connector라는 새로운 클래스를 구현할 것입니다.
    Connector 클래스는 게이트 계층에 상주하지 않습니다. 그러나 각 커넥터에는 두 개의 게이트가 있으며 한 쪽 끝에 하나씩 게이트 계층이 사용됩니다 (그림 12 참조). 이 관계는 객체 지향 프로그래밍에서 매우 중요합니다. 그것은 HAS-A 관계라고 불립니다. 이전에 "IS-A 관계"라는 구를 사용하여 하위 클래스가 상위 클래스와 관련이 있다고 말한 것을 예로 들겠습니다 (예 : UnaryGate IS-A LogicGate).
    이제 Connector 클래스를 사용하여 ConnectorLogicGate 클래스의 인스턴스를 가지지만 계층 구조의 일부가 아닌 LogicGate라는 HAS-A LogicGate가 있다고 합니다. 클래스를 디자인 할 때 상속이 필요한 IS-A 관계와 상속이없는 HAS-A 관계가있는 클래스를 구별하는 것이 매우 중요합니다.
    Listing 12는 Connector 클래스를 보여줍니다. 각 커넥터 객체 내의 두 게이트 인스턴스는 데이터 값이 한 게이트의 출력에서 다음 게이트의 입력 라인으로 "흐를"것을 인식하여 togate 및 togate로 참조됩니다. setNextPin에 대한 호출은 연결에 매우 중요합니다 (Listing 13 참조). 이 메소드를 게이트 클래스에 추가하여 togate가 연결에 적합한 입력 라인을 선택할 수 있도록해야합니다.
    Listing 12
    class Connector:

        def __init__(self, fgate, tgate):
            self.fromgate = fgate
            self.togate = tgate

            tgate.setNextPin(self)

        def getFrom(self):
            return self.fromgate

        def getTo(self):
            return self.togate

    BinaryGate 클래스에서 두 개의 가능한 입력 라인이있는 게이트의 경우 커넥터는 한 라인에만 연결되어야합니다. 둘 다 사용할 수있는 경우 기본적으로 pinA를 선택합니다. pinA가 이미 연결되어 있다면 pinB를 선택합니다. 사용 가능한 입력 라인이 없는 게이트에 연결할 수 없습니다.
    Listing 13
    def setNextPin(self,source):
        if self.pinA == None:
            self.pinA = source
        else:
            if self.pinB == None:
                self.pinB = source
            else:
               raise RuntimeError("Error: NO EMPTY PINS")

    이제는 이전과 같이 외부와 입력 라인에 연결된 게이트의 출력에서 두 곳의 입력을 얻을 수 있습니다. 이렇게하려면 getPinA 및 getPinB 메소드를 변경해야 합니다 (Listing 14 참조). 입력 라인이 아무 것도(None) 연결되어 있지 않다면 이전과 같이 외부 적으로 물어보십시오. 그러나 연결이 있으면 연결이 액세스되고 fromgate의 출력 값이 검색됩니다. 이것은 차례로 게이트가 로직을 처리하도록합니다. 모든 입력을 사용할 수 있고 최종 출력 값이 해당 게이트의 필수 입력이 될 때까지 이 작업이 계속됩니다. 어떤면에서는 회로가 역으로 작동하여 최종적으로 출력을 생성하는 데 필요한 입력을 찾습니다.
    Listing 14
    def getPinA(self):
        if self.pinA == None:
            return input("Enter Pin A input for gate " + self.getName()+"-->")
        else:
            return self.pinA.getFrom().getOutput()

    다음 단락은 앞에서 설명한 회로를 구성합니다.
    >>> g1 = AndGate("G1")
    >>> g2 = AndGate("G2")
    >>> g3 = OrGate("G3")
    >>> g4 = NotGate("G4")
    >>> c1 = Connector(g1,g3)
    >>> c2 = Connector(g2,g3)
    >>> c3 = Connector(g3,g4)

    2 개의 AND 게이트 (g1g2)의 출력은 OR 게이트 (g3)에 연결되고 그 출력은 NOT 게이트 (g4)에 연결됩니다. NOT 게이트의 출력은 전체 회로의 출력입니다. 예를들면 :
    >>> g4.getOutput()
    Pin A input for gate G1-->0
    Pin B input for gate G1-->1
    Pin A input for gate G2-->1
    Pin B input for gate G2-->1
    0

    ActiveCode 4를 사용하여 직접 해보십시오.
    RunLoad HistoryShow CodeLens
    88
                self.pin = source
    89
            else:
    90
                print("Cannot Connect: NO EMPTY PINS on this gate")
    91
    92
    93
    class NotGate(UnaryGate):
    94
    95
        def __init__(self,n):
    96
            UnaryGate.__init__(self,n)
    97
    98
        def performGateLogic(self):
    99
            if self.getPin():
    100
                return 0
    101
            else:
    102
                return 1
    103
    104
    105
    class Connector:
    106
    107
        def __init__(self, fgate, tgate):
    108
            self.fromgate = fgate
    109
            self.togate = tgate
    110
    111
            tgate.setNextPin(self)
    112
    113
        def getFrom(self):
    114
            return self.fromgate
    115
    116
        def getTo(self):
    117
            return self.togate
    118
    119
    120
    def main():
    121
       g1 = AndGate("G1")
    122
       g2 = AndGate("G2")
    123
       g3 = OrGate("G3")
    124
       g4 = NotGate("G4")
    125
       c1 = Connector(g1,g3)
    126
       c2 = Connector(g2,g3)
    127
       c3 = Connector(g3,g4)
    128
       print(g4.getOutput())
    129
    130
    main()
    131

    The Complete Circuit Program. (complete_cuircuit)

    반응형

    '번역 > Problem Solving with Algorithms and Data' 카테고리의 다른 글

    1.15. Key Terms  (0) 2017.10.08
    1.14. Summary  (0) 2017.10.08
    1.12. Defining Functions  (0) 2017.10.08
    1.11. Exception Handling  (0) 2017.10.08
    1.10. Control Structures  (0) 2017.10.08
Designed by Tistory.