引入
众所周知,方法是需要调用执行的,而魔法方法则不一样,他无需你的调用,在特定的时候会自己执行, 例如我们之前所学的__init__
, 在示例对象 ([类名]+()) 的时候触发执行它
1.什么是内置方法
- 定义在类的内部, 以双下滑线开头
__
, 以双下滑线__
结尾的方法 - 特点 : 在某种情况下自动触发执行
2.为什么使用内置方法
- 为了高度定制化我们使用的类或者实例
一.点拦截方法__getattr__
,__setattr__
,__delattr__
__setattr__
: 在 [对象].[属性] = [值] 设置属性值的时候就会触发它的执行__getattr__
: 在 [对象].[属性] 获取属性不存在的时候会触发它的执行__delattr__
: 在 del [对象].[属性] 删除属性的时候会触发它的执行
class Panda:
def __init__(self, name):
self.name = name
def __getattr__(self, item):
print('通过点的方式找属性没有找到, 于是触发了我')
def __setattr__(self, key, value):
print('你通过点的方式修改属性值,触发了我')
# 📛self.key=value
# 📛如此设置值得时候(又通过点的方式设置), 于是又触发__setattr__的执行, 又开始设置,无限递归了
self.__dict__[key]=value # 可以通过操作对象字典 key = value 方式设置
def __delattr__(self, item):
print('通过点的方式删除属性,触发了我')
# 📛del self.item # 与上面一样, 无限递归了
self.__dict__.pop(item) # 同样通过操作对象字典的方式去删属性
# 🔰实例对象的初始化方法其实也进行了设置属性值的操作
P = Panda("hangd") # 你通过点的方式修改属性值,触发了我
# 🔰获取一个不存在的属性
P.age # 通过点的方式找属性没有找到, 于是触发了我
# 🔰设置属性值
P.age = 99 # 你通过点的方式修改属性值,触发了我
print(P.__dict__) # {'name': 'hangd', 'age': 99}
# 🔰删除属性
del P.name # 通过点的方式删除属性,触发了我
print(P.__dict__) # {'age': 99}
- 应用小示例 : 重写字典, 实现字典可以通过点
.
取值赋值, 也可以[]
取值赋值
class MyDict(dict):
def __setattr__(self, key, value):
self[key] = value
def __getattr__(self, item):
return self[item]
DD = MyDict()
print(DD) # {}
# 🔰点取值赋值
DD.name = "shawn"
print(DD.name) # shawn
print(DD) # {'name': 'shawn'}
# 🔰[] 取值赋值
DD["age"] = 20
print(DD["age"]) # 20
print(DD) # {'name': 'shawn', 'age': 20}
二. __getattribute__
1. 先看看 : __getattr__
点 . 属性
没找到触发
class Bar:
def __getattr__(self, item):
print("没找到,触发了我")
bb = Bar()
bb.name # 没找到,触发了我
2.__getattribute__
点 . 属性
无论找没找到都触发
class Bar:
def __init__(self,name):
self.name = name
def __getattribute__(self, item):
print(f"无论找没找到,都触发了我-->{item}")
bb = Bar("shawn")
bb.name # 无论找没找到,都触发了我-->name
bb.age # 无论找没找到,都触发了我-->age
3.两者同时存在
🍔两者同时存在
class Bar:
def __init__(self,name):
self.name = name
def __getattr__(self, item):
print("没找到,触发了我")
def __getattribute__(self, item):
print(f"无论找没找到,都触发了我-->{item}")
bb = Bar("shawn")
bb.name # 无论找没找到,都触发了我-->name
bb.age # 无论找没找到,都触发了我-->age
🍔设置异常
class Bar:
def __init__(self,name):
self.name = name
def __getattr__(self, item):
print("没找到,触发了我")
def __getattribute__(self, item):
print(f"无论找没找到,都触发了我-->{item}")
raise AttributeError('让小弟接管') # 设置异常,直接交给__getattr__
bb = Bar("shawn")
bb.name
'''
无论找没找到,都触发了我-->name
没找到,触发了我
'''
bb.age
'''
无论找没找到,都触发了我-->age
没找到,触发了我
'''
[对象] . [属性]
的调用顺序 : 先执行 __getattribute__—>去类的名称空间找—>__getattr__(本质是去对象自己的名称空间找)[对象] . [属性]
的查找顺序 : 对象自己**—>类—>父类—>**父类
4.总结
__getattribute__
方法优先级比__getattr__
高- 没有重写
__getattribute__
的情况下, 默认使用的是父类的__getattribute__
方法 - 只有在使用默认
__getattribute__
方法中找不到对应的属性时,才会调用__getattr__
- 如果是对不存在的属性做处理,尽量把逻辑写在
__getattr__
方法中 - 如果非得重写
__getattribute__
方法,需要注意两点:- 第一是避免.操作带来的死循环
- 第二是不要遗忘父类的
__getattribute__
方法在子类中起的作用
三.item系列 __getitem__
,__setitem__
,__delitem__
-
__getitem__
: 通过中括号取值, 触发它的执行 -
__setitem__
: 通过中括号赋值, 触发它的执行 -
__delitem__
: 通过中括号删值, 触发它的执行
class Person:
def __getitem__(self, item):
print('取值操作,触发了执行')
def __setitem__(self, key, value):
print('赋值操作,触发了执行')
def __delitem__(self, key):
print('删值操作,触发了执行')
p=Person()
p['name'] = "派小星" # 赋值操作,触发了执行
p['name'] # 取值操作,触发了执行
del p["name"] # 删值操作,触发了执行
- 小示例 : 在不继承字典类的情况下, 自定义一个字典, 支持
[ ]
取值赋值
class Mydict:
def __init__(self,**kwargs):
# 🔰方案一
# for key,value in kwargs.items():
# setattr(self,key,value)
# 🔰方案三
# for key,value in kwargs.items():
# self.__dict__[key]=value
# 🔰方案二
self.__dict__.update(kwargs)
def __getitem__(self, item):
# 🔰方案一
# return self.__dict__[item]
# 🔰方案二
return getattr(self,item)
def __setitem__(self, key, value):
# 🔰方案一
# setattr(self,key,value)
# 🔰方案二
self.__dict__[key]=value
def __delitem__(self, key):
del self.__dict__[key]
dd = Mydict()
print(dd.__dict__) # {}
dd["name"] = "shawn"
print(dd.__dict__) # shawn
print(dd["name"]) # shawn
四. __format__
自定义格式化字符串
1.format( )
函数
前面 字符串类型的内置方法 已经详细的介绍了 format( )
函数的玩法, 下面简单回顾一下
🍉直接传变量名
res="my name is {name} my age is {age}".format(age=18,name="shawn")
print(res) #my name is shawn my age is 18
🍉不放任何值, 让其按位置自动传值
res="my name is {} my age is {}".format(18,"shawn")
print(res) #my name is 18 my age is shawn
🍉通过索引传值
res="my name is {0}{0}{0} my age is {1}{0}".format(18,"shawn")
print(res) #my name is 181818 my age is shawn18
2.__format__
方法
- 其实我们使用
format( )
函数的时候触发的就是__format__
方法
class For:
def __format__(self, format_spec):
print(type(format_spec)) # 看看这个参数是什么类型
print(f"使用format()函数,触发了我-->({format_spec})")
return "aaa" # 必须返回一个字符串(不然报错),可以是任意的字符串(后面可以使用此特性来操作自定义)
F = For()
🔰当第二个参数不传入时默认为空
format(F)
'''输出
<class 'str'>
使用format()函数,触发了我-->()
'''
🔰传参类型 "str"
format(F,"你好")
'''输出
<class 'str'>
使用format()函数,触发了我-->(你好)
3.小示例 : 制作一个输出日期类, 实例的时候传入年,月,日, 可以自定义格式
class Date:
__format_dic = {
"1": "{obj.year}-{obj.mon}-{obj.day}",
"2": "{obj.year}:{obj.mon}:{obj.day}",
"3": "{obj.year}/{obj.mon}/{obj.day}"
}
def __init__(self,Year,Mon,Day):
self