Les méthodes magiques en Python sont des méthodes spéciales qui permettent d'ajouter de la « magie » à une classe. Elles sont souvent nommées entourées de deux traits de soulignement.
Méthode magique de Python, également connue sous le nom de méthode dunder (double soulignement). La plupart du temps, nous les utilisons pour des choses simples comme les constructeurs (init), les représentations sous forme de chaîne (str, repr) ou les opérateurs arithmétiques (add/mul). En fait, il existe de nombreuses méthodes dont vous n'avez peut-être pas entendu parler mais qui sont très utiles. Dans cet article, nous allons trier ces méthodes magiques
Nous connaissons tous la méthode __len__, vous pouvez ! utilisez-le pour implémenter la fonction len() sur la classe conteneur. Mais que se passe-t-il si vous souhaitez obtenir la longueur d'un objet de classe qui implémente un itérateur ?
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
Tout ce que vous avez à faire est d'implémenter la méthode __length_hint__, qui est une méthode intégrée aux itérateurs (et non aux générateurs), comme indiqué ci-dessus. vu, et prend également en charge les changements de longueur dynamiques. Cependant, comme son nom l'indique, il ne s'agit que d'un indice dont l'exactitude n'est pas garantie : pour les itérateurs de liste, vous pouvez obtenir des résultats précis, mais pour les autres itérateurs, ce n'est pas sûr. Mais même si ce n'est pas exact, cela peut nous aider à obtenir les informations dont nous avons besoin, comme expliqué dans le PEP 424.
Dans le code ci-dessus, nous ajoutons un argument mot-clé à la classe de base qui peut être défini lors de la définition du sous-classe. Un cas d'utilisation réel pourrait consister à utiliser cette méthode lorsque vous souhaitez gérer les paramètres fournis plutôt que de simplement les attribuer à une propriété.length_hint doit renvoyer un entier (sinon une TypeError est levée) ou NotImplemented, et n'a pas besoin d'être précis. Il peut renvoyer une valeur supérieure ou inférieure à la taille réelle du conteneur. de NotImplemented indique qu'il n'y a pas d'estimation de longueur finie. Il peut ne pas renvoyer une valeur négative (sinon une ValueError est levée). Bien que la métaprogrammation ne soit peut-être pas quelque chose que nous devons utiliser tous les jours, il existe quelques astuces pratiques pour l'utiliser. Une de ces astuces consiste à utiliser __init_subclass__ comme raccourci pour étendre les fonctionnalités d'une classe de base sans avoir à gérer les métaclasses :
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"): passCopier après la connexion
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")
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")
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
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'
最后一个与元编程相关的方法是__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解释器中,这就与你的代码不兼容了。所以对于这些方法,我们只要记住和使用就好了。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!