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()
'Python' 카테고리의 다른 글
20210128_Python 와 Ruby 비교05 - 객체와 모듈, 다중상속, mixin, 패키지 매니저 (0) | 2021.01.28 |
---|---|
20210123_Python 와 Ruby 비교03, 객체지향, class, instance (0) | 2021.01.24 |
20210122_Python 와 Ruby 비교02 - Ruby 함수, 블럭, 모듈 (0) | 2021.01.23 |
20210121_Python 와 Ruby 비교 01 - Ruby 위주로 (0) | 2021.01.21 |
20210113_ Python 입문10(총 활용), 간단한 타이핑 게임 (0) | 2021.01.13 |