Python

[Python - Tip] __getattr__ vs. __getattribute__ : 차이 및 활용 예시 (feat. __init__)

하룻강아지^^ 2022. 4. 13. 21:02
728x90
반응형

 


요약

- 클래스의 __init__은 훨씬 익숙할 테니 이와 비교해서 설명을 드리면...

- __init__ 은 클래스로 부터 인스턴스를 생성할 때, "자동적으로" 실행이 되는 매소드라고 볼 수 있습니다.

- __getattr__ 이나, getattribute__ 도 __init__ 과 유사하게 특정 시점에 실행되는 매소드 역할을 하는데, 각각 실행되는 때가 다르다고 보면 되겠습니다.

- 프로그램의 특정 동작에서 정보를 가로채서 원하는 다른 동작을 하기 때문에 Hooking (갈고리, 낚시바늘) 이라고 합니다.

- 그럼 __getattr__ 이나, getattribute__ 은 언제 실행될까요? 다음과 같이 정리할 수 있겠습니다.

매소드 실행 시점
__init__ 맨 처음 인스턴스를 생성할 때
__getattr__ 인스턴스의 속성 (변수, 매소드)에 접근할 때, 가장 마지막
__getattribute__ 인스턴스에서 속성 (변수, 매소드)에 접근할 때, 가장 처음

 

- 본 글에서 도출할 수 있는 실용적인 결론은, 일반적인 상황에서는 에러메시지가 "__getattr__  raise ..." 라고 뜬다면, '혹시 변수이름이나 매소드 이름을 잘못 썼는지 확인해보는 것(오타나 대소문자 실수)이 필요하다.' 정도 가 될것 같습니다.

- 빈번히 발생하는 에러메시지에 대한 효율적인 디버깅 방법은 다음을 참고하시면 좋을 것 같습니다.

[Python] - [Python - Error] NameError: ... / AttributeError: ... / __getattr__ raise NameError(attr) - 발생 원인 및 체계적인 디버깅 방법

 

 


상세 설명

__init__ : 인스턴스 생성 시 호출

- 다음 코드에서 확인할 수 있듯이, 클래스로부터 인스턴스가 생성되면 자동으로 __init__ 매소드가 호출됩니다.

- 코드

class TEST: # 클래스 생성
    def __init__(self): # 인스턴스 생성시 자동으로 호출되는 매소드
        self.var0 = "기본값"
        self.var1 = "var1"
        print("TEST CLASS make instance")
        print()

T = TEST() # 인스턴스 생성 --> 자동으로 __init__ 매소드 호출
print(T.var1)

 

- 결과

 

 

__ getattr__ : 인스턴스 속성에 접근 시 가장 마지막에 호출

- 아래 코드와 같이 어떤 속성 (attribute: 변수나 매소드 등)에 접근할 때, 그 속성을 코드에서 끝까지 찾아본 뒤, 없으면 맨 마지막에 실행하는게 __getattr__ 입니다.

 

- 코드

    * var1은 잘 정의 되었으므로 __getattr__을 호출할 필요 없이 변수 이름이 그대로 출력 됩니다.

    * var2는 정의한적 없기 때문에, 속성값을 찾지 못해서 맨 마지막에 __getattr__ 이 호출됩니다.

    * __getattr__ 에서는 보통 (1) 속성을 찾지 못했다는 에러 메시지를 띄우거나, (2) 프로그램 종료를 막기 위해 기본값을 할당하거나 할 수 있습니다.

class TEST: # 클래스 생성
    def __init__(self): # 인스턴스 생성시 자동으로 호출되는 매소드
        self.var0 = "기본값"
        self.var1 = "var1"
        print("TEST CLASS make instance")
        print()

    def __getattr__(self, arg): # 속성 접근 시 가장 마지막에 호출되는 매소드
        print(arg, ": __getattr__ called")
        # raise Exception("No attribute exist:",arg) # 주석 해제 시 에러를 띄울 수도 있습니다.
        return self.var0 # 마지막까지 속성을 찾지 못했으니 기본값을 넣어줍니다. (self.var0 = "기본값")

T = TEST() # 인스턴스 생성 --> 자동으로 __init__ 매소드 호출
print(T.var1)
print()
print(T.var2)
print()

 

- 결과 1

    * "var1"는 정의되었기 때문에 그대로 출력하지만, "var2"는 정의되지 않았기 때문에 __getattr__ 매소드가 호출됩니다.

    * 코드와 같이 __getattr__ 에서 정의되지 않은 변수를 var0인 "기본값" 으로 할당해서 return 해 줍니다.

 

- 결과 2 : "raise Exception(...)" 인 라인의 주석을 풀면 아래와 같은 에러메시지를 띄울 수도 있습니다.

 

 

__getattribute__ : 인스턴스 속성에 접근 시 가장 처음에 호출

- 아래 코드와 같이, 어떤 속성 (attribute: 변수나 매소드 등)에 접근할 때, 그 속성을 코드에서 찾기 전에 가장 처음 실행하는게 __getattribute__ 입니다.

 

- 코드

    * 인스턴스의 어떤 변수에 접근하더라도 가장 먼저 __getattribute__ 가 호출됩니다.

    * 즉, 잘 정의되어있는 var1을 출력하든, 정의되지 않은 var2를 출력하든 상관없이 __getattribute__ 를 호출합니다.

    * __getattribute__는 속성에 접근하기전에 우선적으로 어떤 동작을 하고 싶을 때 사용합니다.

    * 아래 코드에서는 입력받은 인자에 "_new"를 추가해서 return 하도록 구현되었습니다.

    * (참고) arg 대신 super().__getattribute__(arg) 를 사용하는것은 무한 루프를 방지하기 위함입니다.

class TEST: # 클래스 생성
    def __init__(self): # 인스턴스 생성시 자동으로 호출되는 매소드
        self.var0 = "기본값"
        self.var1 = "var1"
        print("TEST CLASS make instance")
        print()

    def __getattr__(self, arg): # 속성 접근 시 가장 마지막에 호출되는 매소드
        print(arg, ": __getattr__ called")
        # raise Exception("No attribute exist:",arg)  # 주석 해제 시 에러를 띄울 수도 있습니다.
        return self.var0 # 마지막까지 속성을 찾지 못했으니 기본값을 넣어줍니다. (self.var0 = "기본값")

    def __getattribute__(self, arg): # 속성 접근 시 가장 처음에 호출되는 매소드
        print(arg, ": __getattribute__ called")
        arg_new = super().__getattribute__(arg) + "_new" # 입력받은 인자(argument)에 "_new"를 붙여 return
        return arg_new
        

T = TEST() # 인스턴스 생성 --> 자동으로 __init__ 매소드 호출
print(T.var1)
print()
print(T.var2)
print()

 

 

- 결과

    * 잘 정의된 var1은 __getattribute__ 를 거쳐서 "var1_new"로 출력이 되었습니다.

    * 정의되지 않은 var2는 (1) 맨 처음 __getattribute__ 를 거쳐서 "var2_new"가 되었지만, (2) 그럼에도 속성을 찾지 못해 __getattr__을 거쳐 var0인 "기본값"으로 할당되고, (3) 다시 __getattribute__ 를 거쳐 "기본값_new"가 되었습니다.

 

 


참고

- 변수 뿐만아니라, 매소드도 마찬가지로 적용됩니다.

- 아래 코드에서 확인할 수 있듯이, Method2는 정의되지 않았기 때문에 __getattr__이 호출됩니다.

- 코드

class TEST: # 클래스 생성
    def __init__(self): # 인스턴스 생성시 자동으로 호출되는 매소드
        self.var0 = "기본값"
        self.var1 = "var1"
        print("TEST CLASS make instance")
        print()

    def __getattr__(self, arg): # 속성 접근 시 가장 마지막에 호출되는 매소드
        print(arg, ": __getattr__ called")
        raise Exception("No attribute exist:",arg)  # 주석 해제 시 에러를 띄울 수도 있습니다.
        # return self.var0 # 마지막까지 속성을 찾지 못했으니 기본값을 넣어줍니다. (self.var0 = "기본값")

    # def __getattribute__(self, arg): # 속성 접근 시 가장 처음에 호출되는 매소드
    #     print(arg, ": __getattribute__ called")
    #     arg_new = super().__getattribute__(arg) + "_new" # 입력받은 인자(argument)에 "_new"를 붙여 return
    #     return arg_new

    def Method1(self):
        print("Method1 called")


T = TEST() # 인스턴스 생성 --> 자동으로 __init__ 매소드 호출
# print(T.var1)
# print()
# print(T.var2)
# print()

T.Method1()
T.Method2()

 

- 결과

 


조금이나마 도움이 되었으면 좋겠습니다!

728x90
반응형