[Python] Class attribute 와 instance attribute 의 구분과 구현
오해
Javascript를 공부할때는 (ES5 시절) 두툼한 책 앞에 앉아서 한 장 한 장 넘겨가며 공부를 했기 때문에, 객체의 생성이나 내부 동작에 대해 꽤나 명료하게 이해하고 있었지만, Python은 100줄 이내의 스크립트로 대충 배워서 쓰고 있었던 탓에 특히 Python의 class attribute 와 instance attribute 차이 구분이 잘 안되었는데, 아래와 같은 코드에서 어려움을 겪었습니다.
분명 class에 바로 member를 선언하면 객체의 static member(=class attribute)로 들어간다고 알고 있었습니다. 아래 그림의 Fig1 처럼 이해를 했는데, 이렇게 이해하고 나면 8번 줄이 설명이 안되고, 그렇다고 Fig2 처럼 보면 11번 줄이 예상했던 것과 동작이 달랐습니다.
왜 그럴까 고민을 하다가 각각의 인스턴스에 __dict__ 를 확인해보고 나서야 알게되었는데, 실제로는 Fig3처럼 동작합니다.
instance2는 자기 자신 인스턴스가 attr 이라는 멤버를 가지고 있고, instance1 은 attr 이라는 멤버를 가지고 있지 않아서 class attribute의 값을 가져오는 형태로 구성이 됩니다. attr을 수정하지 않고 인스턴스에서 가져올때랑 attr 을 수정한 다음 가져올때 가져오는 위치가 바뀐다는 것이 내용을 이해하기 전까지 정적 언어의 객체지향처럼 동작을 예상했기 때문에 저한테는 쉽게 짐작하기 어려운 부분이었습니다.
sth 에 값을 추가하려면 Attribute Error 를 보여주지만, 이 경우에도 class attribute 가 존재하는 경우에는 다시 instance attribute 가 생성됩니다. 만약 class attribute 만 존재하는 경우 instance attribute 가 생성되지 않게 하려면 hasattr로는 판별하기 힘들고, __dict__ 배열에서 참고해야합니다. 이 경우 위 코드가 조금 변형이 됩니다.
개별 클래스에 attribute 가 추가되지 않도록 하는 코드를 조금 더 일반화하기 위해 객체를 상속하여 class attribute 가 추가되지 않도록 할수도 있습니다.
Decorator를 이용해서 구현하는 방법도 존재합니다.
간단하게 python 에서 instance attribute 가 추가되지 않도록 구현하는 방법을 정리하였는데, Fig1 처럼, instance attribute 가 존재하지 않고 class attribute 만 존재할때 알아서 class attribute를 수정하는 객체를 구성할 수도 있습니다.
왜 그럴까 고민을 하다가 각각의 인스턴스에 __dict__ 를 확인해보고 나서야 알게되었는데, 실제로는 Fig3처럼 동작합니다.
instance2는 자기 자신 인스턴스가 attr 이라는 멤버를 가지고 있고, instance1 은 attr 이라는 멤버를 가지고 있지 않아서 class attribute의 값을 가져오는 형태로 구성이 됩니다. attr을 수정하지 않고 인스턴스에서 가져올때랑 attr 을 수정한 다음 가져올때 가져오는 위치가 바뀐다는 것이 내용을 이해하기 전까지 정적 언어의 객체지향처럼 동작을 예상했기 때문에 저한테는 쉽게 짐작하기 어려운 부분이었습니다.
수정
동적으로 인스턴스에 member를 추가할 수 있다는 것은 동적 타입 언어의 기능이지만, 때때로 유지보수나 코드 파악을 어렵게 하기도 합니다. Javascript ES5의 Object.seal() 과 같은 동작을 의도하려고 합니다. python에서는 클래스가 생성된 이후 새로운 member가 추가되지 않도록 __setattr__ 함수를 새로 구현할 수 있습니다.
개별 클래스에 attribute 가 추가되지 않도록 하는 코드를 조금 더 일반화하기 위해 객체를 상속하여 class attribute 가 추가되지 않도록 할수도 있습니다.
Decorator를 이용해서 구현하는 방법도 존재합니다.
간단하게 python 에서 instance attribute 가 추가되지 않도록 구현하는 방법을 정리하였는데, Fig1 처럼, instance attribute 가 존재하지 않고 class attribute 만 존재할때 알아서 class attribute를 수정하는 객체를 구성할 수도 있습니다.
마치며
사실 이렇게 복잡하게 객체 내부에서 구현하지 않아도 __slots__ 에 attribute를 지정하면 instance attribute 를 추가하지 못하게 할 수 있습니다. 심지어 __slots__ 를 지정하면 메모리 사용량을 줄일 수 있는 장점도 있습니다. __slots__ 구현을 코드로 넣다보면 내가 attribute 를 추가하기 위해 __init__ 에 추가하면서 __slots__ 에도 추가해야하는 중복이 마음에 안들어서 이런식으로도 구현이 가능함을 확인했습니다. 물론 실제로 동작해야 하는 프로덕션 코드에서는 __slots__ 를 활용하는게 더 현실적이고 pythonic한 방법이겠습니다.
댓글
댓글 쓰기