Python中的魔法方法

news2024/9/23 17:23:07

python中的魔法方法是一些可以让你对类添加“魔法”的特殊方法,它们经常是两个下划线包围来命名的

Python的魔法方法,也称为dunder(双下划线)方法。大多数的时候,我们将它们用于简单的事情,例如构造函数(init)、字符串表示(strrepr)或算术运算符(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__方法,这个方法是迭代器上的内置方法(不是生成器),正如你上面看到的那样,并且还支持动态长度更改。但是,正如他的名字那样,这只是一个提示(hint),并不能保证完全准确:对于列表迭代器,可以得到准确的结果,但是对于其他迭代器则不确定。但是即使它不准确,它也可以帮我们获得需要的信息,正如PEP 424中解释的那样

length_hint must return an integer (else a TypeError is raised) or NotImplemented, and is not required to be accurate. It may return a value that is either larger or smaller than the actual size of the container. A return value of NotImplemented indicates that there is no finite length estimate. It may not return a negative value (else a ValueError is raised).

元编程

大部分很少看到的神奇方法都与元编程有关,虽然元编程可能不是我们每天都需要使用的东西,但有一些方便的技巧可以使用它。

一个这样的技巧是使用__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

上面的代码我们向基类添加关键字参数,该参数可以在定义子类时设置。在实际用例中可能会在想要处理提供的参数而不仅仅是赋值给属性的情况下使用此方法。

看起来非常晦涩并且很少会用到,但其实你可能已经遇到过很多次了,因为它一般都是在构建API时使用的,例如在SQLAlchemy或Flask Views中都使用到了。

另一个元类的神奇方法是__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")

Singleton类拥有一个私有__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(…),而是调用Document.new(Document),它创建一个裸实例,而不调用__init__。因此,实例的属性(在本例中为text)没有初始化,所欲我们需要额外使用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)定义string的自定义实现。但是我们并不想重新实现每一个字符串方法,比如split、join、capitalize等等。这里我们就可以使用__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解释器中,这就与你的代码不兼容了。所以对于这些方法,我们只要记住和使用就好了。

https://avoid.overfit.cn/post/6a5057b4833b4f188d8c850385cfcbca

作者:Martin Heinz

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/98528.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机毕设Python+Vue兴发农家乐服务管理系统(程序+LW+部署)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

Linux | 套接字(socket)编程 | UDP协议讲解

文章目录TCP与UDP的区别网络字节序套接字接口介绍sockaddr结构服务端UDP套接字设置客户端UDP套接字设置TCP与UDP的区别 TCPUDP传输层协议传输层协议有连接无连接可靠连接不可靠连接面向字节流面向数据报 首先,网络通信模型是分层的,模型的每一层都有属于…

深入jvm字节码

深入jvm字节码1.深入剖析class文件结构1.1初探class文件1.2 class文件结构解析1.2.1 魔数1.2.2 版本号1.2.3 常量池1.2.4 Access flags1.2.5 this_class,super_name,interfaces1.2.6 字段表1.2.7 方法表1.2.8 属性表1.3使用javap查看类文件2.字节码基础2.1字节码概述2.2java虚拟…

一文看懂---B树及其简单实现

目录 1.B树的引入 2.B树的概念 3.B树是如何插入的? 4.具体的代码实现 1.B树的引入 在以往我们在内存中搜索数据时,可以使用红黑树,平衡树,哈希表等数据结构,但是当数据量比较大,不能一次放进内存&…

[附源码]计算机毕业设计Python仓储综合管理系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等…

C++PrimerPlus 第七章 函数-C++的编程模块-7.9 递归

目录 7.9 递归 7.9.1 包含一个递归调用的递归 7.9.2 包含多个递归调用的递归 7.9 递归 下面介绍一些完全不同的内容。C函数有一种有趣的特点——可以调用自己(然而,与C语言不同的是,C不允许main()调用自己),这种功能…

SpringCloud Gateway简单使用

前言 SpringCloud Gateway是一个网关框架,也是现在流行的的一个网关框架,它包括了过滤器、限流、权限、基本路由、整合Eureka 断言predicates 等功能,也会介绍和zuul这个框架的一个对比, Spring Cloud 生态系统中的网关&#xff…

243. 一个简单的整数问题2——差分+树状数组

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一: C l r d,表示把 A[l],A[l1],…,A[r] 都加上 d。 Q l r,表示询问数列中第 l∼r 个数的和。 对于每个询问,输出一个整数表示答案。 …

《爱与自由》豆瓣9.3优秀父母的必读书

《爰和自由》 关于作者 孙瑞雪,中国著名的幼儿教育家与心理学专家,"爱和自由、规则和平等”教育精神的 发起者和倡导者,中国系统引进实施国际蒙特梭利教育第一人,成功实践了科学教育法的本土化。她发展和延伸了蒙特梭利敏感…

Oh My Posh美化CMD、Anaconda Prompt解决方案

网上搜到的Oh My Posh安装配置都是针对power shell的(我参考这篇成功配置了针对power shell的字体和主题)。期间遇到了无法加载文件WindowsPowerShell\profile.ps1的问题,参考这篇解决。由于平时我用Anaconda比较多,而anaconda是基…

基于ARMR和白噪声特性模型及风速威布尔分布研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

PRISEMI芯导产品推荐 | 支持路径管理功能的3A单节锂离子电池充电IC——PSC2965

PRISEMI芯导产品推荐 | 支持路径管理功能的3A单节锂离子电池充电IC——PSC2965 随着便携式电子设备功能越来越多样化和整机性能的不断提升,整机功耗也在面临越来越大的挑战。最直接有效的方式就是提高电池的容量来提高整机的使用时长。为了不降低用户体验&#xff0…

C# 绘图基础

一 GDI技术简介 ① GDI:Graphics Device Interface. ② GDI:GDI的改进; ③ 是.NET框架结构的重要组成部分; ④ 和GDI一样它提供对二维图形图像的支持; 二 .NET 对GDI的封装 三 坐标系统 GDI的坐标系统; …

计算机毕业设计——简单的网页设计

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置,有div的样式格局,这个实例比较全面,有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

基于粒子群优化算法的分布式电源优化调度实现配电网稳定运行(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

WAYON维安提供新产品:新起点,新征程,DCDC炼成之路

WAYON维安提供新产品:新起点,新征程,DCDC炼成之路 新起点,新征程,DCDC炼成之路 随着新能源汽车、5G通信、工业4.0以及人工智能的快速发展,电源管理芯片的应用场景越来越丰富。同时传统行业,如网…

Day834.Dubbo如何用管程实现异步转同步 -Java 并发编程实战

Dubbo如何用管程实现异步转同步 Hi,我是阿昌,今天学习记录的是关于Dubbo如何用管程实现异步转同步的内容。 在很多并发场景下,支持多个条件变量能够让并发程序可读性更好,实现起来也更容易。例如,实现一个阻塞队列&a…

星火计划学习笔记——第八讲Apollo控制模块解析与实践2

文章目录1. Apollo控制框架介绍1.1 控制模块的功能和性能要求1.2 控制模块的总体框架1.3 控制模块的代码结构1.3.1 control -> common 中的主要程序1.3.2 control -> conf 中的主要程序1.3.3 control -> controller 中的主要程序1.3.4 control -> proto 中的主要程…

Android 各镜像文件img详解

Android编译后生成文件,在out/target/product/lime下: cache.img、cust.img、metadata.img、misc.img(本地无)、recovery.img、super.img、userdata.img、vbmeta.img、vbmeta_system.img(仅测试适配工作,而…

Python处理Excel比Vba快100倍,媳妇连连夸赞今晚不用再跪搓衣板----python实战

最近经历了一次把vb脚本改造成python脚本,并获得性能提升数倍的过程,当然,这个过程也不是一帆风顺,中间也经历了一些波折,但是,也收获了一波新的认知。正好最近有时间,姑且写下来记录一下。 什…