Python의 Magic 메소드는 클래스에 "magic"을 추가할 수 있는 특수 메소드입니다. 종종 두 개의 밑줄로 둘러싸여 이름이 지정됩니다.
Dunder(이중 밑줄) 방법으로도 알려진 Python의 마법 방법입니다. 대부분의 경우 생성자(init), 문자열 표현(str, repr) 또는 산술 연산자(add/mul)와 같은 간단한 작업에 이를 사용합니다. 사실, 들어보지 못했을 수도 있지만 매우 유용한 메서드가 많이 있습니다. 이 기사에서는 이러한 마법의 메서드를 정리하겠습니다.
우리 모두는 __len__ 메서드를 알고 있습니다. 컨테이너 클래스에 len() 함수를 구현합니다. 하지만 반복자를 구현하는 클래스 객체의 길이를 얻으려면 어떻게 해야 할까요?
it = iter(range(100)) print(it.__length_hint__()) # 100 next(it) print(it.__length_hint__()) # 99 a = [1, 2, 3, 4, 5] it = iter(a) print(it.__length_hint__()) # 5 next(it) print(it.__length_hint__()) # 4 a.append(6) print(it.__length_hint__()) # 5
위와 같이 반복자(생성자가 아님)에 내장된 메서드인 __length_hint__ 메서드를 구현하기만 하면 됩니다. 볼 수 있으며 동적 길이 변경도 지원합니다. 그러나 이름 그대로 이는 단지 힌트일 뿐이며 완전히 정확하다고 보장되지는 않습니다. 목록 반복자의 경우 정확한 결과를 얻을 수 있지만 다른 반복자의 경우 확실하지 않습니다. 그러나 정확하지 않더라도 PEP 424에 설명된 대로 필요한 정보를 얻는 데 도움이 될 수 있습니다.
위의 코드에서는 하위 클래스를 정의할 때 설정할 수 있는 기본 클래스에 키워드 인수를 추가합니다. . 실제 사용 사례는 단순히 속성에 할당하는 것이 아니라 제공된 매개변수를 처리하려는 경우 이 메서드를 사용하는 것입니다.length_hint는 정수(그렇지 않으면 TypeError가 발생함) 또는 NotImplemented를 반환해야 하며 정확할 필요는 없으며 컨테이너의 실제 크기보다 크거나 작은 값을 반환할 수 있습니다. NotImplemented는 유한 길이 추정이 없음을 나타냅니다. 음수 값을 반환하지 않을 수 있습니다(그렇지 않으면 ValueError가 발생함). 관련, 메타프로그래밍은 매일 사용해야 하는 것이 아닐 수 있지만 이를 사용하기 위한 몇 가지 편리한 트릭이 있습니다. 그러한 트릭 중 하나는 __init_subclass__를 메타클래스를 처리할 필요 없이 기본 클래스의 기능을 확장하는 지름길로 사용하는 것입니다.
class Pet: def __init_subclass__(cls, /, default_breed, **kwargs): super().__init_subclass__(**kwargs) cls.default_breed = default_breed class Dog(Pet, default_name="German Shepherd"): pass
class CallableClass: def __call__(self, *args, **kwargs): print("I was called!") instance = CallableClass() instance() # I was called!이 메소드를 사용하여 호출할 수 없는 클래스를 만들 수 있습니다.
class NoInstances(type): def __call__(cls, *args, **kwargs): raise TypeError("Can't create instance of this class") class SomeClass(metaclass=NoInstances): @staticmethod def func(x): print('A static method') instance = SomeClass() # TypeError: Can't create instance of this class정적 메소드만 있는 클래스의 경우 이 메소드는 인스턴스를 생성하지 않고 사용됩니다. 수업. 또 다른 유사한 시나리오는 싱글톤 패턴입니다. 클래스는 최대 하나의 인스턴스만 가질 수 있습니다.
class Singleton(type): def __init__(cls, *args, **kwargs): cls.__instance = None super().__init__(*args, **kwargs) def __call__(cls, *args, **kwargs): if cls.__instance is None: cls.__instance = super().__call__(*args, **kwargs) return cls.__instance else: return cls.__instance class Logger(metaclass=Singleton): def __init__(self): print("Creating global Logger instance")싱글턴 클래스에는 비공개 __인스턴스가 있습니다. 그렇지 않은 경우 생성 및 할당되고, 이미 존재하는 경우 반환만 됩니다. . 클래스가 있고 __init__을 호출하지 않고 해당 클래스의 인스턴스를 만들고 싶다고 가정해 보세요. __new__ 메소드가 도움이 될 수 있습니다:
class Document: def __init__(self, text): self.text = text bare_document = Document.__new__(Document) print(bare_document.text) # AttributeError: 'Document' object has no attribute 'text' setattr(bare_document, "text", "Text of the document")어떤 경우에는 인스턴스를 생성하는 일반적인 프로세스를 우회해야 할 수도 있습니다. 위의 코드는 이를 수행하는 방법을 보여줍니다. Document(…)를 호출하는 대신 __init__을 호출하지 않고 기본 인스턴스를 생성하는 Document.__new__(Document)를 호출합니다. 따라서 인스턴스의 속성(이 경우 텍스트)은 초기화되지 않으므로 추가적으로 setattr 함수를 사용하여 값을 할당해야 합니다(매직 메소드 __setattr__이기도 함). 왜 이러는 걸까요? 다음과 같이 생성자를 대체하고 싶을 수도 있기 때문입니다.
class Document: def __init__(self, text): self.text = text @classmethod def from_file(cls, file): # Alternative constructor d = cls.__new__(cls) # Do stuff... return dfrom_file 메소드는 생성자 역할을 하며 먼저 __new__를 사용하여 인스턴스를 생성한 다음 __init__를 호출하지 않고 구성합니다. 메타 프로그래밍과 관련된 다음 마법의 방법은 __getattr__입니다. 이 메서드는 일반적인 속성 액세스에 실패할 때 호출됩니다. 이는 누락된 메서드에 대한 액세스/호출을 다른 클래스에 위임하는 데 사용할 수 있습니다.
class String: def __init__(self, value): self._value = str(value) def custom_operation(self): pass def __getattr__(self, name): return getattr(self._value, name) s = String("some text") s.custom_operation() # Calls String.custom_operation() print(s.split()) # Calls String.__getattr__("split") and delegates to str.split # ['some', 'text'] print("some text" + "more text") # ... works print(s + "more text") # TypeError: unsupported operand type(s) for +: 'String' and 'str'문자열의 사용자 정의 구현을 정의하기 위해 클래스에 몇 가지 추가 함수(예: 위의 custom_Operation)를 추가하려고 합니다. 하지만 분할, 조인, 대문자화 등과 같은 모든 문자열 메서드를 다시 구현하고 싶지는 않습니다. 여기서는 __getattr__을 사용하여 기존 문자열 메서드를 호출할 수 있습니다. 이 방법은 일반 메서드에서 작동하지만 위의 예에서 연결과 같은 작업을 제공하는 매직 메서드 __add__는 위임되지 않습니다. 따라서 우리가 그것들도 작동하도록 하려면 그것들을 다시 구현해야 합니다.
最后一个与元编程相关的方法是__getattribute__。它一个看起来非常类似于前面的__getattr__,但是他们有一个细微的区别,__getattr__只在属性查找失败时被调用,而__getattribute__是在尝试属性查找之前被调用。
所以可以使用__getattribute__来控制对属性的访问,或者你可以创建一个装饰器来记录每次访问实例属性的尝试:
def logger(cls): original_getattribute = cls.__getattribute__ def getattribute(self, name): print(f"Getting: '{name}'") return original_getattribute(self, name) cls.__getattribute__ = getattribute return cls @logger class SomeClass: def __init__(self, attr): self.attr = attr def func(self): ... instance = SomeClass("value") instance.attr # Getting: 'attr' instance.func() # Getting: 'func'
装饰器函数logger 首先记录它所装饰的类的原始__getattribute__方法。然后将其替换为自定义方法,该方法在调用原始的__getattribute__方法之前记录了被访问属性的名称。
到目前为止,我们只讨论了魔法方法,但在Python中也有相当多的魔法变量/属性。其中一个是__all__:
# some_module/__init__.py __all__ = ["func", "some_var"] some_var = "data" some_other_var = "more data" def func(): return "hello" # ----------- from some_module import * print(some_var) # "data" print(func()) # "hello" print(some_other_var) # Exception, "some_other_var" is not exported by the module
这个属性可用于定义从模块导出哪些变量和函数。我们创建了一个Python模块…/some_module/单独文件(__init__.py)。在这个文件中定义了2个变量和一个函数,只导出其中的2个(func和some_var)。如果我们尝试在其他Python程序中导入some_module的内容,我们只能得到2个内容。
但是要注意,__all__变量只影响上面所示的* import,我们仍然可以使用显式的名称导入函数和变量,比如import some_other_var from some_module。
另一个常见的双下划线变量(模块属性)是__file__。这个变量标识了访问它的文件的路径:
from pathlib import Path print(__file__) print(Path(__file__).resolve()) # /home/.../directory/examples.py # Or the old way: import os print(os.path.dirname(os.path.abspath(__file__))) # /home/.../directory/
这样我们就可以结合__all__和__file__,可以在一个文件夹中加载所有模块:
# Directory structure: # . # |____some_dir # |____module_three.py # |____module_two.py # |____module_one.py from pathlib import Path, PurePath modules = list(Path(__file__).parent.glob("*.py")) print([PurePath(f).stem for f in modules if f.is_file() and not f.name == "__init__.py"]) # ['module_one', 'module_two', 'module_three']
最后一个我重要的属性是的是__debug__。它可以用于调试,但更具体地说,它可以用于更好地控制断言:
# example.py def func(): if __debug__: print("debugging logs") # Do stuff... func()
如果我们使用python example.py正常运行这段代码,我们将看到打印出“调试日志”,但是如果我们使用python -O example.py,优化标志(-O)将把__debug__设置为false并删除调试消息。因此,如果在生产环境中使用-O运行代码,就不必担心调试过程中被遗忘的打印调用,因为它们都不会显示。
我们可以创建自己的方法和属性吗?是的,你可以,但你不应该这么做。
双下划线名称是为Python语言的未来扩展保留的,不应该用于自己的代码。如果你决定在你的代码中使用这样的名称,那么将来如果它们被添加到Python解释器中,这就与你的代码不兼容了。所以对于这些方法,我们只要记住和使用就好了。
위 내용은 Python의 매직 메소드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!