점프 투 파이썬 : https://wikidocs.net/book/1
파이썬 기본을 갈고 닦자 : https://wikidocs.net/16031
코딩 도장 : https://dojang.io/mod/page/view.php?id=2378
거의 모든 프로그래밍 언어에는 클래스가 있는데(GO, C 제외) 파이썬에도 클래스가 있다.
클래스는 자신과 관련된 데이터와 연산들을 모아놓은 하나의 타입이라고 보면 된다.
https://brilliant.org/wiki/classes-oop/
OOP에서의 클래스는 하나의 틀
이며 하나의 타입
이다. 이를 통해 만들어낸 것들을 객체
라고 한다. 객체
는 instance, object
라고도 불린다.
클래스는 상태(state)와 행동(behavior)
로 이루어져있는데, 상태(state) 또는 속성
는 하나의 맴버 변수 또는 attributes
이고, 행동(behavior)
은 하나의 메서드 또는 함수
를 말한다.
클래스(상태, 행동)
즉 클래스는 다음과 같이 정의할 수 있는 것이다. 그리고 클래스를 통해 만들어진 객체
들은 클래스의 상태
와 메서드
를 갖게된다.
이를 파이썬 코드로 표현하면 다음과 같다.
class Calculator:
def __init__(self):
self.result = 0
def add(self, num):
self.result += num
return self.result
cal1 = Calculator()
cal2 = Calculator()
print(cal1.add(3)) # 3
print(cal1.add(4)) # 7
print(cal2.add(23)) # 23
print(cal2.add(-4)) # 19
아직 문법에 대해 모르는 게 많으니, 위에서 언급한 것만 캐치해보자
class Calculator:
클래스의 선언 부분이다. class 이름:
을 적고 본문이 나온다.
def __init__(self):
self.result = 0
def
는 클래스의 메서드를 말한다. 즉, behavior
를 일컫는다. 여기서 __init__
은 클래스 생성자
로 클래스가 인스턴스를 만들 때 맨 첨에 호출되는 함수이다.
self.result = 0
는 클래스의 맴버변수(상태, 속성)
을 말한다. 이 맴버변수
는 클래스 안에서의 스코프(유효 범위)를 가지며 클래스 안의 메서드들은 모두 접근가능하지만, 클래스 밖에서는 접근이 불가능하다.
def add(self, num):
self.result += num
return self.result
우리가 선언한 메서드이다. __init__
은 특별한 메서드인 것과 달리 add
는 우리가 만든 메서드로 이러한 형식으로 얼마든지 만들 수 있다. 참고로 self
는 어떤 메서드 이건간에 class
안의 메서드라면 꼭 넣어주어야 한다.
cal1 = Calculator()
cal2 = Calculator()
클래스를 통해, 인스턴스(객체)를 생성하는 모습이다. 마치 함수를 호출하듯이 클래스를 호출하면 된다.
print(cal1.add(3)) # 3
print(cal1.add(4)) # 7
print(cal2.add(23)) # 23
print(cal2.add(-4)) # 19
인스턴스를 통해 메서드를 호출한 것이다. 클래스에 만들어놓은 메서드를 호출할 수 있고, 매개변수 또한 넣을 수 있다.
그럼 이제 본격적으로 파이썬의 클래스를 어떻게 사용하고 만드는 지 알아보도록 하자
위에 언급했듯이 클래스는 상태(속성), 행동(메서드)
로 구성된다. 이에 따라 클래스를 만들고 사용하는 방식은 다음과 같다.
class 클래스이름:
def 메서드1(self, 매개변수):
self.맴버변수 = 초기값
self.맴버변수 = 초기값
def 메서드2(self, 매개변수):
본문
return 반환값
객체 = 클래스이름(인자)
객체.메서드2(인자)
뭔가 복잡해보이지만 클래스의 핵심 두 가지를 생각하면 별로 어려운게 없다. 상태(속성), 행동(메서드)
만이 클래스의 전부이기 때문이다.
참고로 맴버변수
는 self.맴버변수
를 통해 등록하고 메서드에서 사용할 수 있게 된다.
그럼 예제를 통해 만들어보도록 하자
간단한 계산기를 만들다고 하자, 그러면 계산에 필요한 함수들이 있을 것이다.
위 함수들을 하나씩 만들어보자
datas = []
def setData(num1, num2):
datas.append(num1)
datas.append(num2)
def add():
return datas[0] + datas[1]
def sub():
return datas[0] - datas[1]
def mul():
return datas[0] * datas[1]
def div():
return datas[0] // datas[1]
setData(1,2)
print(add())
print(sub())
print(mul())
print(div())
함수들을 만들고보니, 5가지 함수들은 사실 하나의 계산기에 필요한 함수들이다. 이렇게 번잡하게 써놓으면 이 함수가 어디에 속하는 함수이고 어디에 쓰이는 지도 알기 쉽지 않다. 또한 관리하기도 쉽지 않다.
그래서 Caculator
라는 클래스에 이 함수들을 행동(메서드)
로 담을 것이다.
참고로 메서드와 함수의 차이는 클래스의 맴버함수로 쓰이면 메서드, 그냥 일반함수로 쓰이면 함수인 것이다. 용어상의 차이이지 실제 코드 상에서 메서드라는 키워드가 따로 있는 것은 아니다. 그렇기 때문에 굳이 엄격하게 구분하진 않아도 된다.
class Calculator:
datas = []
def setData(num1, num2):
datas.append(num1)
datas.append(num2)
def add():
return datas[0] + datas[1]
def sub():
return datas[0] - datas[1]
def mul():
return datas[0] * datas[1]
def div():
return datas[0] // datas[1]
클래스로 함수를 옮겨 Calculator
의 메서드로 만들었다. 그런데 datas
를 메서드들이 읽어오지 못하는 문제가 생긴다.
클래스 안에 쓰이는 맴버변수
는 일반적인 변수 선언으로 사용할 것이 아니라, self
라는 것에 엮어주어야 한다. 그리고 메서드들도 self
에 접근하여 맴버변수를 호출할 수 있도록 self
를 매개변수로 가져야 한다.
즉, self는 클래스가 생성할 인스턴스 자기 자신을 가리키는 것이다
class Calculator:
def setData(self, num1, num2):
self.datas = []
self.datas.append(num1)
self.datas.append(num2)
def add(self):
return self.datas[0] + self.datas[1]
def sub(self):
return self.datas[0] - self.datas[1]
def mul(self):
return self.datas[0] * self.datas[1]
def div(self):
return self.datas[0] // self.datas[1]
calc = Calculator()
calc.setData(1,2)
print(calc.add())
print(calc.sub())
print(calc.mul())
print(calc.div())
다음과 같이 메서드마다 self
를 넣어주고 self
에 맴버변수 datas
를 엮어주면 메서드에서도 맴버변수 datas
에 접근할 수 있게된다.
3
-1
2
0
이제 깔끔하게 정리해보도록 하자
class Calculator:
def setData(self, num1, num2):
self.num1 = num1
self.num2 = num2
def add(self):
return self.num1 + self.num2
def sub(self):
return self.num1 - self.num2
def mul(self):
return self.num1 * self.num2
def div(self):
return self.num1 // self.num2
datas
list로 받는 것이 아닌, 값들로 받아 처리하도록 하였다. 재밌는 것은 이제 우리는 클래스 내부의 것들을 내부에서 self
를 통해 접근할 수 있다.
add
메서드에서 sub
메서드를 호출해보도록 하자
def add(self):
sub() # NameError: name 'sub' is not defined. Did you mean: 'sum'?
return self.num1 + self.num2
다음과 같이 변경해놓고 실행하면 NameError
가 발생한다. 왜 그럴까??
sub
가 어디에 있는 지 찾지를 못해서 그런다. 즉, 클래스 외부의 sub
함수를 찾다가 없어서 에러를 뱉은 것이다. 그래서 우리는 ``self통해
sub를 찾아내면 된다.
self```는 클래스 안의 정보를 담고있기 때문이다.
def add(self):
print(self.sub())
return self.num1 + self.num2
다음과 같이 self.메서드
로 클래스 내부에서 메서드를 호출할 수 있다.
재밌는 것은 self.맴버변수
로 엮어진 맴버변수를 인스턴스
를 통해 접근이 가능하다. 즉, 인스턴스.메서드()
로 호출하듯이 인스턴스.맴버변수
로 호출이 가능하다.
print(calc.num1, calc.num2) # 1 2
이렇게 호출이 가능하다.
이러한 맴버 변수는 인스턴스 별로 따로 관리된다. 다음과 같이 인스턴스를 두 개 만들어보자
calc1 = Calculator()
calc2 = Calculator()
calc1.setData(1,2)
calc2.setData(10,20)
print(calc1.num1, calc1.num2) # 1 2
print(calc2.num1, calc2.num2) # 10 20
즉, 클래스의 요소인 상태(속성, 맴버변수)
는 각 인스턴스마다 관리되는 것이지 클래스 자체에 있는 것은 아니다. 이것은 정적 맴버변수라고해서 클래스 자체에 고유한 변수가 있도록 할 수는 있지만 `맴버 변수
는 아니다.
그런데, 이런 문제가 있다. 맴버변수를 밖에 공개하고 싶지않다면 어떻게 해야할까?? 기존 파이썬을 공부하신 분들은 _맴버변수
로 하면 된다고 알고 계시는 분들이 있다.
class Calculator:
def setData(self, num1, num2):
self._num1 = num1
self._num2 = num2
def add(self):
return self._num1 + self._num2
def sub(self):
return self._num1 - self._num2
def mul(self):
return self._num1 * self._num2
def div(self):
return self._num1 // self._num2
calc1 = Calculator()
calc2 = Calculator()
calc1.setData(1,2)
calc2.setData(10,20)
print(calc1._num1, calc1._num2) # 1 2
print(calc2._num1, calc2._num2) # 10 20
그러나 막상 외부에서도 클래스의 맴버 변수에 접근하는 것이 가능하다. 사실 _맴버변수
는 외부에 접근하는 것을 막는 것이 아닌, 그냥 컨벤션(관습)으로 맴버변수 앞에 _
를 붙이면 우린 그걸 내부에서만 사용하는 변수로 알자! 라고 했을 뿐이다. 때문에 이것이 정말 다른 언어의 private
한 효과를 주진 못한다. 즉, 맴버변수를 외부에서 접근하지 못하도록 할 수가 없다.
그래서 __맴버변수
로 바꾸면 외부에서 접근을 못하도록 하는 기능이 있다.
class Calculator:
def setData(self, num1, num2):
self.__num1 = num1
self.__num2 = num2
def add(self):
return self.__num1 + self.__num2
def sub(self):
return self.__num1 - self.__num2
def mul(self):
return self.__num1 * self.__num2
def div(self):
return self.__num1 // self.__num2
calc1 = Calculator()
calc2 = Calculator()
calc1.setData(1,2)
calc2.setData(10,20)
print(calc1.add()) # 3
print(calc2.add()) # 30
print(calc1.__num1, calc1.__num2) # 'Calculator' object has no attribute '__num1'
print(calc2.__num1, calc2.__num2) # 'Calculator' object has no attribute '__num2'
__맴버변수
로 선언하여 외부에서 클래스의 맴버변수에 접근하지 못하도록 할 수 있다.
본문이 없는 빈 클래스를 만들 수 있다.
class Test:
pass
isinstance
를 사용하면 객체의 자료형을 판단할 수 있다. True, False로 결과가 반환된다.
isinstance(객체, 클래스)
위의 Calculator
을 예시로 들어보면
print(isinstance(calc1, Calculator)) # True
print(isinstance(calc1, int)) # False
다음과 같다.
__slots__
)파이썬 클래스의 문제점 중 하나는 인스턴스에서 자유롭게 맴버변수를 추가할 수 있다는 것이다.
calc1 = Calculator()
calc2 = Calculator()
calc1.random = 23
print(calc1.random) # 23
print(calc2.random) # AttributeError: 'Calculator' object has no attribute 'random'
이 처럼 클래스 내부가 아닌, 인스턴스에서도 속성을 추가할 수 있다. 이렇게되면 문제는 해당 속성은 인스턴스에 귀속되므로 클래스에는 영향을 미치지 않는다. 때문에 다른 인스턴스는 해당 속성을 가질 수 없다.
위에서도 calc1.random = 23
으로 정의하였는데 calc2.random
은 없다고 나온다. 이유는 calc1.random
은 calc1
인스턴스에만 속성이 추가되었기 때문이다.
그래서 특별한 지시자가 있는데 그것이 바로 __slots__
이다. 리스트 형식으로 문자열을 넣어주면 해당 문자열들만 클래스가 아닌 인스턴스에서 속성을 추가할 수 있도록 한다.
class Calculator:
__slots__ = ['random', 'age']
def setData(self, num1, num2):
self.__num1 = num1
self.__num2 = num2
def add(self):
return self.__num1 + self.__num2
def sub(self):
return self.__num1 - self.__num2
def mul(self):
return self.__num1 * self.__num2
def div(self):
return self.__num1 // self.__num2
calc1 = Calculator()
calc1.random = 23
calc1.age = 20
calc1.name ="hello" # AttributeError: 'Calculator' object has no attribute 'name'
그럼 맨 처음에 봤던 __init__
메서드는 무엇일까? 이것은 특별한 메서드로 생성자(constructor)
이라고 한다. 이들은 클래스를 통해 객체를 생성할 때 단 한 번 호출되는 메서드이다.
자동으로 호출되기 때문에 따로 호출해주지 않아도 된다.
class Calculator:
def __init__(self):
print("instance start hello!")
def setData(self, num1, num2):
self.num1 = num1
self.num2 = num2
def add(self):
print(self.sub())
return self.num1 + self.num2
def sub(self):
return self.num1 - self.num2
def mul(self):
return self.num1 * self.num2
def div(self):
return self.num1 // self.num2
calc1 = Calculator() # instance start hello!
calc2 = Calculator() # instance start hello!
calc1.setData(1,2)
calc2.setData(10,20)
print(calc1.num1, calc1.num2) # 1 2
print(calc2.num1, calc2.num2) # 10 20
class에 __init__
하나만 추가했을 뿐인데 객체 생성 시, 자동으로 instance start hello!
로그를 찍은 것을 확인할 수 있다.
__init__
역시도 self
을 써주어 class의 맴버함수임을 알려주어야 한다. 그래야 에러가 발생하지 않는다.
또한, __init__
생성자도 매개변수를 받을 수 있다. 매개변수를 받고, 맴버변수
의 초기값을 설정해줄 수 있다.
class Calculator:
def __init__(self, num1, num2):
self.num1 = num1
self.num2 = num2
print("instance start hello!")
def setData(self, num1, num2):
self.num1 = num1
self.num2 = num2
def add(self):
print(self.sub())
return self.num1 + self.num2
def sub(self):
return self.num1 - self.num2
def mul(self):
return self.num1 * self.num2
def div(self):
return self.num1 // self.num2
calc1 = Calculator(1,2) # instance start hello!
calc2 = Calculator(10,20) # instance start hello!
print(calc1.num1, calc1.num2) # 1 2
print(calc2.num1, calc2.num2) # 10 20
setData
메서드를 사용하지 않고도 처음에 생성자를 통해 맴버 변수
를 초기화시킬 수 있게 되었다.
사실 __init__
은 이름만 생정자 메서드
라고 불리지 일반 함수의 특성은 모두 가진다. 따라서 *args나 **kwargs
도 모두 가능하다.
메서드 오버로딩(overroading)
은 메서드 이름이 같지만 파라미터 수와 타입이 다르면 다른 메서드처럼 다룰 수 있는 기능을 말한다.
가령 java의 경우는 다음의 경우를 허용한다.
class Calculator{
int add(int a, int b){
return a+ b;
}
int add(float a, float b){
return a+ b;
}
int add(int a, int b, int c){
return a + b + c;
}
}
다음과 같이 메서드 이름이 add
로 같지만 파라미터 수는 2개와 3개로 다르기도 하고 타입도 다르다. 따라서 이 3개의 메서드는 다른 메서드로 취급받게 된다. 이를 메서드 오버로딩
이라고 한다.
그러나, 파이썬에서는 이것이 불가능하다.
class Calculator:
def add(self, num1 , num2):
return num1 + num2
def add(self, num1, num2 , num3):
return num1 + num2 + num3
calc = Calculator()
print(calc.add(1,2,3)) # 6
print(calc.add(1,2)) # TypeError: Calculator.add() missing 1 required positional argument: 'num3'
인자 2개 메서드 add
가 인자 3개 메서드 add
에 덮어씌여지고 사라진다. 이는 오버로딩
이 안된다는 것을 알려준다.