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()