본문 바로가기

Python

20210123_Python 와 Ruby 비교03, 객체지향, class, instance

생활코딩 "python&ruby"강의 참고

Python vs Ruby (루비 위주)

객체 지향 프로그래밍 (Object Oriented Programming)

  • 철학적이지만, python과 ruby의 기능을 배우는 것으로 생각하는게 구체적인 느낌으로 배울수 있을 것이다.

  • 모듈(module)은 함수를 수납하여 복잡도를 단순화 시킨다.
  • 클래스(class)는 모듈처럼 그룹핑을 통한 수납이 가능한데, 모듈과 다르게 연관된 함수와 변수를 수납한다.

  • 과정 class를 복제하여 instance를 만든다.
  • 그런데 이 instance는 class와 똑같은 변수와 함수를 품지만, 변수의 값만 각각 바뀌어 품고 있는 공통된 함수를 이용해 결과를 낸다.

1. 객체 지향 프로그래밍의 사례

1) 문자열(string) 클래스 사용 사례

name1 = 'tom' # 문자열 만드는 방법 1 (생성시 코드 효율성을 위해서)
name2 = String.new('jerry') # 문자열 만드는 방법 2 (원래)
name3 = String.new('snoopy')
puts name1.reverse()
puts name1.upcase()
puts name1.size()
  • String : 클래스 , .new(값) : 생성 함수를 통해 -> 인스턴스화가 됨
  • 이를 사용하기 위해서는 name1이라는 변수에 할당시켜야 한다.
  • 이렇게 인스턴스가 되면 기본적으로 가지고 있는 내장함수를 사용하여 name1을 변화 시킬수 있게 된다.

2) 배열(array) 클래스 사용 사례

names = Array.new()
names.push('tom')
names.push('jerry')
names.push('snoopy')
puts names
print names
puts names.join('-') # join 메서드는 값들을 -로 연결해줌 
  • Array : 클래스 , .new()를 통해 생성하고 names에 할당
  • names 인스턴스의 push 함수를 통해 값을 추가


2. 클래스 만들어 보기

1) Ruby

  • Ruby에서 생성자
class Cal
    def initialize(v1, v2) # initialize 생성자
        p v1, v2
    end
end

c1 = Cal.new(10, 10)
# 10
# 10
  • Cal 이라는 클래스를 만드는데 항상 첫글자는 대문자
  • initaialize 라는 생성자 함수를 지정함으로서 인스턴스 생성시 제일먼저 생성자 함수가 실행되게 약속 되어 있고 이를 통해 초기화를 시키는 것임
  • 어차피 생성자 함수는 인스턴스가 만들어 지자 마자 실행되는 것이기에 p v1, v2 은 입력값을 확인용으로만 넣었다.

  • Ruby에서 클래스 함수(메소드)
class Cal
    def initialize(v1, v2) # initialize 생성자
        # p v1, v2
        @v1 = v1
        @v2 = v2
    end
    def add()
        return @v1+@v2
    end
    def subtract()
        return @v1-@v2
    end
end

c1 = Cal.new(10, 10)
p c1.add() # 10 ,10 더한 -> 20 리턴
p c1.subtract() # 10 ,10 을 뺀 -> 0 리턴

c2 = Cal.new(30,20)
p c2.add() # 30 ,20 더한 -> 50 리턴
p c2.subtract() # 30 ,20 을 뺀 -> 10 리턴
  • 생성자에서 선언한 매개변수의 경우 그냥은 다른 함수에서는 사용을 못한다.(공유X) 이를 지역 변수라고 함
  • 공유하기 위해선 @v1 처럼 @를 붙여 공유할수있는 변수를 만들어 주어야 한다. 이 변수를 인스턴스 변수라고 한다.
  • 이를 통해 다른 함수에서 @를 붙여 사용하면 공유가 된다.


2) Python

  • Python에서 생성자와 메소드
class Cal(object):
    def __init__(self, v1, v2) # __init__ 생성자
        print(v1, v2)

c1 = Cal(10, 10)
# 10 10
  • 생성자
  • Cal 이라는 클래스를 만드는데 항상 첫글자는 대문자로 하는게 좋다.
  • __init__ 라는 생성자 함수를 지정함으로서 인스턴스 생성시 제일먼저 생성자 함수가 실행되게 약속 되어 있고 이를 통해 초기화를 시키는 것임
  • 어차피 생성자 함수는 인스턴스가 만들어 지자 마자 실행되는 것이기에 print(v1, v2) 은 입력값을 확인용으로만 넣었다.
  • 루비와 제일 다르 점은 생성자 명칭도 다르지만 self이다. 관습상 self로 칭하고 python에서 생성자 선언시 제일 첫번째 매개 변수가 항상 자기자신 인스턴스를 가르킨다.

class Cal(object):
    def __init__(self, v1, v2):
        print(v1, v2)
        self.v1 = v1
        # 첫번째 매개변수를 꼭 정의해야함(뭐가 되든 상관 없고) python에서 는 @가 self이고 인스턴스가 self로 들어옴
        self.v2 = v2

    def add(self):          # 메소드 첫번째 인자가 인스턴스를 가르킴
        return self.v1+self.v2

    def subtract(self):
        return self.v1-self.v2

c1 = Cal(10, 10)
# Cal이라는 클래스를 복제한 인스턴스를 return해주는 역할 -> c1에 담아서 c1을 통해 인스턴스를 가르킬수 있음
print(c1.add())  
print(c1.subtract()) 

c2 = Cal(30, 20)
print(c2.add())
print(c2.subtract())
  • 메소드
  • Ruby처럼 다른 함수인 메소드에서 생성자에서 선언한 변수를 사용하기 위해서는 self를 사용한다. Ruby에서 @v1 = v1 처럼-> Python에서는 self.v1 = v1의 형식으로 사용하며 또 다른 함수 선언에서 첫 매개변수에 self를 적어 주어야 공유가 가능하다.


3. 객체지향의 중요성

  • ruby 객체지향이 아닌 버전으로 만들어 보기
def add(v1, v2)
    return v1+v2
end

def subtract(v1, v2)
    return v1-v2
end

p add(10, 10) # 1. 다른 함수에서 같이 사용하는 값에 대한 관련성은 모른다.
p subtract(10, 10)
p add(30, 20)
p subtract(30, 20)


num1 = 10
num2 = 10
num3 = 30
num4 = 20

p add(num1, num2) # 2. 이렇게 하면 관련성을 알수 있고 유지보수도 할수 있지만 코드가 길어지고 커짐 
p subtract(num1, num2)
p add(num3, num4)
p subtract(num3, num4)
  • 객체 지향 사용시 객체의 확인 작업과 지정 작업이 들어가서 오히려 코드가 길어지는 것 같으나 프로그램이 커질경우 복잡도를 낮추기 위해서 객체 지향이 필요하다.

  • 그리고 객체 지향시 소속확인을 통해 관련성을 알수 있다. 이를 통해서 값에 대한 처리에 대한 의미가 분명하게 드러나고 복잡한 상태의 프로그램일 경우, 안전하게 값을 저장하고 있기 때문에 영향을 덜 받는다.

  • 로직보다 data가 중요하다. 물론 하드웨어 적으로 cpu가 ssd 보다 비싸지만 어떤데이터가 들어 있는냐에 따라서 그 가치 비교는 완전 달라진다.
    이렇게 데이터는 가치있기에 변수보존이 중요하기 때문에 객체 지향은 중요하다.



4. 인스턴스 변수의 특성

  • 인스턴스 변수는 클래스내 다른 변수들 간에 연계하여 쓸수 있는 변수이다.
  • 하지만 인스턴스 변수를 쓰고 출력하는 방법이 ruby와 python에서 달라진다.

1) Ruby 인스턴스 변수 특성

class C
    def initialize(v)
        @value = v
    end
    def show()
        p @value
    end
end

c1 = C.new(10)
# p c1.value() # error
# c1.value = 20 # error
c1.show() 

  • Ruby에서는 인스턴스 변수를 밖에서 직접적으로 접근할수가 없다.
  • p c1.value() -> value라는 메소드를 찾기 때문에 difine error가 난다.
  • cl.value = 20 -> 이렇게 값을 쓰는 것도 불가능하다.
  • 그래서 show() 메서드를 따로 지정해서 메서드 호출을 통한 인스턴스 변수 값에 접근 가능하다.

2) Python 인스턴스 변수 특성

class C:
    def __init__(self, v):
        self.value = v

    def show(self):
        print(self.value)


c1 = C(10)
print(c1.value) 
c1.value = 20  # 읽어 왔으면 쓰기도 가능함
print(c1.value)
c1.show()  # 메서드 안에서 지정하여 밖에서는 메서드를 통해서 접근하는것도 가능
  • Ruby와 다르게 Python에서는 직접적으로 접근이 가능하다.
  • print(c1.value) , c1.value = 20 을 통해 인스턴스 변수를 읽고 쓰기가 가능하다.
  • 또한 따로 메서드show()를 지정하여 밖에서 메서드 호출로도 가능하다.

3) Ruby 인스턴스 변수 읽고 쓰기를 위한 get ,set 메서드


class C
    def initialize(v)
        @value = v
    end
    def show()
        p @value
    end
    def getValue()
        return @value
    end
    def setValue(v)
        @value = v
    end
end

c1 = C.new(10)
# p c1.value()
p c1.getValue()
# p c1.value = 20
c1.setValue(20)
c1.show()
  • 위처럼 지원이 불가능한 기능을 대체하기 위해, 관습적으로 인스턴스 변수의 값을 읽거나 쓰는 내용을 의미하는 이름인 getValue , setValue가 있다.

4) Python 인스턴스 변수 읽고 쓰기를 위한 get ,set 메서드

class C:
    def __init__(self, v):
        self.value = v

    def show(self):
        print(self.value)

    def getValue(self):
        return self.value

    def setValue(self, v):
        self.value = v


c1 = C(10)
print(c1.getValue())
c1.setValue(20)
print(c1.getValue())
  • Python 에서 또한 getValue , setValue로 메서드를 지정하여 사용가능하다.
  • python은 직접 간접 모두되는데 일반적으로 굳이 메서드를 지정해서 사용하는 경우가 많다. 왜 그럴까?

5) get, set을 사용하는 이유

  • python
class Cal(object):
    def __init__(self, v1, v2):  # 생성자에 유입되는 값도 미리 방지
        if isinstance(v1, int):
            self.v1 = v1
        if isinstance(v2, int):
            self.v2 = v2

    def add(self):
        return self.v1+self.v2

    def subtract(self):
        return self.v1-self.v2

    def setV1(self, v):
        if isinstance(v, int):  # isinstance : 첫번째 인자가 두번째 인자의 인스턴스라면 true를 아니면 false를 return
            self.v1 = v  # false가 되면서 실행이 되지 않고 패스 -> 방지 가능

    def getV1(self):
        return self.v1


c1 = Cal(10, 10)
print(c1.add())
print(c1.subtract())
# c1.v1 = 'one'
# c1.v2 = 30
c1.setV1 = 'one'
c1.getV1 = 30
print(c1.add())  # type 오류가 난다. 문자와 숫사 합이 안됨
print(c1.subtract())
# 입력값이 숫자이길 바람 -> 하지만 입력자는 그걸 모르고 언제든지 문자를 기입가능함 -> 중대한 문제가 생김
  • set, get을 통한 인스턴스 변수의 값을 바꾸거나 하는 경우 개발자가 원하는 입력값을 넣기를 기대하는건 옳지 않다.
  • integer 정수 숫자 값을 넣길 바라지만, 'one'과 같은 문자열을 입력받을수도 있다. 그러면 에러가 생기고 그것은 큰 문제를 초래한다.
  • 그래서 if 조건문과 isinstance()함수를 사용하여 입력 받는 문자가 잘 못되었을 때 그냥 통과하는 조건을 넣는다.
  • isinstance(v1, int) : 첫번째 인자가 두번째 인자의 인스턴스 이냐 ? -> 맞으면 True , 틀리면 False를 내 놓음-> false일 경우 실행하지 않고 pass함
  • 이를 통해 에러를 예방하고자 한다.


  • Ruby
class Cal
    def initialize(v1,v2) 
        # p v1, v2
        @v1 = v1
        @v2 = v2           
    end
    def add()      
        return @v1+@v2
    end
    def subtract()
        return @v1-@v2
    end
    def setV1(v)
        if v.is_a?(Integer) # v.is_a? : v에 담겨 있는 어떤 인스턴스가 integer 인지?
        @v1 = v
        end
    end
    def getV1()
        return @v1
    end
end

c1 = Cal.new(10,10)
p c1.add() 
p c1.subtract()
c1.setV1(20)
p c1.add() 
c1.setV1('one')
p c1.add() 
  • Ruby의 경우에도 이를 방지하고자 v.is_a?(Integer)을 사용한다.
  • v.is_a? : v에 담겨 있는 어떤 인스턴스가 integer 인지? -> true, false



  • 하지만 루비와 파이썬은 인스턴스 변수에 직접 간접 접근이 다르기 때문에 생각해 볼수 있다. 결국엔 파이썬은 인스턴스 변수에 직접 접근하는 것은 막을 수 없다.

  • python은 인스턴스 변수에 직접 접근이 가능하기 때문에 에러가 날수 있어 입력자가 올바르게 사용할것이라고 전제하고 만든것 같고 (자유도를 줌)

  • ruby는 이것 마저 통제하여 get,set을 사용해서 에러를 최소화 시키고자 하는 느낌이다. (엄격함)

  • 그런데 이렇게 set,get메서드를 계속 지정하기란 쉽지 않기에 통합 개발환경IDE에서는 자동으로 이를 만들어준다.