Python中的魔法方法(python中魔术方法)
off999 2024-11-15 23:07 18 浏览 0 评论
python中的魔法方法是一些可以让你对类添加“魔法”的特殊方法,它们经常是两个下划线包围来命名的
Python的魔法方法,也称为dunder(双下划线)方法。大多数的时候,我们将它们用于简单的事情,例如构造函数(__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__方法,这个方法是迭代器上的内置方法(不是生成器),正如你上面看到的那样,并且还支持动态长度更改。但是,正如他的名字那样,这只是一个提示(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解释器中,这就与你的代码不兼容了。所以对于这些方法,我们只要记住和使用就好了。
作者:Martin Heinz
相关推荐
- windows11办公软件(windows11的office)
-
1、首先点击电脑底部的Windows图标2、进入开始页面点击office套件中的任意应用,如Word3、页面弹出登录按钮,和创建按钮,如已有账号,点击登录4、接着进入激活office页面,输入offi...
-
- 7z文件怎么打开(7z文件改什么后缀才能打开)
-
7z是一种压缩格式,和我们在Windows平台上看到的rar类似;7z原本是7-zip开源的压缩文件所支持的压缩格式,目前大多数压缩软件也支持打开,如若安装了第三方压缩软件双击即可打开7z。7z文件的打开方式介绍如下: 1、扩展名为...
-
2025-11-18 02:03 off999
- 重装系统后连不上网(重装系统后无法连网)
-
1、首先,在安装系统完成之后,界面右下角网络连接显示不可用?我们需要通过,系统本身自带的诊断系统进行诊断,以避免是否是物理原因造成的网络不能正常链接。2、其次,检测结果如果是显示的未能安装网络适配器,...
- 破解wifi密码有什么办法(破解wifi密码有什么办法幻影)
-
破解路由器admin管理员密码方法:只能间接的破解。 方法一、1、打开浏览器---输入192.168.1.1(一般路由器地址是这个或者查看路由器背面的登录信息)进路由---输入用户名,密码...
- 电脑截屏如何截屏(电脑截屏截屏用哪个键)
-
1、直接点击键盘上的PrtScSysRq,即可截图,然后直接在聊天框或者图画中“Ctrl+V”粘贴就行了。2、登录QQ以后,点击快捷键“Ctrl+Alt+A”即可截图,框选过后,直接“...
- 迅捷路由器登录(yr1900g路由器登录入口)
-
入口如下:1.打开网页后输入192.168.1.1或tplogin.cn。2.第一次登录路由器或恢复出厂设置后再次设置,按提示设置好管理员密码、上网参数、wifi名称和密码。3.再次进入登录页面中,输...
- pdf格式怎么编辑(怎么创建pdf格式的文件)
-
1、电脑打开PDF文件。2、电脑打开PDF文件后,点击工具栏中的编辑。3、进入编辑页面后,可以点击文字,对pdf文件进行编辑。4、点击裁剪页面选项,就可以对PDF文件中的页面大小进行裁剪。5、PDF文...
- 电脑显示器不亮(电脑显示器不亮了)
-
多种原因:1、检查电脑主机与显示器之间的连接是否松动、损坏,显示器是否正常。2、这是最常见的故障,内存条接触不良导致显示器无信号。解决办法:断电/拔出内存条,用橡皮擦将金手指擦亮再装回去即可。3、显卡...
- 电脑频繁总自动关机(电脑经常性自动关机)
-
电脑总是自动关机原因如下 1、原因一:设置的问题 有的用户会在电脑上安装管家类软件,这些软件里会有一些设置预定时间关机的功能,比如设置为17:00关机,那么到了下午5点后它就会自动关机,一般检查一...
- 邮箱注册百度账号(邮箱注册百度帐号)
-
要使用邮箱注册天翼云盘,首先需要打开天翼云盘的官方网站。在注册页面中,选择使用邮箱注册并输入您的邮箱地址。然后,按照提示填写您的个人信息,包括用户名、密码等等。最后,点击注册按钮,等待验证邮件的发送。...
- 台式电脑截屏键快捷方式(台式电脑的截图快捷键在哪)
-
方法/步骤1第一个办法自然是我们最常见最简单的,使用“PrintScreen”键截图了。点击“PrintScreen”键,我们就可以直接截取全部屏幕,找个对话框或者文字区域粘贴就好了。我截的图是这样的...
- cad2014密钥001f1不对(cad2014密钥001f1无效)
-
Excel中序号要想输成001,我们可以进行如下的操作,我们先将所有输入序号的这一列全部选定,也就是点击英文字母这一列就可以全部选定了,然后我们在这个选定的区域的状拍下去,点击鼠标右键,再点击数值,再...
- xp强行删除管理员开机密码(windowsxp强行删除开机密码)
-
要清除WindowsXP开机密码,首先需要进入安全模式,然后进入控制面板,选择用户账户设置,再选择删除密码或更改密码选项,输入当前密码,然后将密码字段留空即可清除密码。如果忘记了密码,可以使用软件工...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
python入门到脱坑 输入与输出—str()函数
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
宝塔面板如何添加免费waf防火墙?(宝塔面板开启https)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
慕ke 前端工程师2024「完整」
-
失业程序员复习python笔记——条件与循环
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
