본문 바로가기
Python

[Python] 파이썬 클래스 사용법 및 예제

by A6K 2022. 11. 10.

파이썬은 클래스(class)를 통해 객체지향프로그래밍을 지원한다. 파이썬을 단순 스크립트 작성용으로 사용할 경우 클래스까지 필요하지 않을 수 있다. 하지만 파이썬을 이용해서 좀 더 복잡하고 큰 프로젝트를 하는 경우에는 클래스를 활용해서 코드의 재사용성을 늘릴 수 있고, 프로그램을 적절하게 모듈화해서 유지보수하기도 편해질 수 있다. (물론 잘써야…)

파이썬 클래스

파이썬에서는 클래스를 다음과 같은 문법으로 정의할 수 있다.

class MyClass:
  def __init__(self):
    # 생성자 구현
    pass
  def method1(self):
    # 메소드 구현
    pass

class 키워드 뒤에 정의하고자하는 클래스의 이름을 입력한다. 그리고 콜론으로 클래스를 정의하기 위한 블럭을 시작한다. 이후 def 키워드 뒤에 클래스에 정의될 메소드의 이름과 인자들이 오고, 역시 콜론을 찍어서 메소드의 정의 블럭을 시작한다.

이렇게 만들어진 클래스를 이용해서 다음과 같이 객체를 생성해 사용할 수 있다.

my_class = MyClass()

생성자

클래스를 이용해서 객체를 생성할 때, 제일먼저 호출되는 것이 생성자(constructor)다. 객체가 생성될 때, 객체를 위한 메모리가 할당되고 생성자가 실행되어 객체의 초기 설정을 진행한다.

파이썬 클래스에서는 __init__ 라는 이름의 메소드가 생성자 역할을 한다. 클래스의 객체를 생성하면 __init__() 메소드가 호출된다. 예를 들어보자.

class MyClass:
  def __init__(self):
    print('생성자 호출')
    
a = MyClass()
b = MyClass()

# 생성자 호출
# 생성자 호출

MyClass의 객체를 두개 생성했다. 두 번의 생성에서 __init__() 생성자가 한번씩 호출되어 '생성자 호출'이라는 문자열을 화면에 찍었다.

이렇게 만들어진 객체의 타입을 조사해보면 MyClass 임을 알 수 있다.

class MyClass:
  def __init__(self, value):
    self.value = value
    
a = MyClass(10)
print(type(a))

# <class '__main__.MyClass'>

메소드

클래스의 메소드는 클래스에 속해있는 함수를 의미한다. 함수가 def 키워드로 정의되었듯이 클래스를 정의하는 블록에서 def 키워드로 시작하는 부분이 메소드 정의부분이다. 특이한 점은 클래스의 메소드는 첫 번째 인자로 'self'를 받는다는 점이다.

self는 객체 그 자신을 의미한다. self를 이용해서 객체 자신이 가지고 있는 메소드를 호출하거나 인스턴스 변수의 값에 접근할 수 있다.

예를 들어보자.

class MyClass:
  def __init__(self, value):
    self.value = value
  def get_value(self):
    return self.value
    
a = MyClass(10)
value = a.get_value()
print(value)

# 10

MyClass 클래스의 객체를 생성할 때, 숫자값 하나를 넣어줬다. 이 값은 __init__() 생성자로 넘겨져 self.value 라는 인스턴스 변수에 할당된다. 이후 get_value() 메소드가 호출되면 self.value에 담겨있는 값을 리턴해준다.

파이썬에서 객체를 통해서 클래스의 메소드를 호출하기 위해서는 . 연산을 사용한다. 즉 객체변수.메소드() 형태로 호출한다. 그러면 메소드의 첫번째 인자로 객체변수가 담고 있는 객체가 넘겨지고, 메소드의 인자가 있다면 그 뒤에 이어서 넘겨진다.

흥미로운 예제를 하나 더 보자.


class MyClass:
  def __init__(self, value):
    self.value = value
  def get_value(self):
    return self.value
  def return_zero():
    return 0
    
a = MyClass(10)
value = a.return_zero()

# Traceback (most recent call last):
#   File "./test.py", line 13, in <module>
#     value = a.return_zero()
# TypeError: return_zero() takes 0 positional arguments but 1 was given

return_zero()라는 메소드를 정의했다. 이번에는 메소드 정의에 self 인자를 쓰지 않았다. 이런 상황에서 a 객체를 통해 return_zero() 메소드를 호출하면 TypeError가 발생한다. a.return_zero()를 호출하면 a에 해당하는 객체가 self 형태로 return_zero()의 첫번째 인자로 넘겨지는데 MyClass 클래스에는 self 를 첫번째 인자로 받는 return_zero() 메소드가 없기 때문에 에러가 발생한다.

self 인자가 지정되지 않은 메소드는 객체를 통해서 호출하지말고 클래스를 통해서 호출해야한다. 자바에서의 static 메소드와 비슷하다고 생각하면 될 것 같다.

class MyClass:
  def __init__(self, value):
    self.value = value
  def get_value(self):
    return self.value
  def return_zero():
    return 0
    
value = MyClass.return_zero()
print(value)

# 0

인스턴스 변수

클래스의 인스턴스 변수 혹은 멤버변수를 가질 수 있다. 클래스가 객체로 만들어지면 그 내부에 어떤 값을 저장할 수 있는 변수를 가질 수 있는데 이를 인스턴스 변수 혹은 멤버 변수라고 한다.

예를 들어보자.

class Person:
  def __init__(self, name, age):
    self.name = name
    self.age = age
  
  def get_name(self):
    return self.name
    
  def get_age(self):
    return self.age
    
a = Person('Dave', 27)
b = Person('Tom', 32)
 
print(f'{a.get_name()} is {a.get_age()} years old')
print(f'{b.get_name()} is {b.get_age()} years old')

# Dave is 27 years old
# Tom is 32 years old

Person이라는 클래스를 정의했다. 사람의 정보를 저장하는 클래스로 인스턴스 변수로 이름을 저장하는 name과 나이를 저장하는 age를 갖는다. 생성자에서 이름과 나이를 입력받아 self.name과 self.age라는 변수에 저장한다. 이 변수가 인스턴스 변수로 클래스 객체 내부에 저장된다.

get_name() 메소드를 호출하면 객체 내부에 있는 self.name 값을 get_age() 메소드를 호출하면 객체 내부에 있는 self.age 값을 리턴해준다.

클래스 변수

인스턴스 변수는 객체마다 독립적으로 자신의 값을 갖는다. 그런데 인스턴스 변수와는 또 다른 형태의 변수가 있다. 바로 클래스 변수다.

클래스 변수는 변수의 값이 객체 내부가 아닌 클래스에 저장된다.

class Person:
  class_var = '클래스 변수'
  
print(Person.class_var)

# 클래스 변수

인스턴스 변수처럼 self.변수 형태로 저장하는게 아닌 그냥 class_var 변수를 선언했다. 클래스 변수는 Person.class_var 처럼 클래스이름.클래스변수로 사용할 수 있다.

클래스 변수는 객체를 통해서도 접근할 수 있다.

class Person:
  class_var = '클래스 변수'
  
a = Person()
b = Person()

print(a.class_var)
print(b.class_var)

# 클래스 변수
# 클래스 변수

이제 클래스 변수를 수정해보자. 

class Person:
  class_var = '클래스 변수'
  
a = Person()
b = Person()

Person.class_var = '새로운 값'

print(a.class_var)
print(b.class_var)

# 새로운 값
# 새로운 값

클래스 변수를 '새로운 값'으로 변경하니 객체들에서 접근하는 클래스 변수들의 값이 모두 변경되었다.

그렇다면 객체를 통해서 클래스 변수의 값을 변경하려고하면 어떨까?

class Person:
  class_var = '클래스 변수'
  
a = Person()
b = Person()

a.class_var = '새로운 값'

print(a.class_var)
print(b.class_var)

# 새로운 값
# 클래스 변수

a.class_var 에 접근해서 값을 수정하는 행위는 클래스 변수를 변경하는게 아닌 인스턴스 변수인 class_var를 생성하는 동작으로 해석된다. 따라서 a.class_var의 값은 인스턴스 변수인 '새로운 값'이 출력되고, b.class_var의 값은 클래스 변수인 '클래스 변수'가 출력된다.

클래스의 상속

클래스는 다른 클래스를 상속해서 구현할 수 있다. 클래스를 상속한다는 의미는 다른 클래스의 정의를 기반으로 약간만 수정해서 새로운 클래스를 만든다는 것을 의미한다.

파이썬에서 클래스의 상속은 다음과 같이 할 수 있다.

class 부모클래스:
  ...
  
class 자식클래스(부모클래스):
  ...

자식클래스를 정의할 때 부모클래스를 상속해서 구현하려면 클래스 이름 옆에 소괄호로 상속받을 부모클래스의 이름을 적어주면된다. 이렇게 구현하면 자식클래스는 부모클래스에 정의되어 있는 멤버 변수와 메소드들을 그대로 상속받아 사용할 수 있다.

예를 들어보자.

class Person:
  def __init__(self, name, age):
    self.name = name
    self.age = age
  
  def get_name(self):
    return self.name
    
  def get_age(self):
    return self.age
    
class Student(Person):
  def __init__(self, name, age, grade):
    super().__init__(name, age)
    self.grade = grade

  def get_grade(self):
    return self.grade
   
a = Student('Dave', 27, 'A')
b = Student('Tom', 32, 'B')
 
print(f'{a.get_name()} is {a.get_age()} years old and grade is {a.get_grade()}')
print(f'{b.get_name()} is {b.get_age()} years old and grade is {b.get_grade()}')

# Dave is 27 years old and grade is A
# Tom is 32 years old and grade is B

Person 클래스를 상속한 Student 클래스는 Person 클래스에 정의되어 있는 get_name(), get_age() 메소드를 그대로 상속받고 있다. 별도로 정의를 하지 않아도 부모 클래스에 정의되어 있는 메소드를 사용할 수 있다. 여기에 추가로 자식 클래스만의 메소드인 get_grade() 메소드를 추가로 정의해서 사용할 수도 있다.

자식 클래스인 Student 클래스의 생성자를 보면, super().__init__() 메소드를 호출하는 부분을 볼 수 있다. 이 코드는 부모 클래스의 생성자를 호출하는 코드로 멤버 변수에 name과 age를 할당하는 코드를 별도로 구현하지 않고 부모 클래스의 생성자를 호출하는 것으로 사용한다.

만약 자식 클래스의 생성자에서 추가로 해야할 동작이 없다면 자식 클래스의 생성자는 따로 만들지 않아도 된다. 그렇게 되면 자식 클래스로 객체를 생성할 때 부모 클래스의 생성자가 알아서 호출된다. 하지만 자식 클래스의 생성자를 만들게 되면 부모 클래스의 생성자 메소드가 덮어쓰여져(오버라이드) 부모 클래스의 생성자가 자동으로 호출되지 않는다. 따라서 부모 클래스의 생성자를 호출해야할 필요가 있다면 super().__init__()을 호출해서 명시적으로 호출해줘야한다.

상속 관계에 있을 때 부모 클래스에 접근하려고 하면 이처럼 super()를 사용하면 된다.

메소드 오버라이드

클래스를 상속해 구현하면서 부모 클래스의 메소드를 다시 정의할 수 있다. 이를 메소드 오버라이드(Override)라고 한다.

class Parent:
  def method1(self):
    print('parent method')
    
class Child(Parent):
  def method1(self):
    print('child method')

a = Parent()
b = Child()

a.method1()
b.method1()

# parent method
# child method

Parent 클래스에 method1 이라는 메소드가 있다. Child 클래스는 Parent 클래스를 상속해서 구현했다. 이 때, method1() 메소드를 자식 클래스에서 다시 정의했다. 즉 method1() 메소드를 오버라이드했다. 자식 클래스로 객체를 생성한 후 method1()을 호출하면 자식 클래스에서 재정의한 메소드가 호출된다.


 

 

파이썬 스크립트 작성에 도움되는 글 모음

파이썬으로 프로그램을 작성할 때 도움되는 글들을 모아본다. 개발환경 [Python] macOS에 파이참 설치 [Python] 파이참 깃허브 연동 [Python] 파이썬 PIP란? [Python] VSCode를 이용한 개발환경 [Python] python3를

hbase.tistory.com

 

댓글