共计 21033 个字符,预计需要花费 53 分钟才能阅读完成。
引入
众所周知,方法是需要调用执行的,而魔法方法则不一样,他无需你的调用,在特定的时候会自己执行, 例如我们之前所学的__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.year = Year
self.mon = Mon
self.day = Day
def __format__(self, format_spec):
if not format_spec or not format_spec in self.__format_dic: # 如果格式编号为空或者不在自定义字典里面
f = self.__format_dic["1"] # 那我们就给一个默认的格式
else:
f = self.__format_dic[format_spec] # 否则就使用你传入的格式
return f.format(obj=self) # 使用 "obj=self" 赋值的方法避免递归调用
mm = Date(2020,12,28)
print(format(mm,"1")) # 2020-12-28
print(format(mm,"2")) # 2020:12:28
print(format(mm,"3")) # 2020/12/28
print(format(mm,)) # 2020-12-28 (空, 选择默认)
print(format(mm,"3iop")) # 2020-12-28 (不存在, 选择默认)
五. 析构方法 __del__
前面我们学了 __init__
这个构造方法, 在实例对象的时候自动触发, 也叫初始化, 而析构方式是在执行 del
对象的时候, 也就是在 对象被清理之前 自动触发 __del__
的执行, 那么我们就可以在这方法里面进行一些操作
class Test:
def __del__(self):
print(" 删除对象, 触发了我 ")
T = Test()
del T # 删除对象, 触发了我
1.__del__的使用场景
- 一般用来进行回收系统资源的操作
- 如果一个对象仅仅占用应用程序 (用户级) 的资源时, 删除对象时, 垃圾回收机制会自动回收该对象所占用的应用程序资源, 即用户态内存
class A:
def __del__(self):
print("--- 触发 del---")
a = A()
del a
print("--- 程序结尾 ---")
''' 输出
--- 触发 del---
--- 程序结尾 ---
'''
- 当一个程序运行完毕时, 也会自动触发 GC, 进而 GC 触发
__del__
class A:
def __del__(self):
print("--- 触发 del---")
a = A()
print("--- 程序结尾 ---")
''' 输出
--- 程序结尾 ---
--- 触发 del---
'''
- 当对象涉及到申请了操作系统的资源, 比如 open 打开了文件, 或者与网络连接等, GC 无法派上用场, 那么就需要我们重写一下
__del__
的功能
🔰重写示例 : 对象中打开了一个文件
class Open:
def __init__(self):
self.f = open("test26.py","rt",encoding="utf-8") # 打开了一个文件
def __del__(self):
self.f.close() # 触发执行的时候, 关闭文件(系统资源的回收)
print(" 删除对象之前我关闭了文件, 哈哈 ")
O = Open()
del O # 删除对象之前我关闭了文件, 哈哈
六. 注释文档 __doc__
- 返回类的注释信息
class Test:
'''
这是用来测试__doc__的注释信息
信息信息
'''
...
T = Test()
print(T.__doc__)
''' 输出
这是用来测试__doc__的注释信息
信息信息
'''
- 该属性无法被继承
class Foo:
'我是描述信息'
pass
class Bar(Foo):
pass
F = Foo()
print(F.__doc__) # 我是描述信息
B = Bar()
print(B.__doc__) # None
七. 描述符 __get__, __set__, __delete__
1. 什么是描述符
-
描述符的本质就是一个新式类, 在这个新式类中至少实现了
__get__()
,__set__()
,__delete__()
中的一个就称为 描述符 , 也被称为 描述符协议 -
__get__(self,inatance,owener)
: 当访问一个属性的时候触发 __set__(self,instance,value)
: 为一个属性赋值时触发__delete__(self,instance)
: 使用 del 删除一个属性的时候触发
self | 描述符的对象 |
---|---|
instance | 使用描述符的对象 |
owner | 设置了描述符的类(也就是 instance 的类) |
value | instance 的值 |
- 定义一个描述符
class MyDecriptor:
def __get__(self, instance, owner):
print('触发 get')
def __set__(self, instance, value):
print('触发 set')
def __delete__(self, instance):
print('触发 delete')
🔰以下方法是描述符的实例进行的操作, 并不会触发上面三种方法, 不要搞混了
f1=MyDecriptor()
f1.name='shawn'
print(f1.name)
del f1.name
- 设置简单代理, 定义成另一个类的类属性
🌙描述符一
class Str:
def __get__(self, instance, owner):
print('---> 触发 Str_get')
def __set__(self, instance, value):
print('---> 触发 Str_set')
def __delete__(self, instance):
print('---> 触发 Str_Delete')
🌙描述符二
class Int:
def __get__(self, instance, owner):
print('---> 触发 Int_get')
def __set__(self, instance, value):
print('---> 触发 Int_set')
def __delete__(self, instance):
print('---> 触发 Int_Delete')
🌙普通类
class Person:
name = Str() # 将 name 给 Str 代理 (也可以说 name 是描述符对象)
age = Int() # 将 age 给 Int 代理 (也可以说 age 是描述符对象)
def __init__(self, name, age):
self.name = name # 这里赋值就会触发描述符__set__方法
self.age = age
⭐实例对象
P1 = Person(" 派大星 ",22)
# ---> 触发 Str_set
# ---> 触发 Int_set
⭐查找属性
print(P1.name)
# ---> 触发 Str_get
# None
⭐查找属性
print(P1.age)
# ---> 触发 Int_get
# None
⭐设置属性
P1.name = " 海绵宝宝 "
# ---> 触发 Str_set
⭐查找属性
print(P1.name)
# ---> 触发 Str_get
# None
- 查看类与对象属性字典
print(P1.__dict__) # {} 为空
print(Person.__dict__)
'''从中取出了两个(name,age)'name': <__main__.Str object at 0x00000267479E7AC8>,'age': <__main__.Int object at 0x00000267479E7B08>'''
- 小结
- 只要类属性被描述符类代理了, 以后使用 [对象] . [属性], 就会触发描述符类的__set__, __get__, __delete__方法
- 并且被代理的属性只在类名称空间有, 对象名称空间就没有该属性了
2. 描述符是做什么的
描述符的作用是用来代理另外一个类的属性的, 并且必须把描述符定义成这个类的类属性,不能定义到构造函数中
class Str:
def __get__(self):
pass
class Duck:
name = Str() # 正确 : name 被 Str 代理
def __init__(self):
self.name = Str() # 错误 : 只能代理类属性, 不能代理对象属性
- 代理属性小 示例 : 代理属性, 并限制传入数据的类型
🌙数据描述符
class Str:
def __init__(self, agencyName, agencyType):
'''
:param agencyName: 被代理的属性名字
:param agencyType: 被代理的属性类型
'''
self.name = agencyName
self.type = agencyType
# 将属性存在描述符的 __dict__ 里面, 也可也直接放到 "P1" 对象属性字典中去, 看自己需求
self.date_dict = self.__dict__
def __get__(self, instance, owner):
return self.date_dict[instance]
def __set__(self, instance, value):
# 在这里可以进行属性类型的判断
if isinstance(value, self.type):
self.date_dict[instance] = value
else:
print(" 类型不正确 ")
def __delete__(self, instance):
del self.date_dict[instance]
🌙普通类
class Panda:
name = Str("name", str) # 将描述符实例当做属性保存
age = Str("age", int)
def __init__(self, name, age):
self.name = name
self.age = age
P1 = Panda(" 派大星 ", 18)
print(P1.__dict__) # {} 空的, 因为全被代理了
print(P1.name) # 派大星
print(P1.age) # 18
P1.name = " 海绵宝宝 "
P1.age = 22
print(P1.name) # 海绵宝宝
print(P1.age) # 22
print(P1.__dict__) # {}
# 类型错误时
P1.name = 2222 # 类型不正确
P1.age = "3333" # 类型不正确
print(Panda.__dict__["name"].__dict__)
# {'name': 'name', 'type': <class 'str'>, 'date_dict': {...}, <__main__.Panda object at 0x000001DBE96F7A88>: '海绵宝宝'}
由上面示例我们可以知道, 一个类中被代理的属性将会保存在代理类 (也就是描述符) 的属性字典中, 而描述符对象又被当做属性保存在类的属性字典中
- 将进行过类型判断的属性放入到 "P1" 对象属性字典中, 方法一样, 只需要将 "self" 改成 "instance"
class Str:
def __init__(self,agencyName, agencyType):
self.name = agencyName
self.type = vagencyType
def __get__(self, instance, owner):
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value,self.type):
print(" 类型不符合 ")
return
else:
instance.__dict__[self.name] = value
return
def __delete__(self, instance):
instance.__dict__.pop(self.name)
3. 描述符的种类与属性查找优先级
- 通常的只要定义了
__get__
和 另一个或两个方法的类, 我们就称之为 数据描述符 -
如果一个类只定义了
__get__
方法 (没有修改数据的功能) , 我们称之为 非数据描述符 -
属性查找优先级 :
- 无论有没有都会先触发
__getattribute__
- 类属性
- 数据描述符 (如果数据描述符中重写了
__getattribute__
可能会导致无法调用描述符) - 实例属性 (对象属性)
- 非数据描述符
-
找不到触发
__getattr__()
的执行 -
示例 : 类属性 > 数据描述符
🌙数据描述符
class Str:
def __get__(self, instance, owner):
print('---> 触发 Str_get')
def __set__(self, instance, value):
print('---> 触发 Str_set')
def __delete__(self, instance):
print('---> 触发 Str_Delete')
🌙普通类
class Person:
name = Str()
def __init__(self,name):
self.name = name
Person.name = " 派大星 " # 类的属性
p = Person("name") # ---> 触发 Str_set
print(p.name) # 派大星
🔰由上面的实验可知, 最先找的是类属性
- 示例 : 数据描述符 > 实例属性
🌙数据描述符
class Str:
def __get__(self, instance, owner):
print('---> 触发 Str_get')
def __set__(self, instance, value):
print('---> 触发 Str_set')
def __delete__(self, instance):
print('---> 触发 Str_Delete')
🌙普通类
class Person:
name = Str() # name 是描述符对象
def __init__(self,name,age):
self.name = name
self.age = age
P1 = Person(" 海绵哥哥 ",99) # ---> 触发 Str_set
P1.name = " 海绵爸爸 " # ---> 触发 Str_set
P1.name # ---> 触发 Str_get
del P1.name # ---> 触发 Str_Delete
print(P1.__dict__) # {'age': 99} (查看对象属性字典, 没有 name 属性)
print(Person.__dict__) # ['name'] (查看类属性字典, 存在 name, 这个 name 就是描述符对象)
- 示例 : 示例属性 > 非数据描述符
🔰以下比较并没有实际意义, 只是便于对描述符的理解
🌙非数据描述符
class Str:
def __get__(self, instance, owner):
print('---> 触发 Str_get')
def __delete__(self, instance):
print('---> 触发 Str_Delete')
🌙普通类
class Person:
name = Str() # name 是描述符对象
def __init__(self,name,age):
self.name = name
self.age = age
P1 = Person(" 海绵妈妈 ",88) # 抛出异常 : "AttributeError" 属性中没有__set__方法
八. __call__
- 触发条件 : [对象] + () 就触发
__call__
的执行
class Person:
def __init__(self,name):
self.name = name
def __call__(self, *args, **kwargs):
print(args)
print(kwargs)
print(self.name + " 触发了__call__")
P1 = Person(" 派大星 ")
P1()
# ()
# {}
# 派大星触发了__call__
P1(1,2,3,4,name="shawn")
# (1, 2, 3, 4)
# {'name': 'shawn'}
# 派大星触发了__call__
一切皆对象, Person 类也是一个对象, Persion+() 触发的是生成 Persion 类的类里的
__call__
而 Persion 类生成的对象 +() 触发的才是 Persion 类的
__call__
九. __init__ 和 __new__
1. 先看代码 :
class Person:
def __init__(self,name):
print(" 触发__init__")
self.name = name
def __new__(cls, *args, **kwargs):
print(" 触发__new__")
# obj = object.__new__(cls) # 这个是直接使用 object 实例出来的对象
obj = super(Person,cls).__new__(cls) # 这个是通过当前类的父类实例出来的对象
return obj
p = Person("shawn")
# 触发__new__
# 触发__init__
print(p.name) # shawn
在一个类中如果没有重写
__new__()
方法, 那么就会使用父类中的__new__()
方法, 如果父类中没有, 则会一直追溯到 object 中的__new__()
方法如果在类中重写了
__new__()
方法, 我们可以通过 return 出一个实例, 该实例可以直接是 object 实例出来的对象, 也可以是当前类的父类实例出来的对象 (看上面代码)return 当前类的对象就会自动触发
__init__()
方法, 该实例是通过 cls 参数来控制是当前类的实例, 如果是别的类的实例或者其他任意东西则不会触发__init__()
的执行
- 无法触发
__init__
示例 :
🐞当 "return" 的不是当前类的实例, 是其它的一些任意数据
class Persion:
def __init__(self,name):
print(" 触发__init__")
self.nam = name
def __new__(cls, *args, **kwargs):
print(" 触发__new__")
obj = super(Person,cls).__new__(cls)
return 1111
p = Person("shawn") # 触发__new__ (没有触发__init__的执行)
print(p.name) # 抛出异常 : "AttributeError" 没有这个属性
🐞当 "return" 的不是当前类的实例, 是其它类的实例(这里我们让 Person 继承 Str, 然后 return 出 Str 的实例)
class Str:
'''Str_obj'''
class Person(Str):
def __init__(self,name):
print(" 触发__init__")
self.name = name
def __new__(cls, *args, **kwargs):
print(" 触发__new__")
obj = super(Str,Str).__new__(Str)
print(obj.__doc__) # Str_obj
return obj
p = Person("shawn") # 触发__new__ (同样只是触发__new__, 并没有触发__init__方法)
print(p.name) # 同样抛出异常 : "AttributeError" 没有这个属性
2. 使用方法比较
__new__()
用于 创建实例 ,所以该方法是在 实例创建之前 被调用,它是 类级别 的方法,是个静态方法-
__init__()
用于 初始化实例 ,所以该方法是在 实例对象创建后 被调用,它是 实例级别 的方法,用于设置对象属性的一些初始值 -
由此可知,
__new__()
在__init__()
之前被调用。如果__new__()
创建的是当前类的实例,会自动调用__init__()
函数
3. 传入参数比较
__new__()
至少有一个参数 cls, 代表当前类, 此参数在实例化的时候由 Python 解释器自动识别__init__()
至少有一个参数 self, 就是__new__()
返回的实例,__init__()
在__new__()
的基础上完成一些初始化的操作
4. 返回值的比较
__new__()
必须要有返回值, 返回实例对象__init__()
不需要返回值
十. __str__ 和 __repr__
1.__str__
- 触发条件 : print([对象]) 的时候触发
__str__
的执行
class Person:
def __init__(self,name):
self.name = name
def __str__(self):
print(" 打印对象时触发了__str__")
return f" 对象名字 : {self.name}" # 🔰返回值必须是一个字符串, 否则报错
P1 = Person("shawn")
P2 = Person(" 乔治 ")
print(P1)
# 打印对象时触发了__str__
# 对象名字 : shawn
print(P2)
# 打印对象时触发了__str__
# 对象名字 : 乔治
print(P2.name) # 乔治
- 返回一个看起来字典格式的字符串示例
🔰示例一:
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return f"{self.__dict__}"
P1 = Person(" 木之本樱 ",88)
print(P1) # {'name': '木之本樱', 'age': 88}
🔰示例二:
import json
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return json.dumps(self.__dict__)
P1 = Person("shawn",22)
print(P1) # {"name": "shawn", "age": 22}
2.__repr__
- 触发条件 : 在命令窗口中直接写 [对象] 时触发
__repr__
的执行
>>> class Person:
... def __init__(self, name):
... self.name = name
... def __repr__(self):
... print(" 命令窗口直接写对象触发了__repr__")
... return f" 名字 : {self.name}" # 🔰返回值也必须是一个字符串, 否则报错
...
>>> P1 = Person(" 佩奇 ")
>>> P1
命令窗口直接写对象触发了__repr__
名字 : 佩奇
>>> P1.name
'佩奇'
十一. __module__ 和 __class__
1.__module__
- 可以查看当前操作的对象在哪个模块
🐞文件 "test.py" 内容
class Foo:
def __init__(self,name):
self.name = name
🐞当前执行文件内容
from test import Foo
obj = Foo("shawn")
print(obj.__module__) # test (test 来自模块)
class Bar:
def __init__(self,name):
self.name = name
B = Bar(" 野猪 ")
print(B.__module__) # __main__ (属于当前文件)
2.__class__
- 可以查看当前操作的对象所属的类
from test import Foo
obj = Foo("shawn")
print(obj.__class__) # <class 'test.Foo'>
print(obj.__class__.__name__) # Foo
class Bar:
def __init__(self,name):
self.name = name
B = Bar(" 野猪 ")
print(B.__class__) # <class '__main__.Bar'>
print(B.__class__.__name__) # Bar
十二. __slots__ 和 __dict__
1.__slots__
默认我们可以给 class 实例绑定任何属性和方法,这就是动态语言的灵活性如果我们想要限制 class 的属性怎么办?比如,只允许对 Persion 实例添加 name 和age属性。为了达到限制的目的,Python允许在定义 class 的时候,定义一个特殊的__slots__变量,来限制该 class 能添加的属性
- slots是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
class Str:
# __slots__ = "name"
# __slots__ = ["name","age"]
__slots__ = ("name","age") # 对象只拥有 name,age 属性
def __init__(self,name,age):
self.name = name
self.age = age
# self.sex = sex
p1 = Str("shawn",22)
print(p1.__slots__) # ('name', 'age')
print(p1.name,p1.age) # shawn 22
p1.friend = " 水冰月 " # 报错 : "AttributeError" (friend 没有放到 slots 中)
一旦在类中使用了 slots 属性, 实例出来的对象将不会有自己的 dict, 所有的对象, 都共享使用类名称空间中的属性, 在此之前每生成一个对象, 都会申请一份内存空间, 对象越多, 占用的空间就越多, 使用了 slots 后, 对象越多, 越节省内存
- 共享类的名称空间属性示例
class Str:
__slots__ = ["name","age"]
def __init__(self,name,age):
self.name = name
self.age = age
p1 = Str(" 大雄 ",22)
p2 = Str(" 静香 ",90)
print(p1.name,p1.age) # 大雄 22
print(p2.name,p2.age) # 静香 90
print(p1.__slots__) # ['name', 'age']
print(p2.__slots__) # ['name', 'age']
print(p1.__dict__) # 报错 : "AttributeError" 没有该属性
print(p1.__dict__) # 报错 : "AttributeError" 没有该属性
print(Str.__dict__)
''' 输出
{'__module__': '__main__', '__slots__': ['name', 'age'], '__init__': <function Str.__init__ at 0x0000025344BEC828>, \
'age': <member 'age' of 'Str' objects>, 'name': <member 'name' of 'Str' objects>, '__doc__': None}
'''
- 当改变类的 name 和 age 时
Str.name = " 小玉 "
Str.age = 77
print(p1.name,p1.age) # 小玉 77
print(p2.name,p2.age) # 小玉 77
# 🔰发现所有对象的属性都发生了改变
总结 : 第一 : slots 属性是类属性, 可以用来限制实例对象能拥有的属性, 并且不再有自己的名称空间
第二 : 对象属性字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用 slots 取代实例的 dict
2.__dict__
- dict 在前面的许多地方都使用到了 dict 这个属性, 它用来查看一个对象或类的名称空间属性, 也可以说属性字典
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
P1 = Person(" 夏尔凡多姆海恩 ",1250)
print(P1.__dict__)
# {'name': '夏尔凡多姆海恩', 'age': 1250}
print(Person.__dict__)
'''{'__module__':'__main__','__init__': <function Person.__init__ at 0x0000021CDDDAC4C8>,'__dict__': <attribute'__dict__'of'Person'objects>,\'__weakref__': <attribute'__weakref__'of'Person'objects>,'__doc__': None}'''
十三. __all__
前面模块介绍章节其实已经介绍过了 __all__
的使用, 它用来在使用 from [模块名] import *
的时候, 针对这个 *
来限制能够使用的属性
🐞文件 "test.py" 内容
__all__ = ["Foo","Bar"] # 如此只能使用 Foo,Bar 两个属性
class Foo:
def __init__(self):
print("Foo")
class Bar:
def __init__(self):
print("Bar")
class Abc:
def __init__(self):
print("Abc")
🐞当前执行文件内容
from test import *
f = Foo() # Foo
b = Bar() # Bar
A = Abc() # 报错 : "NameError" 这个名字没有找到
十四. __iter__ 和 __next__
前面 迭代器 那一章节我们讲解了可迭代对象和迭代器对象的特点以及 For 循环的原理 :
- 可迭代对象 (iterable) : 在 python 中,但凡内置有
__iter__
方法的对象,都是可迭代的对象 - 迭代器对象 (iterator) : 内置有
__next__
和__iter__
方法的对象, 就是迭代器对象, 可迭代对象执行__iter__
得到的返回值就是迭代器对象 - For 循环原理请到上面链接查看 (重点)
接下来我们来给一个类重写 __iter__
以及 __next__
方法, 让其可以迭代, 也可以被 for 循环
class Foo:
def __init__(self,start,end):
self.__start = start-1
self.__end=end
def __iter__(self):
return self
def __next__(self):
self.__start+=1
if self.__start>=self.__end:
raise StopIteration('超出值') # 抛出一个异常,For 循环会检测异常自动结束循环
return self.__start
for i in Foo(1,10):
print(i) # 1 2 3 4 5 6 7 8 9
在函数学习的那一章节, 我们通过函数实现了 MyRange, 现在我们试试使用类来实现
class MyRange:
def __init__(self,start,stop,step=1):
self.__start = start-1
self.__stop = stop
self.__step = step
def __iter__(self):
return self
def __next__(self):
self.__start += self.__step
if self.__start >= self.__stop:
raise StopIteration(" 超出值 ") # 抛出异常
return self.__start
for i in MyRange(0,10,2):
print(i) # 1 3 5 7 9
应用小示例 : 产生一个小于某值的斐波那契数列
class Fib:
def __init__(self,count):
self.__start = 0
self.__stop = 1
self.__count = count
def __iter__(self):
return self
def __next__(self):
self.__start,self.__stop = self.__stop,self.__start+self.__stop
if self.__start >= self.__count:
raise StopIteration(" 超出 ") # 抛出异常
return self.__start
for i in Fib(50):
print(i) # 1 1 2 3 5 8 13 21 34
补充链式调用知识
- 链式调用是设计程序的模式, 在 Java 和 js 中被广泛应用
- 链式调用格式 : A. 方法. 方法. 方法. 方法
- 优势和好处 : 代码量大大减少, 逻辑集中清晰明了, 且易于查看和修改
class Person:
def __init__(self,name,age,sex):
self.name= name
self.age = age
self.sex = sex
def print_name(self):
print(self.name)
return self # 返回对象本身
def print_age(self):
print(self.age)
return self # 返回对象本身
def print_sex(self):
print(self.sex)
return self # 返回对象本身
P1 = Person(" 市丸银 ",45,"man")
P1.print_name() # 市丸银
P1.print_age() # 45
P1.print_sex() # man
P1.print_name().print_age().print_sex() # 市丸银 45 man
十五. __len__
计算对象的长度, return 是多少, len([对象])
的结果就是多少
- 触发条件 : 使用
len([对象])
时触发, 返回值必须为整数
🐞"return" 多少就是多少
class Person:
def __len__(self):
return 11111 # 只能返回整数, 不然报错 : "TypeError" 类型错误
p = Person()
print(len(p)) # 11111
🐞
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __len__(self):
return len(self.__dict__)
p = Person(" 我爱罗 ",5)
print(len(p)) # 2
p.sex = "man"
print(len(p)) # 3
十六. __hash__
- 触发条件 : 使用
hash([对象])
函数的时候触发, 里面返回什么值就是什么值, 返回值必须是整数
class Str:
def __hash__(self):
return 1231 # 只能返回整数, 不然报错 : "TypeError" 类型错
p = Str()
print(hash(p)) # 1231
- 可变类型 -----> 不可 hash 类型
- 不可变类型 -----> 可 hash 类型
print(hash(132)) # 132
print(hash(1.1)) # 230584300921369601
print(hash((4,5))) # 3713084879518070856
print(hash([11,23,4,4])) # 报错
print(hash({"age":12})) # 报错
- 如果一个对象不可hash, 我们可以通过重写
__hash__
方法让它变得可hash
class Foo:
def __init__(self):
self.name= " 黑子 "
self.l=[1,2,3,]
def __hash__(self):
return hash(self.name)
f=Foo()
print(hash(f)) # -3176404254760418957
十七. __eq__
- 触发条件 : 在两个对象进行
==
比较值的时候触发__eq__()
的执行, 在该方法内部可以自定义比较规则
print(12 == [1,2,3]) # False
print("w" == (2,3,5,)) # False
l1 = [1,2,3]
l2 = [1,2,3]
print(l1 == l2) # True (只比较值, 不比较 ID)
- 自定义规则示例
class Str:
def __init__(self,name):
self.name = name
def __eq__(self, other): # self(本身对象),other(另一个对象)
if self.name == other.name: # 只要名字相同, 我就认为相同
return True
else:
return False
p1 = Str(" 黑子 ")
p2 = Str(" 黑子 ")
p3 = Str(" 白子 ")
print(p1 == p2) # True
print(p1 == p3) # False
十八. 上下文管理协议 __enter__ 和 __exit__
1. 什么是上下文管理协议
上下文管理协议就是 with 语句, 为了让一个对象兼容 with 语句,必须在这个对象的类中声明 __enter__()
和__exit__()
方法
- with 对象, 触发对象的
__enter__
的执行 - 在 with 同一级别写代码, 脱离了with, 就会执行
__exit__
class Open:
def __enter__(self):
print("----->enter 执行了 ")
return "enter 的返回值 "
def __exit__(self, exc_type, exc_val, exc_tb):
print("----->exit 执行了 ")
with Open() as f: # 触发 __enter__
print(f)
print("----> 结束 ") # 触发 __exit__
'''
----->enter 执行了
enter 的返回值
----->exit 执行了
-----> 结束
'''
2.exit 的三个参数
- exc_type:异常类型
- exc_val:异常信息
- exc_tb:追溯信息
with语句中代码块出现异常,则 with 后的代码都无法执行
class Open:
def __enter__(self):
print("----->enter 执行了 ")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(" 异常类型 --->",exc_type)
print(" 异常信息 --->",exc_val)
print(" 追溯信息 --->",exc_tb)
print("----->exit 执行了 ")
with Open() as f:
print(f)
raise AttributeError(" 这里设置一个异常, 则下面代码不会再运行 ")
print("----> 结束 ") # 这一行无法运行
'''
----->enter 执行了
<__main__.Open object at 0x0000022E31429D08>
异常类型 ---> <class 'AttributeError'>
异常信息 ---> 这里设置一个异常, 则下面代码不会再运行
追溯信息 ---> <traceback object at 0x0000022E3142F0C8>
----->exit 执行了
(抛出异常) : AttributeError: 这里设置一个异常, 则下面代码不会再运行
'''
3. 为什么要使用上下文管理器
- 可以自动的操作(创建 / 获取 / 释放)资源,如文件操作、数据库连接
- 无需在使用
try...execept...
去处理异常
如果 __exit__()
返回值为 True, 那么异常会被清空,就好像啥都没发生一样,with 后的语句正常执行
class Open:
def __enter__(self):
print("----->enter 执行了 ")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(" 异常类型 --->",exc_type)
print(" 异常信息 --->",exc_val)
print(" 追溯信息 --->",exc_tb)
print("----->exit 执行了 ")
return True
with Open() as f:
print(f)
raise AttributeError(" 这里设置一个异常, 但上面 exit 的 return 是 True, 所以该异常被忽略 ")
print("----> 结束 ") # 这一行正常运行
'''
----->enter 执行了
<__main__.Open object at 0x000001D6CC399D08>
异常类型 ---> <class 'AttributeError'>
异常信息 ---> 这里设置一个异常, 则下面代码不会再运行
追溯信息 ---> <traceback object at 0x000001D6CC39F0C8>
----->exit 执行了
----> 结束
Process finished with exit code 0
'''
4. 自定义一个 open , 可以进行文件操作
class Open:
def __init__(self,path,mode="rt",encoding="utf-8"):
# 拿到一个 open 对象, 那么我们就可以借用 open 对象的方法, 当然你也可以重写方法 read(),write()等
self.f = open(path,mode,encoding=encoding)
def __enter__(self):
return self.f # 返回 open 对象 (作用就是为了可以使用它的方法)
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type: # 如果不为空, 说明 with 上下文内部出错误了
print(exc_val) # 打印一下错误信息
print(" 错误已被忽略 ") # 提示一下不会结束程序
return True # return True 就是为了忽略错误
self.f.close() # 当 with 内部没有错误, 正常结束时进行资源清理工作(关闭文件等)
with Open("test.py","rt",encoding="utf-8") as f:
res = f.read()
print(res)
5. 总结
使用 with 语句的目的就是把代码块放入 with 中执行,with结束后,自动完成清理工作,无须手动干预
在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在 exit 中定制自动释放资源的机制,你无须再去关注这个问题