2019년 6월 23일 일요일

#4 자료구조&알고리즘 - OOP in python


OOP(Object Oriented Programming) in python


문제해결에 필요한 DATA를 Class로 모델링해줄 수 있습니다.

Class는 객체의 모양과 작업을 가지고 있는데요. 모양을 property , 작업을 method라고 불러줍시다.


Class의 기본적인 method


 

    __init__ method


    모든 클래스가 제공하는 첫번째 method는 constructor입니다
    constructor은 data객체를 어떻게 만드는지 정의합니다

    이는 객체가 생성될때 항상 호출 되며 __init__으로 설정해줄수 있습니다

class number:

#the methods go here

def __init__(self, a):

self.num = a


    이런식으로 설정해주면 생성자를 통해 객체를 생성할때 객체변수를 할당하여 시작할수 있습니다.

        ex ) mynumber = number(1)


    그런데 이때 __init__ 안에 들어 있는 self는 객체 자기 자신을 참조하는 특수 매개변수로
    생성자에서는 제공해주면 안됩니다.

    이러면 객체의 가장 처음 상태가 설정이 된것입니다.




    다른 Method 구현하기

    >>>mynumber = number(1) 
    >>>print(mynumber)
    <__main__.mynumber instance at 0x409b1acc>


    이는 print()는 문자열을 print해주는데 객체는 문자열을 리턴하고 있지 않기 때문입니다

    그 이유는 모든 클래스에서 standard method로 __str__을 가지고 있는데 해당 method가
    인스턴스 주소 문자열을 반환하는 것이기 때문입니다

    따라서 우리는 __str__을 override해서 class의 return값을 변경해줄 수 있습니다.


class number:

#the methods go here

def __init__(self, a):
self.num = a

def __str__(self):
return str(self.num)


    이런식으로 변경해주게 되면 원하는 모양으로 출력이 됨을 알 수 있습니다.

    >>>mynumber = number(1) 
    >>>print(mynumber)
            '1'


    __add__ , __eq__ 도 이와 같은 방법으로 변경 가능합니다.


shallow equalliy Vs Deep Equality

Shallow Equality  : 같은 주솟값을 참조하고 있는 변수를 비교하는 것

Deep Equality : 주솟값에 상관없이 같은 값을 가지고 있는지 비교하는 것

python 의 equal 은 기본적으로 shallow 인데 우리는 override를 통해 deep으로 바꿔줄 수 있습니다.



Inheritance


    OOP의 아주 중요한 개념으로

    subclass 는 superclass의 상태나 동작을 상속받아 사용할수 있습니다.

../_images/inheritance1.png


    위의 구조를 inheritance hierarchy 라고 부르는데

    list는 child( or subclass) 이고
    sequence 는 parent(or superclass)라 합니다.

    그리고 이 관계를 list IS-A sequential collection 이라 부릅니다.


    따라서 위의 관계에 따라 list,tuple, string은 sequence의 기능들을 사용할 수 있습니다.

    그러나 mutable함에 있어서는 서로 구분되는데 이처럼 parent의 특성을 사용할수 있고
    각 class마다 구분되는 성질을 가질 수도 있습니다.


    이렇게 상속을 통해 계층적으로 클래스를 구성하게 되면 이전의 코드를
    상속하고 확장하여 더욱 효율적으로 만들어 나갈수 있습니다.


연습해보기


    이를 더 자세히 알아보기 위해 논리 Gate를 구성하는 연습을 해보도록 합시다.

../_images/gates.png

    Logic Gate 는 Gate의 Label(이름) 과 output을 나타냅니다.

    Binary Gate 와 Unary Gate는 input line의 갯수에 대한 구분으로 나뉘어졌습니다..


Logic Gate(최상위 Class)


class LogicGate:

def __init__(self, n):
self.label = n #생성자를 통해서 입력받은 이름을 label로 지정한다
self.output = None

def getLabel(self):
return self.label

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

여기서 getOutput method내의 poerformGateLogic 이 구현되지 않은것을 알수 있다.

이것은 LogicGate 를 상속받은 Gate들의 특성에따라 구현할 수 있도록 놔둔 것으로

OOP의 아주 강력한 기능이며 추상 method 라고 한다

따라서 LogicGate 를 상속하는 Class 들은 performGateLogic()을 구현하도록 강요받으며

클래스의 확장측면에서 유용한 방법이다.



BinaryGate 


class BinaryGate(LogicGate): #LogicGate를 inheritance

def __init__(self, n):
LogicGate.__init__(self, n) #상속받은 Class 의 __init__ 을 명시적으로 사용해준다

self.pinA = None #BinaryGate 이므로 pinA, pinB라는 이름으로 inputline 을 나타낸다
self.pinB = None #pin은 컴퓨터회로에서 입력 라인을 나타내는 말이다.

def getPinA(self): #getLabel을 통해서 Gate의 이름을 보여주고 pin의 값을 입력받는다
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()+"-->"))

BinaryGate Class를 이용해 입력받는 pinA와 pinB를 설정한다



UnaryGate 


class UnaryGate(LogicGate):

def __init__(self, n):
super(UnaryGate,self).__init__(n) #명시적으로 parent class 를 지정하는 대신 super함수를 이용해서
self.pin = None #사용해줄 수 있다. self는 UnaryGate를 말한다.

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


BinaryGate 와 달리 1개의 값만을 입력받으면 된다.


AndGate

class AndGate(BinaryGate): #BinaryGate를 상속받아 And 동작을 하는 Gate를 만들어주자

def __init__(self, n):
super(AndGate, self).__init__(n) #BinaryGate를 상속 받아서 생성자를 동작시켜준다.
#추가적인 동작만 하면되므로 부모클래스의 생성자를 그대로 사용한다
def performGateLogic(self):

a = self.getPinA() #BinaryGate의 getPinA method를 실행해 입력받은 값을 a에
b = self.getPinB() #getPinB method를 통해 입력받은 값을 b로 참조하여 if문을 통과시켜준다.
if a == 1 and b == 1:
return 1
else:
return 0

AndGate는 입력받은 값에 대해 실질적으로 동작을 하는 Gate이므로 and에 대한 연산을 performGateLogic으로 구현한다


실행 


>> > g1 = AndGate("G1")
>> > g1.getOutput()
Enter Pin A input for gate G1-->1
Enter Pin B input for gate G1-->0
0


g1이라는 객체 로 AndGate Class의 인스턴스를 생성한다. 이름은 G1

g1.getOutput()을 해주게 되면

LogicGate getOutput method가 실행 되고

getOutput method 내의 performGateLogic method AndGate에 의해서 실행되며

performGateLogic 내의 getPinBinaryGate에 의해서 실행된다.

따라서 입력을 받아 a와 b에 저장해주고 performGateLogic으로 연산을 거쳐 out을 해주는 과정을 거친다.


HAS-A Class 연습


위의 Gate들은 단일 Gate로 개개인의 input과 output을 가지고 있는데

이런한 Gate들이 서로 연결되어 한 Gate의 output이 다른게이트의 input으로 동작할 수 있다

우리는 이를 Connector Class를 이용해 구현해 줄수 있으며
Connector Class 는 Logic Class , Binary Class, Unary Class ... 로 이루어진 계층구조에 존재하지 않는다.

따라서 우리는 Connector Class HAS_A logicGate라고 해줄수 있는데
이는 Connector Class에 LogicClass의 instance 가 있지만
Connector Class는 LogicClass 의 계층적 구조에 포함되지 않는다는 것을 의미한다

앞에서 언급한 IS-A구조와 HAS-A구조를 구별하여 Class를 설계하는 것이 중요하다

Connector Class


class Connector:

def __init__(self, fgate, tgate): #data가 한 Gate에서
self.fromgate = fgate #다른 게이트로 향할 곳을 설정
self.togate = tgate #fromgate -> togate

tgate.setNextPin(self) #togate를 NextPin으로 설정한다

def getFrom(self):
return self.fromgate

def getTo(self):
return self.togate


BinaryGate 수정하기


class BinaryGate(LogicGate):
#메소드 수정
def getPinA(self):
if self.pinA == None: #pinA가 설정되어 있지 않으면예정처럼 pinA의 입력을 받는다.
return input("Enter Pin A input for gate " + self.getLabel()+"-->")
else:
return self.pinA.getFrom().getOutput() #만약 pinA가 설정이 되어있으면 pinA를 가져와서 fromgate를
#가져와서 getOutput method를 실행한다.

#추가 method
def setNextPin(self, source):
if self.pinA == None: #만약 pinA가 아무런 연결이 되어 있지않으면
self.pinA = source #기본적으로 pinA를 먼저 선택해야합니다
else:
if self.pinB == None: #pinA가 연결이 되어 있고 pinB가 연결이 되어있지 않으면
self.pinB = source #pinB에 연결해줍니다.
else:
raise RuntimeError("Error: NO EMPTY PINS") #두개의 pin모두 연결이 되어있으면 Error를 발생시킵니다.


실행


>> > g1 = AndGate("G1")
>> > g2 = AndGate("G2")
>> > g3 = OrGate("G3")
>> > g4 = NotGate("G4")
>> > c1 = Connector(g1, g3)
>> > c2 = Connector(g2, g3)
>> > c3 = Connector(g3, g4)

위와 같이 실행해 주게 되면

connector 의 생성자로 각각의 object들이 들어가게되고 setNextPin method에 connector객체가 전달되게 된다

이는 나중에 self.pin 에 connector 객체가 들어 있으므로 
getPin을 이용할 fromgate로부터 나오는 output을 getPin의 return값으로 반환하게 된다.


G1(And)  ↘️    
G2(And)  ⟶⟶     G3(Or)  ⟶ 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


g4 getOutput(LogicGate)   ->  g4performGateLogic(Notgate) 실행
g4 의 performGateLogic(Notgate) -> g4의 getPin(UnaryGate)실행
g4의 getPin(UnaryGate)실행  -> g3의 getOutput()실행
g3의 getOutput(LogicGate) -> ⋯

위의 과정을 거쳐

G1 과 G2의 pinA,pinB로 입력받은 값에 대한 연산결과를 각각
G3 , G4 로 전달해줄수 있게 된다.


가장 많이 본 글