본문 바로가기

Python

20210127_Python 와 Ruby 비교04 - 인스턴스 변수 접근, 클래스 변수(클래스 멤버), 오버라이딩

Python vs Ruby (루비 위주)

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

Ruby와 Python의 인스턴스 변수의 접근

  • 지난시간에 말한것과 같이 Ruby와 Python의 경우에는 인스턴스 변수에 대한 접근 특성은 다르다.
  • Ruby의 경우에는 인스턴스 변수에 직접 접근이 불가능하고 간접적으로 접근이 가능하다.
  • 이에 반해, Python의 경우에는 인스턴스 변수에 접근이 직, 간접 모두 가능하다.

  • 그런데 이렇게 인스턴스 변수에 직, 간접 접근에는 장 단점이 존재한다.
  • Python의 경우에는 직간접으로 접근을 허용함으로써 자유도가 좋고 편리하지만, 그 만큼 데이터의 오류를 초래할 수 있다.
  • 또한 Ruby의 경우에는 데이터의 오류를 잘 정제 할수 있지만, 그만큼 불편하게 된다.
  • 그래서 이를 보완하고자 각 언어는 대안방법을 가지고 있다.

  • Python에서는 직접 접근을 막고 , Ruby에서는 직접 접근을 가능하게 하는 방법

Ruby와 Python의 인스턴스 변수의 접근 대안

  • Ruby의 경우

  • 외부에서 직접 접근시키기 위해서 속성(attribute)를 사용한다.

  • attr_reader :인스턴스 변수이름 : 인스턴스 변수를 읽기 가능한 속성으로 지정(읽기 모드) -> 인스턴스 변수의 값을 불러오는 경우 p c1.value()

  • attr_writer :인스턴스 변수이름 : 인스턴스 변수를 쓰기 가능한 속성으로 지정(쓰기 모드) -> 인스턴스 변수의 값을 쓰는 경우 c1.value = 20

  • attr_accessor :인스턴스 변수이름 : 인스턴스 변수를 읽고,쓰기 가능한 속성으로 지정 (읽기, 쓰기모드)

  • 위처럼 읽기, 쓰기 방식을 나누어 둔 까닭은 데이터 접근에 대해서 엄격히 하여 에러발생을 줄이기 위해서 이다.

class C
    # attr_reader :value 
    # attr_writer :value 
    attr_accessor :value 
    def initialize(v)
        @value = v
    end
    def show()
        p @value
    end
end

c1 = C.new(10)
p c1.value() # 외부에서 접근하는 인스턴스 변수인 value는 속성, attribute 이다.
c1.value = 20 # 속성읽기 가능만으로는 실행이 불가함(attr_writer)
p c1.value()

  • Python의 경우

  • Python의 경우에는 직, 간접 모두 접근이 가능하기에 오히려 직접 접근을 제한할 필요가 있다.

  • 제한을 위해서 인스턴스 변수 이름에 __를 붙인다.

  • 이렇게 하면 외부에서 직접 접근시 에러가 난다. 단, 간접 적으로 메소드로 접근은 가능하다.

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

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


c1 = C(10)
# print(c1.__value) # error
c1.show()


Inheritance (상속)

  • 상속이 없으면 코드 중복이 심해짐 -> 중복 제거 -> 가독성, 유지보수 등이 좋음 그래서 상속이 필요함
  • 상속은 객체의 기능을 그대로 가져와서 기능을 추가하는 느낌이다.

1. 상속 코드 구조

  • Python
  • Python에서는 Class를 선언할때 괄호 안에 부모 요소를 넣으면 된다.
  • 상속 기능 실행 구조
    • ex) c2라는 인스턴스의 클래스를 찾는다. -> 그 클래스 안에서 method1이 있는지 확인해보고 없으면 -> 부모 클래스에 method1이 있는지 확인하고 return
    • 즉, 먼저 자신의 인스턴스 클래스에서 해당 메소드를 찾아 확인하고 없으면 부모 클래스에가서 해당 메소드를 찾는 방식

class Class1(object):
    def method1(self):
        return 'm1'

c1 = Class1()
print(c1.method1())
print()

class Class2(Class1):  # Class3는 Class1을 상속하여 기능을 사용한다.
    def method2(self): return 'm2'

c2 = Class2()
print(c2.method1())
print(c2.method2())
print()

  • Ruby
  • Ruby의 경우에는 <라는 기호를 사용하여 부모 클래스를 옆에 써준다. (큰쪽이 부모)
class Class1
    def method1()
        return 'm1'
    end
end

c1 = Class1.new()
p c1, c1.method1()

class Class3 < Class1 # < 기호를 통해서 Class3는 Class1을 상속 받는다. 큰쪽이 부모
    def method2()
        return 'm2'
    end
end

c3 = Class3.new()
p c3, c3.method1()
p c3, c3.method2()

# 중복을 제거하고 부모 클래스의 재사용을 높인다.

2. 계산기 객체를 이용한 상속 활용

  • Python
"메인 기능 +, -"
class Cal(object):
    def __init__(self, v1, v2):
        self.v1 = v1
        self.v2 = v2

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

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

"추가할 기능 *, /"
class CalMultiply(Cal):
    def multiply(self):
        return self.v1*self.v2


class CalDivide(CalMultiply):
    def divide(self):
        return self.v1/self.v2

"실행 해보기"
c1 = CalMultiply(10, 10)
print(c1.multiply())
print(c1.add())
print(c1.subtract())

c2 = CalDivide(20, 10)
print(c2.multiply())
print(c2.add())
print(c2.subtract())
print(c2.divide())

  • 실제로 여러가지 프로그램이 만들어 지다 보면 상속에서 같은 기능들이 발생할 수도 있으며 그런 것들이 충돌하며 모순이 생기도 함
  • 그래서 어떤 코딩이 좋은 것인지는 발전하면서 생각해 나가야 함 (너무 많은 지식에 노출되지 않도록 길을 잘 찾아 가야함-> 안그러면 코딩을 못함)

  • Ruby
"메인 기능 +,-"
class Cal
    def initialize(v1,v2) 
        @v1 = v1
        @v2 = v2          
    end
    def add()      
        return @v1+@v2
    end
    def subtract()
        return @v1-@v2
    end
end

"추가할 기능 *,/"
class CalMultiply < Cal
    def multiply()
        return @v1*@v2
    end
end

class CalDivide < CalMultiply
    def divide()
        return@v1/@v2
    end
end

"실행해 보기"
c1 = CalMultiply.new(10, 10)
p c1.add()
p c1.multiply()
p c1.subtract()

c2 = CalDivide.new(20, 10)
p c2.add()
p c2.multiply()
p c2.subtract()
p c2.divide()


Class member (클래스 멤버)

  • 클래스에서 사용하는 함수를 메소드라고 했었다. 이 메소드 중에서도 인스턴스 변수를 사용하는 메서드도 있고 인스턴스 변수가 필요없는 메서드도 있다.

  • 이러한 차이점은 각 상황, 필요에 따라 다르기에 분류해 놓은 것이다.

  • 예시) Ruby

    • 아래 코드는 date라는 모듈을 들고와서 Date 클래스를 사용하여 d1, d2라는 인스턴스에 값을 할당 시켰다.
    • 그러면 출력시 p d1.year()d1이라는 인스턴스에 값을 필요로 하는 year이라는 클래스 내 함수를 사용한 것이다. 그래서 이것을 인스턴스에 소속 되어 있다고 이야기 하고 인스턴스 멤버라고도 이야기 한다.
    • 그에 반해 p Date.today()의 경우에는 인스턴스와 그에 대한 값이 필요 없는 함수인 today라는 클래스 내 함수를 사용했다.
    • 그래서 Date라는 클래스 이름과 함께 붙어서 사용하여 클래스 소속(클래스 멤버)이라고 한다.
    • 핵심, 인스턴스 멤버, 클래스 멤버가 있으며 호출시 인스턴스 멤버는 인스턴스와 , 클래스 멤버는 클래스와 함께 쓰인다는 것!
require 'date'
d1 = Date.new(2000, 1, 1) 
d2 = Date.new(2010, 1, 1) 

p d1.year() # d1이라는 인스턴스의 내부값 중 연도의 값을 return
p d2.year()
#  각각의 year라는 메소드는 각 인스턴스에 소속되어 있다.

p Date.today() 
  • 위는 존재하는 함수를 사용하여 분석한 것이고, 이제 우리가 직접 클래스 멤버와 인스턴스 멤버를 정의 할경우를 알아보자

1. 클래스 멤버 정의, 코드 구조

  • Python

  • Python의 경우에는 인스턴스 멤버, 클래스 멤버 중에서 클래스 멤버가 두개로 나뉜다.

  • static의 경우 함수 이름은 아무렇게 해도 되지만 이를 표시하기 위해서 코드 위에 @staticmethod라는 장식자를 넣어준다.

  • class 의 경우도 함수 이름은 아무렇게 해도 되지만 이를 표시하기 위해서 코드 위에 @classmethod라는 장식자를 넣어준다.

    • 그리고 class는 추가적으로 인스턴스 멤버의 self처럼 cls라는 매개변수를 첫번째에 넣어 주어야 한다.
  • 클래스 멤버 두개 모두 호출시에는 클래스 이름을 붙여서 호출 하여야 한다.

class Cs:
    @staticmethod  # 위에 장식자 넣어주기 (규칙이므로 꼭 저이름대로 넣어줘야 함)
    def static_method():
        print("Static method")

    @classmethod  # 위에 장식자 넣어주기 (규칙이므로 꼭 저이름대로 넣어줘야 함)
    def class_method(cls):  # self 처럼 클래스 메소드에서는 cls로 첫번째 인자를 줌
        print("Class method")

    def instance_method(self):
        print("instance_method")


i = Cs()
Cs.static_method()  # 클래스 소속이므로 Cs.
Cs.class_method()  # 클래스 소속이므로 Cs.
i.instance_method()  # 인스턴스 소속이므로 i.

  • Ruby
  • Ruby의 경우에는 클래스 멤버는 한가지 이고 클래스 멤버임을 표기 하기 위해서 정의하는 함수 이름에 클래스 이름.을 붙여서 정의한다.
  • 호출시에도 python과 마찬가지로 클래스.클래스 멤버 , 인스턴스.인스턴스 멤버 로 호출
class Cs
    def Cs.class_method() # Cs.를 붙여서 Cs라는 클래스의 멤버라는 것을 분명히 해야함
        p 'Class method'
    end
    def instnace_method()
        p 'Instnace_method'
    end
end

i = Cs.new()
Cs.class_method() # class_method()는 Cs라는클래스 소속인 클래스 멤버라는 것임 
i.instnace_method() # instance_method는 i 라는 인스턴스의 소속인 인스턴스 멤버라는 것임
# Cs.instnace_method() # error
# i.class_method() # error
# 클래스와 클래스 메소드의 합으로
# 인스턴스와 인스턴스 메소드의 합으로 구성 해야 한다.

2. 클래스 안의 클래스 메서드와 변수간에 관계 (클래스 변수)

  • Python

  • 클래스 변수의 경우에는 인스턴스 변수와 달리 클래스 메서드, 인스턴스 메서드 모두에서 공유하면서 사용이 가능하다.

  • 클래스 변수 정의 방법은 클래스 정의 구역과 함수 정의 구역 사이에 정의 하면 된다.

  • 클래스 변수를 각 함수에서 사용할때는 클래스.클래스 변수 이름으로 사용한다. 정의 할때는 그냥 클래스 변수 이름으로 정의하고

  • 예시) getCount 클래스 메서드 만들기 -> 인스턴스 개수를 세어주는 기능

class Cs:
    count = 0  # 메소드 밖 클래스 안에 정의하면 클래스의 소속인 변수로 인식

    def __init__(self):
        Cs.count += 1  # 메소드 안에서 접근할 때는 Cs.를 붙여야 한다.

    @classmethod
    def getCount(cls):  # cls는 메소드가 소속된 클래스를 나타냄 -> 여기에서는 Cs
        return Cs.count


i1 = Cs()
i2 = Cs()
i3 = Cs()
i4 = Cs()

print(Cs.getCount()) 

  • Ruby
  • Ruby에서 클래스 변수 사용은 @@클래스 변수 이름으로 사용한다. 정의 구역은 Python과 같다.
  • 다만, Python과 다르게 각 메서드 안에서도 똑같이 @@클래스 변수 이름로 사용된다.
class Cs
    @@count = 0 # 기준이 될수 있는 최초값 설정
    def initialize()
        # @count # 인스턴스 변수 사용
        @@count += 1 # 클래스 변수 사용시 (initaialize를 실행시킬때 마다 @@count 변수를 1씩 증가 시킴)
    end
    def Cs.getCount() # 클래스 변수를
        return @@count
    end
end

i1 = Cs.new()
i2 = Cs.new()
i3 = Cs.new()
i4 = Cs.new()

p Cs.getCount() # getCount 메서드가 인스턴스가 몇개 생성 되었는지 알려줌

3. 계산기 예제에서 클래스 변수 활용 (히스토리 보기 기능 추가)

  • Ruby
  • history라는 클래스 메서드를 만들 예정이고
  • 기능은 각 인스턴스 생성하여 계산하면 계산한 것을 모두 배열에 저장하여 호출시 계산과 결과를 모두 보여줌
  • 일단, 최고 부모인 Cal클래스에 Cal.history클래스 메서드르 만들어줌, 최상단에 @@_history = []으로 배열을 만드는데 _를 왜 넣었는지는 모르겠다. 파이썬으로 햇갈려서 그런 걸수도 있겠다.
  • 집중해서 보아야 할 것은 Cal.history 클래스 메서드 구조, 그리고 루비에서의 포맷팅 방법 , push함수
class Cal
    @@_history = []  # 배열을 만듦
    def initialize(v1,v2) 
        @v1 = v1
        @v2 = v2          
    end
    def add()
        result = @v1+@v2 
        @@_history.push("add : #{@v1}+#{@v2}=#{result}") # push는 배열에 끝 부분에 값을 추가해줌 
        # 루비에서 타입 변형을 하려면 result.to_s()를 해줘야 하는데 더 쉽게
        # 루비에서의 포맷팅 방법  #{변수} 로 가능   
        return result
    end
    def subtract()
        result = @v1-@v2 
        @@_history.push("subtract : #{@v1}-#{@v2}=#{result}")
        return result
    end
    def Cal.history() # 히스토리 클래스 
        for item in @@_history # 반복해서 뽑아서 보여줌
            p item
        end
    end
end

class CalMultiply < Cal
    def multiply()
        result = @v1*@v2 
        @@_history.push("multiply : #{@v1}*#{@v2}=#{result}")
        return result
    end
end

class CalDivide < CalMultiply
    def divide()
        result = @v1/@v2 
        @@_history.push("divide : #{@v1}/#{@v2}=#{result}")
        return result
    end
end

c1 = CalMultiply.new(10, 10)
p c1.add()
p c1.multiply()
p c1.subtract()

c2 = CalDivide.new(20, 10)
p c2.add()
p c2.multiply()
p c2.subtract()
p c2.divide()

p Cal.history()

  • Python
class Cal(object):
    _history = []

    def __init__(self, v1, v2):
        self.v1 = v1
        self.v2 = v2

    def add(self):
        result = self.v1+self.v2
        Cal._history.append(
            "add : {0}+{1}={2}".format(self.v1, self.v2, result))
        return result

    def subtract(self):
        result = self.v1-self.v2
        Cal._history.append("subtract : %d-%d=%d" % (self.v1, self.v2, result))
        return result

    @classmethod
    def history(cls):
        for item in Cal._history:  # 외부에서 사용안하고 내부에서만 사용하므로 _를 붙임
            print(item)


class CalMultiply(Cal):
    def multiply(self):
        result = self.v1*self.v2
        Cal._history.append("multiply : %d*%d=%d" % (self.v1, self.v2, result))
        return result


class CalDivide(CalMultiply):
    def divide(self):
        result = self.v1/self.v2
        Cal._history.append("divide : %d/%d=%d" % (self.v1, self.v2, result))
        return result


c1 = CalMultiply(10, 10)
print(c1.multiply())
print(c1.add())
print(c1.subtract())

c2 = CalDivide(20, 10)
print(c2.multiply())
print(c2.add())
print(c2.subtract())
print(c2.divide())

Cal.history()


오버 라이딩(overriding)

  • 오버 라이딩은 부모 객체와 자식객체의 기능이름이 같은 경우, 자식 객체의 기능이 우선되어 지는 것을 말함
  • 즉, 부모 기능에 올라 탔다. -> 오버 라이드
  • Python에서 의미하는 super() 는 동일한 이름의 함수가 존재시 부모 클래스를 의미함 (한단계 위 부모 O, 최상위 부모X)
  • 클래스를 의미하기 때문에 super().함수 처럼 함수이름을 명명하여 사용함

1. 오버라이딩 코드 구조

  • Python
class C1:
    def m(self):
        return 'parent'


class C2(C1):
    # pass  # 메서드가 존재하지 않는 클래스로 만들기 가능
    def m(self):  # 부모와 같이 만들어서 재정의 -> 오버라이딩
        # super().m()  # 원래의 부모의 메서드를 사용하고 싶을때
        return super().m() + ' child'


o = C2()
print(o.m())

  • Ruby

  • Ruby에서 super()의 의미는 현재 소속되어 있는 함수와 같은 이름을 가진 부모 메소드를 가르킴 (한단계 위 부모 O, 최상위 부모X)

  • 이처럼 Python과 다르게 Ruby에서는 함수를 가르키기 때문에 특정 함수의 이름을 붙여서 사용하지 않는다.

class C1
    def m()
        return 'parent'
    end
end

class C2 < C1
    def m()
        return super()+ ' child' 
    end
end

o = C2.new()
p o.m() 
# 파이썬에 super는 부모클래스를 의미하고 
# 루비에 super는 현재 소속되어 있는 함수와 같은 이름을 가진 부모 메소드를 가르킴

2. 계산기 예제에서 오버라이딩 활용

  • Python
  • info 라는 인스턴스 메서드를 통해서 각 인스턴스에 할당된 클래스에 따라서 어떻게 호출 되는지
  • 즉, c0, c1, c2에 각 클래스 마다 다르게 info를 구성하여 할당하고 어떻게 호출 되는지 확인
class Cal(object):
    _history = []

    def __init__(self, v1, v2):
        self.v1 = v1
        self.v2 = v2

    def add(self):
        result = self.v1+self.v2
        Cal._history.append(
            "add : {0}+{1}={2}".format(self.v1, self.v2, result))
        return result

    def subtract(self):
        result = self.v1-self.v2
        Cal._history.append("subtract : %d-%d=%d" % (self.v1, self.v2, result))
        return result

    @classmethod
    def history(cls):
        for item in Cal._history:
            print(item)

    def info(self):
        return "Cal => v1 : %d, v2 : %d" % (self.v1, self.v2)


class CalMultiply(Cal):
    def multiply(self):
        result = self.v1*self.v2
        Cal._history.append("multiply : %d*%d=%d" % (self.v1, self.v2, result))
        return result

    def info(self):
        return "CalMultiply => %s" % (super().info())


class CalDivide(CalMultiply):
    def divide(self):
        result = self.v1/self.v2
        Cal._history.append("divide : %d/%d=%d" % (self.v1, self.v2, result))
        return result

    def info(self):
        return "CalDivdie => %s" % (super().info())


c0 = Cal(30, 60)
print(c0.info())
c1 = CalMultiply(10, 10)
print(c1.info())
c2 = CalDivide(20, 10)
print(c2.info())

  • Ruby
class Cal
    @@_history = []  
    def initialize(v1,v2) 
        @v1 = v1
        @v2 = v2          
    end
    def add()
        result = @v1+@v2 
        @@_history.push("add : #{@v1}+#{@v2}=#{result}") 
        return result
    end
    def subtract()
        result = @v1-@v2 
        @@_history.push("subtract : #{@v1}-#{@v2}=#{result}")
        return result
    end
    def Cal.history() 
        for item in @@_history 
            p item
        end
    end
    def info() #-----------------정의----------------------
        return "Cal : v1 = #{@v1} , v2 = #{@v2}"
    end
end

class CalMultiply < Cal
    def multiply()
        result = @v1*@v2 
        @@_history.push("multiply : #{@v1}*#{@v2}=#{result}")
        return result
    end
    def info() #------------------정의-------------------
        return "CalMultiply => #{super()}"
    end
end

class CalDivide < CalMultiply
    def divide()
        result = @v1/@v2 
        @@_history.push("divide : #{@v1}/#{@v2}=#{result}")
        return result
    end
    def info() #-----------------정의--------------------
        return "CalDivide => #{super()}"
    end
end

"출력 확인"
c0 = Cal.new(30, 60)
p c0.info()
c1 = CalMultiply.new(10, 10)
p c1.info()
c2 = CalDivide.new(20, 10)
p c2.info()