>  기사  >  백엔드 개발  >  Python의 매직 메소드

Python의 매직 메소드

WBOY
WBOY앞으로
2023-04-13 10:25:061352검색

Python의 Magic 메소드는 클래스에 "magic"을 추가할 수 있는 특수 메소드입니다. 종종 두 개의 밑줄로 둘러싸여 이름이 지정됩니다.

Python의 매직 메소드

Dunder(이중 밑줄) 방법으로도 알려진 Python의 마법 방법입니다. 대부분의 경우 생성자(init), 문자열 표현(str, repr) 또는 산술 연산자(add/mul)와 같은 간단한 작업에 이를 사용합니다. 사실, 들어보지 못했을 수도 있지만 매우 유용한 메서드가 많이 있습니다. 이 기사에서는 이러한 마법의 메서드를 정리하겠습니다.

Iterator size

우리 모두는 __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
위의 코드에서는 하위 클래스를 정의할 때 설정할 수 있는 기본 클래스에 키워드 인수를 추가합니다. . 실제 사용 사례는 단순히 속성에 할당하는 것이 아니라 제공된 매개변수를 처리하려는 경우 이 메서드를 사용하는 것입니다.

매우 모호하고 거의 사용되지 않는 것처럼 보이지만 실제로는 SQLAlchemy 또는 Flask Views와 같은 API를 구축할 때 일반적으로 사용되기 때문에 여러 번 접했을 수도 있습니다.

또 다른 메타클래스 매직 메소드는 __call__입니다. 이 메소드를 사용하면 클래스 인스턴스가 호출될 때 발생하는 일을 사용자 정의할 수 있습니다.

 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 d

from_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__는 위임되지 않습니다. 따라서 우리가 그것들도 작동하도록 하려면 그것들을 다시 구현해야 합니다.

自省(introspection)

最后一个与元编程相关的方法是__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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 51cto.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제