python 클래스!

클래스

class Car:
    brand = 'hyundai'           # 클래스변수
    def __init__(self, name):   # 생성자
        self.name = name

    def print_name(self):
        print(self.name)


class Truck(Car):
    def __init__(self, name, size):
        super().__init__(name)
        self.size = size

    def print_name(self):       # 메서드오버라이딩
        print("truck name:", self.name)

    def print_size(self):
        print(self.size)


if __name__ == '__main__':
    c1 = Car('sonata')
    print(Car.brand)    # hyundai
    c1.print_name()     # sonata

    Truck.brand = 'kia'
    t1 = Truck('bongo', 10)
    print(Truck.brand)  # kia
    t1.print_name()     # truck name: bongo
    t1.print_size()     # 10
    print(Car.brand)    # hyundai

클래스변수는 클래스 단위에서 공유되는 전역변수
Car.brand, Truck.brand 을 출력값을 확인하면 알 수 있다, 초기값은 부모클래스에서 가져온다.

메서드

아래 2종류의 메서드에 대해 알아보자.

  • 정적메서드
  • 클래스메서드
    클레스메서드는 좀더 기능이 추가된 정적메서드라 볼 수 있다.
class Pizza(object):
    # 클래스변수
    radius = 42

    def __init__(self, size):
        self.size = size

    # 일반메서드
    def get_size(self):
        return self.size

    # 정적메서드
    @staticmethod
    def mix_ingredients(x, y):
        return x + y

    # 클래스메서드
    @classmethod
    def get_radius(cls):
        return cls.radius

if __name__ == '__main__':
    ing = Pizza.mix_ingredients(1, 2)
    rad = Pizza.get_radius()
    print(ing) # 3
    print(rad) # 42

클래스메서드 특징

  • 객체생성 없이 바로 호출 가능
  • cls 클래스바인딩을 사용해 클래스 상태(변수)에 접근가능
  • 주로 펙토리 패턴으로 객채생성 해야할 경우 사용함
# 클레스메서드를 사용한 펙토리 패턴
@classmethod
def factory(cls, init_dict):
    return cls(init_dict.name, init_dict.age)

special method

__init__, __call__

class CountCalls(object):
    # 생성자
    def __init__(self, num1, num2):
        self.num1 = num1
        self.num2 = num2
        print("init invoked", self.num1, self.num2)
    # 호출가능객체(callable object) 로 정의할 떄 사용하는 메서드
    def __call__(self, keyword1, keyword2):
        print("call invoked", keyword1, keyword2)


if __name__ == '__main__':
    cc1 = CountCalls(1, 2)  # init invoked, 1, 2
    cc1("test", "print")    # call invoked test print

__call__ 은 인스턴스를 함수처럼 사용할 수 있게 한다.

class Adder:
    def __init__(self, increment):
        self.increment = increment

    def __call__(self, value):
        return value + self.increment

# 함수처럼 동작하는 클래스 인스턴스
add_five = Adder(5)
print(add_five(10))  # 15

# 다른 인스턴스
add_ten = Adder(10)
print(add_ten(10))  # 20

__str__, __repr__

__str__: str은 입력 받은 객체의 문자열 버전을 반환하는 함수 __repr__: Representation 의 약자,

추상메서드

추상메서드는 하위 클래스가 재정의하지 않으면 오류를 발생하기 위한 개념으로 자주 사용한다.
JIT 언어인 파이썬에서 아래와 같이 Pizza 객체를 생성했다고 컴파일에러를 발생시키지 않기에 쉽게 버그가 발생한다.

class Pizza(object):
    pass

class CheesePizza(Pizza):
    def get_name(self):
        return "cheese"

if __name__ == '__main__':
    p = Pizza() # 에러발생하지 않음

이때 유용하게 사용할 수 있는 패키지가 abc(abstract base class)

from abc import ABC, abstractmethod


class Pizza(ABC):
    @abstractmethod
    def get_name(self):
        """TODO method overriding"""


if __name__ == '__main__':
    p = Pizza()  # 생성자를 호출하자마자 에러가 발생
                 # TypeError: Can't instantiate abstract class Pizza with abstract method get_name

클래스 decorator

__call__ 를 사용하면 클래스를 함수처럼 사용할 수 있기 때문에 클래스를 decorator 로 정의할 수 있다.
아래 같이 클로저를 사용하지 않아도 내부 변수를 유지하기 위해 사용할 수 있고

class CountCalls(object):
    def __init__(self, f):
        self.f = f
        self.called = 0

    def __call__(self, *args, **kwargs):
        self.called += 1
        return self.f(*args, **kwargs)


@CountCalls
def print_hello1():
    print("hello1")


@CountCalls
def print_hello2():
    print("hello2")


if __name__ == '__main__':
    print_hello1()
    print_hello1()
    print_hello2()
    print_hello2()
    print_hello2()
    print(print_hello1.called) # 2
    print(print_hello2.called) # 3

decorator 매개변수 지정하는 것도 중첩함수 정의 없이 쉽게 정의 가능하다.

class MyDecorator:
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

    def __call__(self, func):
        def wrapped_func(*args, **kwargs):
            print(f"Arguments received: {self.arg1}, {self.arg2}")
            return func(*args, **kwargs)

        return wrapped_func


@MyDecorator("Hello", "World")
def my_function():
    print("Function is called")


my_function()
my_function()
# Arguments received: Hello, World
# Function is called
# Arguments received: Hello, World
# Function is called

상속

class Car:
    ...

class Truck(Car):
    ...

클래스정의시 상속할 클래스명을 삽입하여 진행한다.
메서드 오버라이딩 개념역시 적용된다.

메서드해석순서(MRO:method resolution order) - 다중상속

class MyClass:
    pass


if __name__ == '__main__':
    print(MyClass.mro()) # [<class '__main__.MyClass'>, <class 'object'>]
    print(type(MyClass.mro())) # <class 'list'>

pythonMRO 라는 클래스간 상속트리(함수호출순서)를 구축한다.

class Parent:
    pass


class Child(Parent):
    pass


if __name__ == '__main__':
    print(Child.mro())  # [<class '__main__.Child'>, <class '__main__.Parent'>, <class 'object'>]

MRO 의 출력결과를 보면 배열안에 Child, Parent, object 가 순서대로 들어가 있다.

이번엔 다중상속시에 MRO 구성을 확인해보면 C, A, B, object 순서대로 들어가 있다.

class A(object):
    def foo(self):
        pass

class B(object):
    def bar(self):
        pass

class C(A, B):
    def baz(self):
        pass

if __name__ == '__main__':
    print(C.mro())
    # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

메서드가 호출되면 MRO 상속트리를 기준으로 호출할 메서드를 찾는다.

클래스정의문 또한 일종의 참조변수에 존재하는 데이터(참조변수)로써 클래스관련 동작을 할 때 마다 해당 참조변수로부터 값을 가져와 동작시킨다.

그래서 아래같은 클래스정의 참조변수를 사용한 이상한 코드 역시 정상 동작한다.

def test_func():
    return Parent  # 파이썬 가장 기본클래스 를 가리키는 참조변수


class Parent:
    pass


class Child(test_func()):
    pass


if __name__ == '__main__':
    print(Child.mro())  # [<class '__main__.Child'>, <class '__main__.Parent'>, <class 'object'>]

super

super 에는 2개의 인자값이 들어간다.

  • 첫번째 인자는 프록시로 생성할 클래스타입
  • 두번째 인자는 인스턴스, 첫째 인자의 자식 인스턴스이어야함.

파이썬에서 super는 상위객체의 생성자를 호출하는 것과 비슷하다.
super 를 호출할 때 마다 첫번째 인자의 프록시 객체 super<상위객체> 가 생성된다.

아래와 같이 바로 상위객체의 함수를 호출하기 위해 super(C, self) 이런식으로 많이 사용했는데
Python 3 부터 super 함수의 인자가 사라졌다, 바로 위의 부모 프록시 객체를 생성한다는 가정 하에 super().foo() 형태로 호출 가능하다.

class A(object):
    def foo(self):
        return "foo"


class B(object):
    def foo(self):
        return "bar"

    def bar(self):
        return "bar"


class C(A, B):
    def foo(self):
        return "baz" + super(C, self).foo() + super(A, self).foo()

    def bar(self):
        return "baz" + super().bar()


if __name__ == '__main__':
    c = C()
    print(C.mro())  # [<class '__main__.C'>,
                    #   <class '__main__.A'>,
                    #   <class '__main__.B'>,
                    #   <class 'object'>]

    print(c.foo())  # bazfoobar
    print(c.bar())  # bazbar

MRO 리스트를 살펴보고 super 의 첫번째 인자를 삽입하여 상위클래스의 메서드를 호출시킬 수 있다.

Enum

from enum import Enum, IntEnum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

class Status(IntEnum):
    SUCCESS = 1
    FAILURE = 2
    PENDING = 3

if __name__ == '__main__':
    for color in Color:
        print(color)
        # Color.RED
        # Color.GREEN
        # Color.BLUE

@dataclass

python 3.7 에 추가

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

자동으로 __init__, __repr__, __eq__ 등의 메서드를 생성한다.