Heim >Backend-Entwicklung >Python-Tutorial >Magische Methoden in Python

Magische Methoden in Python

WBOY
WBOYnach vorne
2023-04-13 10:25:061430Durchsuche

Magische Methoden in Python sind spezielle Methoden, mit denen Sie einer Klasse „Magie“ hinzufügen können. Sie werden oft von zwei Unterstrichen umgeben benannt.

Magische Methoden in Python

Pythons magische Methode, auch bekannt als Dunder-Methode (doppelte Unterstreichung). Meistens verwenden wir sie für einfache Dinge wie Konstruktoren (init), String-Darstellungen (str, repr) oder arithmetische Operatoren (add/mul). Tatsächlich gibt es viele Methoden, von denen Sie vielleicht noch nie gehört haben, die aber sehr nützlich sind. In diesem Artikel werden wir diese magischen Methoden klären.

Die Größe von Iteratoren

Wir alle kennen die __len__-Methode Verwenden Sie es, um die Funktion len() für die Containerklasse zu implementieren. Aber was ist, wenn Sie die Länge eines Klassenobjekts erhalten möchten, das einen Iterator implementiert?

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

Alles, was Sie tun müssen, ist die Methode __length_hint__ zu implementieren, die eine integrierte Methode für Iteratoren (nicht Generatoren) ist, wie oben beschrieben sichtbar und unterstützt auch dynamische Längenänderungen. Allerdings handelt es sich, wie der Name schon sagt, nur um einen Hinweis, dessen völlige Genauigkeit nicht garantiert werden kann: Bei Listeniteratoren können Sie genaue Ergebnisse erhalten, bei anderen Iteratoren ist dies jedoch nicht sicher. Aber selbst wenn es nicht korrekt ist, kann es uns helfen, die benötigten Informationen zu erhalten, wie in PEP 424 erläutert.

length_hint muss eine Ganzzahl (sonst wird ein TypeError ausgelöst) oder NotImplemented zurückgeben und muss nicht genau sein. Es kann einen Wert zurückgeben, der entweder größer oder kleiner als die tatsächliche Größe des Containers ist von NotImplemented weist darauf hin, dass es keine endliche Längenschätzung gibt (andernfalls wird ein ValueError ausgelöst). Obwohl Metaprogrammierung möglicherweise nicht jeden Tag verwendet werden muss, gibt es einige praktische Tricks, um sie zu verwenden. Ein solcher Trick besteht darin, __init_subclass__ als Verknüpfung zu verwenden, um die Funktionalität einer Basisklasse zu erweitern, ohne sich mit Metaklassen befassen zu müssen:

 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
Mit dem obigen Code fügen wir der Basisklasse ein Schlüsselwortargument hinzu, das beim Definieren der Unterklasse festgelegt werden kann . Ein echter Anwendungsfall könnte die Verwendung dieser Methode sein, wenn Sie die bereitgestellten Parameter verarbeiten möchten, anstatt sie nur einer Eigenschaft zuzuweisen.

Es scheint sehr undurchsichtig zu sein und wird selten verwendet, aber tatsächlich sind Sie vielleicht schon oft darauf gestoßen, da es im Allgemeinen beim Erstellen von APIs verwendet wird, beispielsweise in SQLAlchemy oder Flask Views.

Eine weitere magische Methode der Metaklasse ist __call__. Diese Methode ermöglicht die Anpassung dessen, was passiert, wenn eine Klasseninstanz aufgerufen wird:

 class CallableClass:
def __call__(self, *args, **kwargs):
print("I was called!")
 
 instance = CallableClass()
 
 instance()
 # I was called!

Sie können damit eine Klasse erstellen, die nicht aufgerufen werden kann:

 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

Für Klassen mit nur statischen Methoden wird diese Methode verwendet, ohne eine Instanz davon zu erstellen Klasse.

Ein weiteres ähnliches Szenario ist das Singleton-Muster – eine Klasse kann höchstens eine Instanz haben:

 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")

Singleton-Klasse hat eine private __instanz – wenn nicht, wird sie erstellt und zugewiesen, wenn sie bereits existiert, wird sie nur zurückgegeben .

Angenommen, es gibt eine Klasse und Sie möchten eine Instanz davon erstellen, ohne __init__ aufzurufen. Die Methode __new__ kann dabei helfen:

 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")

In einigen Fällen müssen wir möglicherweise den üblichen Prozess der Erstellung einer Instanz umgehen. Der obige Code zeigt, wie das geht. Anstatt Document(…) aufzurufen, rufen wir Document.__new__(Document) auf, wodurch eine bloße Instanz erstellt wird, ohne __init__ aufzurufen. Daher wird das Attribut der Instanz (in diesem Fall Text) nicht initialisiert, daher müssen wir zusätzlich die Funktion setattr verwenden, um den Wert zuzuweisen (es ist auch eine magische Methode __setattr__).

Warum machst du das? Weil wir möglicherweise den Konstruktor ersetzen möchten, wie zum Beispiel:

 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

Hier wird die from_file-Methode definiert, die als Konstruktor fungiert und zunächst die Instanz mit __new__ erstellt und sie dann konfiguriert, ohne __init__ aufzurufen.

Die nächste magische Methode im Zusammenhang mit Metaprogrammierung ist __getattr__. Diese Methode wird aufgerufen, wenn der normale Zugriff auf Eigenschaften fehlschlägt. Dies kann verwendet werden, um Zugriffe/Aufrufe auf fehlende Methoden an eine andere Klasse zu delegieren:

 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'

Wir möchten der Klasse einige zusätzliche Funktionen hinzufügen (wie „custom_operation“ oben), um eine benutzerdefinierte Implementierung von String zu definieren. Aber wir wollen nicht jede String-Methode wie Split, Join, Capitalize usw. neu implementieren. Hier können wir __getattr__ verwenden, um diese vorhandenen String-Methoden aufzurufen.

Während dies für normale Methoden funktioniert, beachten Sie, dass im obigen Beispiel die magische Methode __add__ (die Operationen wie Verbindungen bereitstellt) nicht delegiert wird. Wenn wir also wollen, dass sie auch funktionieren, müssen wir sie neu implementieren.

自省(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解释器中,这就与你的代码不兼容了。所以对于这些方法,我们只要记住和使用就好了。

Das obige ist der detaillierte Inhalt vonMagische Methoden in Python. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:51cto.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen