티스토리 뷰

OOP

Class in Python3

nickas 2020. 3. 29. 02:53

Class in Python3

클래스의 구조는 Python2Python3가 다름.
Python2는 더 이상 개발이 되지 않기에 Python3 내용만 기록.

클래스를 정의하면 클래스 오브젝트(객체)가 되고, 인스턴스를 생성하면 인스턴스 오브젝트(객체)가 됨.

편의상 클래스 오브젝트를 클래스로, 인스턴스 오브젝트를 인스턴스로 표기.

클래스 생성

클래스는 아래와 같이 class 구문을 사용하여 정의하며 클래스가 생성됨.

class Test:
    pass

인스턴스 생성

인스턴스는 아래와 같이 생성하며 인스턴스가 생성됨.

test = Test()

변수

클래스 변수

class Test:
    class_var = 0

>>> Test.class_var
0
>>> test = Test()
>>> test.class_var
0

>>> Test.class_var += 1
>>> Test.class_var
1
>>> test.class_var
1

클래스 내부에서 사용하기 위한 변수

인스턴스 변수

class Test:
    pass

>>> test = Test()
>>> test.instance_var = 0
0
>>> test.instance_var += 1
>>> test.instance_var
1

>>> test1 = Test()
>>> test.instance_var = 0
>>> test1.instance_var
0
>>> test1.instance_var += 1
>>> test1.instance_var
1

각 인스턴스 내에서 사용하기 위한 변수

클래스 변수 vs 인스턴스 변수

class Test:
    class_var = 0

>>> Test.class_var
0
>>> test = Test()
>>> test.class_var
0
>>> test1 = Test()
>>> test1.class_var
0

# 클래스 변수 값 변경
>>> Test.class_var += 1
>>> Test.class_var
1
>>> test.class_var
1
>>> test1.class_var
1

# 인스턴스 변수 생성
>>> test.instance_var = 0
>>> test.instance_var
0
>>> test1.instance_var = 0
>>> test1.instance_var
0

# 인스턴스 변수 값 변경
>>> test.instance_var += 1
>>> test.instance_var
1
>>> test1.instance_var
0

클래스 변수를 사용하면 생성된 모든 인스턴스에서 같은 데이터를 사용가능.
각각의 인스턴스는 해당 인스턴스 변수를 사용하기 때문에 각 인스턴스 마다 데이터가 달라짐.

Method 추가 하기

class 내부에 def 구문을 사용하여 추가

인스턴스 메소드 추가

class Test:
    def instance_method(self):
        print('I am instance method')

>>> test = Test()
>>> test.instance_method
<bound method Test.instace_method of <__main__.Test object at 0x101d43d30>>
>>> Test.test
<function Test.instance_method at 0x101c75a60>
>>> test.instance_method()
I am instance method
>>> Test.instance_method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: instance_method() missing 1 required positional argument: 'self'
>>> Test.instance_method(test)
I am instance method

인스턴스 메소드는 첫 번째 파라미터에 self가 필수

이름을 self로 하지 않아도 되지만, 일반적으로 self를 많이 사용함.
나 혼자 개발하면 다른 이름을 써도 되지만, 오픈소스를 이용하거나 다른 사람과 같이 개발 할 경우는 self를 사용하는게 제일 좋음. 즉 그냥 self 써!!!

클래스를 사용하여 메소드에 접근 가능하나 self 파라미터에 값을 전달해야함.

self는 인스턴스 오브젝트를 의미하고 인스턴스 오브젝트를 사용하여 메소드에 접근할 때는 자동으로 self 파라미터에 인스턴스 오브젝트를 자동으로 전달.

Class 메소드 추가

@classmethod 데코레이터를 사용하여 정의

class Test:
    @classmethod
    def class_method(cls):
        print('I am class method')


>>> test = Test()
>>> test.class_method
<bound method Test.class_method of <class '__main__.Test'>>
>>> Test.class_method
<bound method Test.class_method of <class '__main__.Test'>>
>>> test.class_method()
I am class method
>>> Test.class_method()
I am class method

Class 메소드는 첫번 째 파라미터에 cls가 필수

파라미터 cls는 다른 이름을 사용해도 되지만 일반적으로 cls를 사용.
cls는 클래스 오브젝트를 의미하고 클래스 오브젝트가 파라미터로 전달됨.

Static 메소드 추가

@staticmethod 데코레이터를 사용하여 정의

class Test:
    @staticmethod
    def static_method():
        print('I am static method')

>>> test = Test()
>>> test.static_method
<function __main__.Test.static_method()>
>>> Test.static_method
<function __main__.Test.static_method()>
>>> test.static_method()
I am static method
>>> Test.static_method()
I am static method

Static 메소드는 파라미터가 필요하지 않음. 일반 함수가 클래스 안에 정의된 형태라고 보면 됨.

Instance 메소드 vs Class 메소드 vs Static 메소드

class Test:
    class_var = 0
    def instance_method(self):
        self.instance_var = 0

    @classmethod
    def class_method(cls):
        cls.class_var += 1

    @staticmethod
    def static_method():
        static_var = 1
        return static_var

>>> Test.instance_method
<function __main__.Test.instance_method(self)>
>>> Test.class_method
<bound method Test.class_method of <class '__main__.Test'>>
>>> Test.static_method
<function __main__.Test.static_method()>

>>> test = Test()
>>> test.instance_method
<bound method Test.instance_method of <__main__.Test object at 0x104ff6e80>>
>>> test.class_method
<bound method Test.class_method of <class '__main__.Test'>>
>>> test.static_method
<function __main__.Test.static_method()>

>>> Test.class_var
0
>>> Test.static_method()
1
>>> test.class_var
0
>>> test.static_method()
1

>>> Test.class_method()
>>> Test.class_var
1
>>> test.class_method()
>>> test.class_var
2

>>> test.instance_method()
>>> test.instance_var
0
>>> test.__dict__
{'instance_var': 0}

클래스 메소드는 클래스 변수와 같은 클래스 내부의 데이터를 조작하기위해 사용하고, 인스턴스 메소드는 각각의 인스턴스 데이터를 조작하기위해 사용.
Static 메소드는 클래스나 인스턴스 데이터와 상관 없이 독립적인 기능을 수행하기 위해 사용. 외부에 별도의 함수로 정의해도 되고 해당 클래스와 관련있는 기능일 경우 가독성을 위해 클래스 내부에 정의해서 사용

Attribute

클래스나 인스턴스에서 사용하기 위한 값으로 변수나 그 밖의 오브젝트들이 될 수 있고 __dict__를 사용하여 모든 attribute을 확인 할 수 있음.

클래스 attribute와 인스턴스 attribute로 나뉨.

인스턴스 attribute

인스턴스 생성 시 attribute 추가

__init__() 생성자(Constructor)를 사용하면 인스턴스 생성 시 자동으로 attribute를 인스턴스에 추가.

class Test:
    def __init__(self):
        self.a = 1

>>> test = Test()  # 자동으로 'a' attribute가 추가됨.
>>> test.a         # attribute 확인
1
>>> test.__dict__  # 모든 attribute 확인
{'a': 1}

. notation를 사용하여 Attribute에 접근.

__dict__ 사용하면 모든 Attribute를 확인 할 수 있음.

메소드를 사용한 attribute 추가

class Test:
    def add_attribute(self):
        self.a = 1

    def access_attribute(self):
        print(self.a)  # self.a가 할당되어 있지 않으면 에러 발생

>>> test = Test()
>>> test.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'a'
>>> test.add_attribute()
>>> test.access_attribute()
1

할당하여 attribute 추가

class Test:
    pass

>>> test = Test()
>>> test.instance_var = 0
>>> test.instance
0
>>> test.__dict__['instance_var1'] = 0
>>> test.instance_var1
0

명시적으로 __dict__를 사용하여 attribute를 추가 할 수 있음

함수(메소드)도 추가 가능

class Test:
    pass

def func():
    print('test')

>>> test = Test()
>>> test.func = func
>>> test.func()
test

위 방법은 임시로 특정 인스턴스에 attribute을 추가하기 위해 사용.

setattr, getattr 사용하여 attribute 추가/접근

class Test:
    pass

>>> test = Test()
>>> setattr(test, 'instance_var', 0)
>>> test.instance_var
0
>>> getattr(test, 'instance_var')
0

setattr(object 명, attribute 이름, 값)을 이용하여 attribute를 할당할 수 있음.
getattr(object 명, attribute 이름)을 이용하여 attribute 값을 얻어 올 수 있음.

__setattr__, __getattr__ 사용

class Test:
    def __setattr__(self, attr, value):
        self.__dict__[attr] = value
        # 또는
        # super().__seattr__(attr, value)
        # self.attr = value -> 무한루프 됨. 사용금지

    def __getattr__(self, attr):
        if attr == 'age':
            return 40
        else:
            raise AttrubuteError(name)

>>> test = Test()
>>> test.a = 1  # Call __setattr__
>>> test.age      # Call __getattr__
40

__setattr__ 매직 메소드를 정의하면 인스턴스 오브젝트에 값을 할당 시 자동으로 __setattr__ 이 호출됨.

__getattr__ 매직 메소드를 정의하면 인스턴스 딕셔너리에 없는 attribute에 접근 시 자동으로 __getattr__이 호출 됨.

__setattr__ 메소드에 Attribute를 self.a = 0 이렇게 정의하면 무한 루프가 발생 함.
왜냐하면 self.a = 0 구문을 실행하면 내부적으로 슈퍼 클래스(암묵적으로 object 클래스를 상속받음)에 정의된 __setattr__를 호출하여 attribute를 할당되는데 sub 클래스에 __setattr__를 오버로딩 했기 때문에 우리가 정의한 __setattr__ 메소드가 실행되고 self.a = 0을 다시 실행하고 이것이 반복되기 때문에 무한 루프가 발생.
그래서 명시적으로 __dict__ 딕셔너리에 attribute를 할당.

__setattr__ 매직 메소드는 사용자 정의 attribute를 할당하고 싶을 때 사용.
예를 들어, attribute 이름이 name인 경우만 attribute 할당을 허용하고 싶은 경우.

class Test:
    def __setattr__(self, attr, value):
        if attr == 'name':
            self.__dict__[attr] = value
        else:
            raise AttributeError(attr)

property 이용하기

class Test:
    def set_attr(self, value):
        self.a = value

    def get_attr(self):
        return self.a  # self.a가 미리 정의되어 있지 않으면 에러 발생
    attribute = property(get_attr, set_attr, None, None)

>>> test = Test()
>>> test.attribute = 0
>>> test.attribute
40

property(getter, setter, del, docs)를 사용하여 attribute를 추가/접근 할 수 있다.

@property 데코레이션 사용하기

class Test:
    @property
    def instance_attribute(self):  # getter
        return self.instance_var  # self.instance_var이 미리 정의되어 있지 않으면 에러 발생

    @instance_attribute.setter
    def instance_attribute(self, value): # setter
        self.instance_var = value

>>> test = Test()
>>> test.instance_attribute = 0
>>> test.instance_attribute
0

Attribute를 추가 할 때 메소드 이름을 attribute 이름 처럼 사용하여 값을 할당
Attribute에 접근 할 때 메소드 이름을 attribute 이름 처럼 사용하여 접근

Attribute 지우기

class Test:
    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = 3

>>> test = Test()
>>> del self.a
>>> test.__dict__
{'b': 2}
>>> delattr(test, 'b')
>>> test.__dict__
{}
>>> test.__dict__.pop('c')

del, delattr(object, attr)를 사용하여 attribute 삭제

deldelattr은 기능적인 차이가 있음.
del이 좀 더 빠름

attribute는 __dict__에 딕셔너리로 저장되므로 pop() 사용하여 삭제

class Test:
    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = 3

    def __delattr__(self, attr):
        print(f'{name} attribute가 삭제됨')
        super().__delattr__(attr)
        # 또는
        # self.__dict__.pop(attr)
        # del self.__dict__[attr]

>>> test = Test()
>>> del test.a              # __delattr__ 호출
>>> delattr(test, 'b')      # __delattr__ 호출
>>> test.__dict__.pop('c')  # __delattr__ 호출 안함

__delattr__을 사용하여 사용자 정의 할 수 있음.

최근에 올라온 글
글 보관함