먹고 기도하고 코딩하라

[인프런 리프 2기] 2주 - 파이썬 클래스 심화 본문

일상

[인프런 리프 2기] 2주 - 파이썬 클래스 심화

2G Dev 2021. 3. 20. 18:29
728x90
728x90

이전 글 보기

 

[인프런 리프 2기] 1주 - 환경 설정

오늘은 인프런 리프 1주차 미션인 섹션 0과 1을 정리해 보겠습니다. 제가 수강하는 강의는 [우리를 위한 프로그래밍], 파이썬 중급 과정 인프런 오리지널 강의입니다. OT부터 가볍게 들어봤습니다

dev-dain.tistory.com

 

 

오늘은 인프런 리프 2주차 미션인 섹션 2를 정리해 보겠습니다.

 

 

제가 수강하는 강의는 [우리를 위한 프로그래밍], 파이썬 중급 과정 인프런 오리지널 강의입니다.

 

 

섹션 2 : 파이썬 클래스 심화

초보자를 위한 파이썬 강의들이 어느 범위까지 다루는지는 책이나 강의마다 다르고, 그 범위도 약간 모호합니다. 그래도 시중에 나온 입문자용 교재나 강의 목차를 훑어보면 클래스를 아예 안 배우지는 않는 것 같던데요. 실제로 저도 학교 파이썬 강의에서도 클래스는 배웠습니다. 다만 OOP(Object-Oriented Programming) 측면에서 깊게 배우지는 않고 그냥 자바에서 배웠던 것과 같이 이런 클래스 개념이 있다~ 정도로만 짚고 넘어가는 수준이었습니다.

 

중급 강의답게 입문자용 클래스 학습이 아니라 좀 더 세밀한 학습을 할 수 있었는데요.

섹션 2에서는

(1) 객체지향 프로그래밍의 장점과 같은 유형의 여러 데이터들이 있을 때 클래스로 인스턴스로 만들어 관리하는 장점
(2) 클래스 내에서 쓸 수 있는 빌트인 메소드(생성자, 출력 등)
(3) 클래스 내에서 쓸 수 있는 변수 종류
(4) 클래스 내에서 쓸 수 있는 메소드의 종류
(5) 클래스와 인스턴스의 속성, 메소드, 변수 확인하기

등을 집중적으로 다뤘습니다.

하나씩 짧게짧게 살펴 보겠습니다.

약간 티저 맛보기 느낌으로 갈 거라서 강의 내용 전부를 담지는 않습니다.

 

 

2-1. 절차지향 프로그래밍과 객체지향 프로그래밍

저는 1학년 때 C를 배우고 2학년 때 자바를 배웠는데요. C는 전형적인 절차지향 언어이고 자바는 전형적인 객체지향 언어입니다. 예시로, C에서는 클래스 개념이 따로 주어지지 않고 primitive 타입만 쓸 수 있으며, 데이터를 묶으려면 struct 예약어로 구조체를 선언해야 했죠. 프로그램도 데이터 중심이 아닌 일의 순서가 중심이 되도록 짰고요. 하지만 자바에서는 클래스 개념이 있고 객체 위주로 프로그래밍을 하게 됩니다. 어떤 것이 좋다 나쁘다 할 건 아닌 것 같고, 상황에 따라 절차지향 언어를 쓸 수도 있고 객체지향 언어를 쓸 수도 있다고 생각합니다.

객체 지향 프로그래밍 패러다임의 장점은 (1) 코드 재사용이 쉽고, (2) 유지보수가 쉽다는 것입니다.

 

 

2-2. 빌트인 메소드

보통 파이썬 클래스는 다음과 같이 생겼습니다.

class Student:
  def __init__(self, name, major, year):
    self._name = name
    self._major = major
    self._year = year
    
  def sayHi(self):
    print('%s, Hi!' % self._name)

생성자 함수만 하나 있는 간소한 클래스를 만들어 봤는데요.

일단 생성자 함수의 첫 번째 인자가 self인 건 클래스 인스턴스 자신을 가리킨다는 건 파이썬 클래스를 공부하신 분들은 다들 아실 것 같고요. 그 뒤의 매개변수도 아실 것 같습니다.

저는 처음에 self._name = name 처럼 왜 변수 앞에 언더스코어(_)가 붙는지 궁금해했는데, 파이썬 컨벤션으로 비공개 메소드나 인스턴스 변수에는 언더스코어를 1개 붙인다네요. 이게 클래스 외부에서 접근하지 말라는 암묵적인 표시가 된다고 합니다. 자바의 private 접근자처럼 강제로 막을 수 있는 수단이 파이썬에 없기 때문에 이런 컨벤션을 만들어 지킨답니다.

 

클래스를 만들었으니 인스턴스를 하나 만들어 출력해 볼까요?

s = Student('Alice', 'Computer Science', 3)
print(s)

# 결과 : <__main__.Student object at 0x0000015713B23400>

print에 넣어서 출력해 보면 인스턴스의 객체 타입과 함께 메모리 어디에 저장되어 있는지 주소가 나옵니다. 뭘 의도했든 간에 그것과는 맞지 않는 것 같죠?

이럴 때 쓰는 빌트인 메소드가 __str__ 입니다. 자바를 해 보신 분이라면 toString 메소드와 비슷한 역할이라고 설명하면 좀 와닿으실 듯해요.

 

# ...Student
  def __str__(self):
    return "{}'s major: {}, year: {}".format(self._name, self._major, self._year)

# 결과 : Alice's major: Computer Science, year: 3

주의하실 점은 출력할 내용을 적어주되, __self__에서 return으로 넘겨줘야 합니다. 쉽게 말해 다음과 같이 쓰시면 안 된다는 겁니다. 

 

  def __str__(self):
    print("{}'s major: {}, year: {}".format(self._name, self._major, self._year))

# 결과 : TypeError: __str__ returned non-string (type NoneType)

__str__은 string을 반환하도록 되어 있습니다. __str__ 메소드는 클래스 인스턴스를 print()에 넣었을 때 호출되는 것으로 약속이 되어 있습니다.

벌써 하나 배웠네요. 클래스를 내가 원하는 식으로 출력하고 싶다면 무슨 메소드를 선언하고 사용하면 된다? __str__ 메소드입니다. 클래스 내장 메소드로, 다른 이름도 아니고 꼭 __str__로 하셔야 합니다.  

 

__str__ 비슷한 빌트인 메소드로 __repr__도 있는데, 언뜻 보면 비슷~합니다. 차이점은 강의에서 확인하시는 걸루... ㅋㅋ 사실 검색해도 나오긴 합니다. 미묘~한 차이가 있고 강의에서 설명도 했습니다만 솔직히 확 와닿지는 않네요. ㅎㅎ

[Python] __str__와 __repr__의 차이 살펴보기

 

 

2-3. 클래스 내에서 쓸 수 있는 변수 종류

클래스 내에서 쓸 수 있는 변수 종류로는 인스턴스 변수클래스 변수가 있습니다.

인스턴스 변수는 위의 예제에서 본 것과 같이 self._name 처럼 self로 접근해서 쓸 수 있는 변수, 즉 클래스로는 접근이 안 되고 개별 인스턴스로만 접근할 수 있는, 인스턴스 자체적으로 갖는 변수를 말하는 겁니다.

반면 클래스 변수는 해당 클래스의 모든 인스턴스가 똑같이 공유하는 변수입니다. 변수 이름에 언더스코어를 붙이지 않는 건 클래스 변수라는 암묵적인 표시입니다.  

예제로 확인해 보겠습니다.

class Student:
  student_num = 0

  def __init__(self, name, major, year):
    self._name = name
    self._major = major
    self._year = year
    Student.student_num += 1

  def __str__(self):
    print("{}'s major: {}, year: {}".format(self._name, self._major, self._year))



s1 = Student('Alice', 'Computer Science', 3)
s2 = Student('Bob', 'English Literature', 4)
s3 = Student('Carol', 'Economics', 1)

print(s1.student_num)	# 3
print(s2.student_num)	# 3
print(Student.student_num)	# 3

student_num 이라는 변수를 하나 추가했습니다. student_num 앞에는 언더스코어가 붙지 않았으므로 클래스 변수라는 의미를 갖습니다.

생성자 함수는 인스턴스가 생성될 때 딱 한 번 호출되는 거 아시죠? 생성될 때 클래스의 student_num이 1 증가하도록 했습니다. Student.student_num과 같이 클래스 이름으로 접근한 것에 유의하세요.

Student 클래스의 객체를 3개 만들고 인스턴스와 클래스 각각으로 student_num에 접근하면 똑같은 결과가 나오는 것을 확인할 수 있습니다. 당연한 말이지만 Student._year 이런 식으로 클래스 자체에 선언되지 않은 변수에 접근하면 AttributeError 납니다. 하지 마세요. ^^

 

 

2-4. 클래스 내에서 쓸 수 있는 메소드의 종류

클래스 내에서 쓸 수 있는 메소드로는 인스턴스 메소드, 클래스 메소드, 정적(static) 메소드가 있습니다.

 

(1) 인스턴스 메소드

제일 일반적이고 가장 많이 쓰는 메소드입니다. 메소드의 매개변수 첫 번째 자리에 (self)가 있는 메소드를 의미합니다. 매개변수 이름이 self여야 한다는 법은 없지만 이것도 관례와 비슷한 것이라 특별한 이유가 없다면 self를 쓰는 게 인스턴스 그 자신을 나타내는 의미를 그대로 나타내는 거라서 좋습니다.

 

  def sayHi(self):
    print('%s, Hi!' % self._name)

호출할 때는 다음과 같이 호출할 수 있습니다. 둘 다 결과는 같습니다.

 

Student.sayHi(s1)
s1.sayHi()

# 아래는 안 됨
Student.sayHi()
# 결과 : TypeError: detail_info() missing 1 required positional argument: 'self'

 

(2) 클래스 메소드

클래스 메소드는 클래스와 인스턴스가 모두 접근할 수 있는 메소드입니다. 데코레이터를 붙여서 이것이 클래스 메소드임을 나타내 줍니다.

 

@classmethod
def transfer_student(cls, num):
  cls.student_num = num

@classmethod 라는 데코레이터를 붙여 주면 그 아래의 함수는 클래스 메소드가 됩니다. 

클래스 메소드는 2개의 인자를 받는데요. 첫 번째 cls는 클래스 그 자체를 뜻하며, num은 변수 자리입니다. 함수의 행위 자체는 말도 안 되지만 이렇게 씁니다. cls는 클래스 자체가 되니 클래스 변수인 student_num에도 접근할 수 있는 것입니다. 

호출할 때는 다음과 같이 호출합니다. 클래스.메소드(변수) 이렇게요.

 

Student.transfer_student(20)

 

(3) static 메소드

static 메소드는 정적 메소드라고도 부릅니다. static 메소드의 특징은 크게 두 가지인데요, 첫 번째는 클래스 메소드와 같이 데코레이터가 붙는다는 것이고, 두 번째는 인자 목록에 self가 없다는 점입니다. 또한 클래스와 인스턴스 모두로 호출이 가능합니다.

 

@staticmethod
def isSenior(s):
  if s._year == 4:
    return '{} is senior.'.format(s._name)

인자 목록에 self나 cls 등의 인자가 없다고 해서 인자로 객체를 받을 수 없다는 뜻은 아닙니다. 위와 같이 Student 객체를 인자로 받을 수 있습니다. 다만 꼭 인스턴스로 호출을 해야 하는 게 아닌 거죠. 즉, 다음과 같이 호출이 가능합니다.

 

Student.is_senior(s1)
s2.is_senior(s2)

 

 

2-5. 클래스와 인스턴스의 속성, 메소드, 변수 확인하기

dir()은 어떤 객체를 인자로 넣으면 해당 객체가 어떤 변수와 메소드를 가졌는지 쭉 나열합니다.

__dict__는 현재 인스턴스와 클래스의 속성을 딕셔너리로 확인할 수 있습니다.

 

# dir 호출
# 클래스로 dir
print(dir(Student))
# 결과 : ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'isSenior', 'sayHi', 'student_num', 'transfer_student']

# 인스턴스로 dir
print(dir(s1))
# 결과 : ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_major', 
'_name', '_year', 'isSenior', 'sayHi', 'student_num', 'transfer_student']

뭔가 다른 게 보이시나요? 클래스로 dir을 했을 때는 기본적인 빌트인 메소드와 우리가 직접 정의한 메소드인 sayHi, isSenior, transfer_student, 클래스 변수인 student_num이 추가로 나오지만, 인스턴스로 dir을 했을 때는 거기에 덧붙여 인스턴스가 가진 인스턴스 변수까지 나오게 됩니다. 이처럼 뭔가 데이터를 갖고 놀아야 하는데 뭘 쓸 수 있는지 모르겠다면 dir 목록을 호출해서 어떤 메소드, 변수를 쓸 수 있는지 확인할 수 있습니다.

이번에는 dict를 호출해 보겠습니다.

 

# dict 호출
# 클래스로 dict
print(Student.__dict__)
# 결과 : {'__module__': '__main__', 'student_num': 3, '__init__': <function Student.__init__ at 0x0000024E160AA0D0>, '__str__': <function Student.__str__ at 0x0000024E160AA160>, 'sayHi': <function Student.sayHi at 0x0000024E160AA1F0>, 'transfer_student': <classmethod object at 0x0000024E16053400>, 'isSenior': <staticmethod object at 0x0000024E16053BB0>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}

# 인스턴스로 dict
print(s3.__dict__)
# 결과 : {'_name': 'Carol', '_major': 'Economics', '_year': 1}

dir과 달리 클래스와 인스턴스로 호출했을 때의 결과가 각각 많이 다릅니다. 클래스로 dict를 호출했을 때는 현재 클래스가 가진 속성과 값을 딕셔너리 형태(key-value)로 출력합니다. 그래서 클래스 변수인 student_num이 있고, 저희가 지정해준 __init__, __str__, sayHi, isSenior, transfer_student 등이 있습니다.

반면 인스턴스로 dict를 호출했을 때는 간소합니다. 인스턴스 변수로만 이뤄진 걸 볼 수 있습니다. 주로 인스턴스로 dict를 호출하게 되니 클래스로 dict 호출한 건 참고만 하면 좋을 것 같습니다.

 


 

이번 시간에는 클래스와 매직 메소드를 간단히 살펴 봤습니다.

어떠신가요? 저는 클래스를 간단히 만들어서 쓰긴 했지만, 이처럼 메소드와 변수를 클래스/인스턴스 별로 세세하게 구분해서 나누는 작업까지는 해본 적이 없는데요. 학교에서 배우지 않기도 했고요. 그래서 이번 강의에서 설명해주시는 게 특히 유용하게 느껴진 것 같습니다. 수업 중간중간에 파이썬 컨벤션을 언급하거나, 어려운 개념은 검색으로도 개념을 확인시켜주는 게 특히 좋았네요. ㅎ

 

자세한 내용은 강의에서 확인해 보시기 바랍니다. ㅎㅎ

다음에는 파이썬 데이터 모델 내용으로 포스팅하겠습니다.

 

 

이 포스팅은 '인프런 리프' 활동의 일환으로, <우리를 위한 프로그래밍 : 파이썬 중급> 강의를 무료로 제공받고 수강한 후기를 담고 있습니다.

 

 

 

References

 

 

다음 글 보기

 

[인프런 리프 2기] 2주 - 파이썬 스페셜 메소드

오늘은 인프런 리프 2주차 미션인 섹션 3을 정리해 보겠습니다. 제가 수강하는 강의는 [우리를 위한 프로그래밍], 파이썬 중급 과정 인프런 오리지널 강의입니다. 섹션 3 : 파이썬 스페셜 메소드

dev-dain.tistory.com

 

728x90
반응형
0 Comments
댓글쓰기 폼