파이썬

파이썬(python) - 클래스(class)의 특징

티베트 모래여우 2021. 11. 4. 14:27
반응형

개요

이번 포스트에서는 파이썬에서 클래스가 가지는 특징에 대해 다뤄보겠습니다.

클래스가 무엇인지, 어떻게 선언하는지 등 기초적인 내용은 다루지 않으므로 유의해주세요.

걱정마세요. 구글신께서 다 알려주신답니다


클래스의 특징

1. 모든 클래스는 기본적으로 object 클래스를 상속받고 있다.

- objcet 클래스는 최상위 클래스로서 파이썬에서 선언되는 모든 클래스는 기본적으로 object 클래스를 자동으로 상속받습니다.

class test: # 1
    pass

class test(): # 2
    pass

class test(object): # 3
    pass

따라서 위의 세 가지 방법 중 어떤 방법을 사용하더라도 생성되는 클래스에 차이는 없습니다.

다만 1번, 2번 방법은 구 버전 파이썬(정확히는 파이썬 2의 2.2 이후 버전)에서 호환성 관련 문제를 일으킵니다. 따라서 호환성에 중점을 둬야 한다면 3번 방법으로 클래스를 선언해야 합니다.

object 클래스는 특수한 메소드(매직 메소드)와 특수한 속성을 가지고 있습니다. 다른 기능이 있는 것은 아니지만 이 built-in 되어있는 메소드와 속성을 오버라이딩 하여 원하는 동작을 하게끔 만들 수 있습니다.

2. 파이썬의 모든 자료형은 사실 클래스다.

- 파이썬에 존재하는 모든 자료형은 클래스로서 존재합니다. 따라서 어떤 데이터가 있을 때, 그 데이터는 자기가 속하는 자료형의 인스턴스가 됩니다.

print(type(int))
# <class 'type'>

type에 int를 넣어서 출력하면 이렇게 클래스라고 알려줍니다. 이는 다른 자료형도 마찬가지 입니다.

print(isinstance(1, int))   # True
print(isinstance("hi", str))# True

isinstance로 확인해보면 True를 반환합니다.

print(dir(int))
# ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', 
'__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', 
'__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', 
'__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', 
'__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', 
'__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', 
'__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', 
'__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', 
'__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 
'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 
'real', 'to_bytes']

dir에 int를 넣어서 출력해보았습니다. 정말 많은 매직 메소드들을 가지고 있죠? 이를 통해 자료형 클래스 또한 object 클래스를 상속받고 있음을 알 수 있습니다.

같은 자료형끼리 연산할 때 내부적으로 __add__, __mul__과 같은 매직 메소드를 사용하는데 이 내용은 추후 매직 메소드에 대해 다룰 때 다시 설명드리겠습니다.

다 외울 필요는 없으니 겁먹지 마세요

3. 내부 메소드를 인스턴스 메소드(Instance method), 클래스 메소드(Class method), 스태틱 메소드(Static method)로 구분할 수 있다.

- 선언 방법과 목적에 따라 메소드를 3가지로 구분할 수 있습니다.

(클래스 변수 : 클래스 전체에서 전역으로 사용되는 값. 생성된 모든 인스턴스에서 값을 공유한다.)

(인스턴스 변수: 인스턴스마다 독립된 값을 가지는 변수. 하나의 인스턴스에서 값을 변경해도 다른 인스턴스에 영향을 미치지 않는다.)

class Test(object):
    age = 10 # 클래스 변수

    def __init__(self, name):
        self._name = name # 인스턴스 변수

    def say_hi(self):                  # 3-1. 인스턴스 메소드
        print(f"hi, I'm {self._name}")

    @classmethod
    def greeting(cls):                 # 3-2. 클래스 메소드
        print(f"I'm {cls.age} years old")

    @staticmethod                      # 3-3. 스태틱 메소드
    def good_bye():
        print("bye bye")

3-1. 인스턴스 메소드(Instance method)

ㄴ 일반적으로 가장 많이 사용하게 되는 유형입니다. 클래스를 통해 생성한 인스턴스에서 호출할 수 있습니다. 클래스가 object 클래스를 무조건 상속받듯이 인스턴스 메소드는 첫 번째 인자로 무조건 객체 자신을 넘겨받게 됩니다.(예시에서는 self가 객체 자신이 됩니다.)

이렇게 넘겨받은 self 인자를 통해 __init__에서 정의한 인스턴스 변수에 접근할 수 있습니다.

각 객체는 독립적이므로 인스턴스 메소드를 통한 행동은 메소드를 호출한 객체에게만 영향을 미칩니다.

class Test(object):
    def __init__(self, name):
        self._name = name

    def print_name(self):  # _name 출력
        print(self._name)

    def change_name(self, name):  # _name 변경
        self._name = name


test1 = Test("A")
test2 = Test("B")
test1.print_name()  # A
test2.print_name()  # B

test2.change_name("C")  # test2의 _name을 C로 변경

test1.print_name()  # A
test2.print_name()  # C

test1과 test2 객체를 생성하고 test2의 _name 값만 인스턴스 메소드를 이용해 바꾸었습니다.

test1의 _name 값은 변하지 않고 test2의 값만 변한 것을 확인할 수 있습니다.

3-2. 클래스 메소드(Class method)

ㄴ 클래스 메소드는 인스턴스 대신 클래스 자체에서 호출되는 메소드입니다.

@classmethod 라는 데코레이터를 사용하여 선언할 수 있습니다. 인스턴스 메소드처럼 첫 번째 인자로 무언가가 넘어오는데, 이는 클래스 자신을 의미합니다.(위의 예시에선 cls) 따라서 클래스 메소드는 인스턴스 변수나 메소드에 접근할 수 없지만 대신 클래스 변수에 접근이 가능합니다.

class Test(object):
    money = 100

    @classmethod
    def add_money(cls, value):
        cls.money += value

test = Test()
test2 = Test()

print(test.money)  # 100
Test.add_money(100)
print(test2.money) # 200

Test 클래스를 이용해 인스턴스를 2개 만들고 두 인스턴스를 통해 클래스 변수를 출력하였습니다. 첫 번째 print는 100을, 두 번째 print는 200을 출력하는 것으로 보아 확실히 같은 값을 공유하는 것을 알 수 있습니다.

class Test(object):
    def __init__(self, name, age) -> None:
        self._name = name
        self._age = age

    @classmethod
    def create(cls, name, age):
        return cls(name, age)


test1 = Test("춘삼이", 12)
test2 = Test.create("덕팔이", 11)

print(isinstance(test1, Test)) # True
print(isinstance(test2, Test)) # True

또한 첫 번째 인자가 클래스 자신이라는 것을 활용해 클래스 메소드로 인스턴스를 직접 생성할 수도 있습니다.

3-3. 스태틱 메소드(Static method)

ㄴ 스태틱 메소드는 클래스 메소드처럼 클래스에서 바로 호출할 수 있는 메소드입니다.

@staticmethod 데코레이터를 이용해 선언할 수 있습니다. 언뜻 보면 클래스 메소드와 비슷해 보이지만 스태틱 메소드는 기본 인자로 넘어오는것이 아무것도 없습니다. 따라서 스태틱 메소드는 다른 외부적인 상태에 영향을 미치지 않는 순수 함수(pure function)로서 사용합니다. 유틸리티 적인 측면이 강하다고 생각하시면 됩니다.

class Test(object):
    
    @staticmethod
    def join_str(*args):
        return "".join(args)


test = Test.join_str("A", "B", "C", "D")
print(test)

스태틱 메소드 join_str은 전적으로 args인자에만 의존합니다. 따라서 같은 값을 넣으면 항상 같은 결과를 반환합니다. 이렇게 인스턴스나 클래스 자체에 아무런 영향을 미치지 않을 때 스태틱 메소드를 사용합니다.

참고로 이 스태틱 아닙니다.

반응형