- 📢博客主页:盾山狂热粉的博客_CSDN博客-C、C++语言,机器视觉领域博主
- 📢努力努力再努力嗷~~~✨
💡大纲
⭕与面向过程的编程相比,面向对象的编程显得更符合人们的思维逻辑。
👉什么是对象、什么是类
👉类的定义:类名、属性(成员变量)、方法(成员函数)
👉创建类的实例(对象):访问成员变量与成员函数、修改成员变量与成员函数
👉类的继承与多态
一、什么是对象?什么是类?
(一)什么是对象?
💡对象 = 属性 + 方法
👉人们更擅长对于某一个对象进行处理(函数),对需要处理的对象进行内部信息(属性)的操作
👉一切皆对象,每个对象都有自己的属性,每个对象都有自己的动作行为
(二)什么是类?
👉类是对象的载体
👉就是很多很多同类的对象(比如狼狗、哈士奇、比熊)的公共特征抽象出来,创建通用的类
(三)类与对象
💡self是什么?
👉self起到的作用是绑定,将实例对象与类的方法绑定
👉传递给方法的是实例对象本身,通过self参数传递告诉python是哪个对象在调用函数
二、类的定义
💡三要素:类名、属性、方法
(一)类的命名要求
👉类名要有实际意义,尽量做到见名知意,对于复杂的类名需要对其功能进行描述
👉驼峰命名法:单词首字母大写
⚠️和函数一样,类的前后也要空两行
(二)构造函数
💡def __init__(self,要传递的参数)
👉在实例化对象的同时实现个性化定制
class C:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
return self.x + self.y
def mul(self):
return self.x * self.y
c = C(2,3)
c.add() # 5
c.mul() # 6
三、创建实例
(一)实例的创建
👉将实例赋值给对象,传入相应的参数
👉对象 = 类名(必要的初始化参数)
(二)访问属性
👉实例名.属性名
(三)访问行为
👉实例名.方法名(必要的参数)
(四)修改属性
1、直接修改
👉在实例化对象的时候直接赋新值
👉先访问,后修改
2、通过方法修改属性
class C:
x = 100
def set_x(self,v):
self.x = v
c = C()
c.set_x(250)
c.x # 250
四、类的继承
💡子类可以得到父类所有的东西
(一)简单的继承
class A:
x = 520
def hello(self):
print("我是A")
class B(A): # B继承自A
pass
b = B()
b.x # 520
b.hello() # 我是A
class C(A):
x = 520
def hello(self):
print("我是C")
c = C()
c.x # 520
c.hello() # 我是C
1、 判断一个对象是否属于某个类
isinstance(c,A) # 因为C继承自A
isinstance(c,C) # True
2、判断一个类是否是某个类的子类
issubclass(A,C) # False
issubclass(C,A) # True
(二)多重继承
1、 多重继承
💡一个子类继承多个父类,对于类中属性与函数的访问顺序是根据父类继承顺序从左到右
👉只有当在前面的父类中查找不到对应的属性才会到后面的父类中进行查找
class A:
x = 520
def hello(self):
print("我是A")
class B:
x = 880
y = 250
def hello(self):
print("你好,我是B")
class C(A,B):
pass
c = C()
c.x # 520
c.hello() # 我是A
c.y # 250
2、Mix-in及案例源码剖析
💡Mix-in:混入、乱入,一种设计模式
👉设计模式:根据编程语言的特性,针对已有的且反复出现的问题而设计的解决方案
class Animal:
def __init__(self, name, age) -> None:
self.name = name
self.age = age
def say(self):
print(f"我叫{self.name},我今年{self.age}岁")
class FlyMixin: # 这是插入的
def Fly(self):
print("我会飞~")
class Pig(FlyMixin,Animal):
def special(self):
print("我会吃")
p = Pig("大肠",25)
p.say()
p.special()
p.Fly()
'''
我叫大肠,我今年25岁
我会吃
我会飞~
'''
(三)组合
💡在一个类中,实例化多个别的类的对象t、c、d,然后在函数中通过self.t.函数名的方式来实现组合
(四)绑定
💡self起到的作用是绑定,将实例对象与类的方法绑定
👉可以通过对象._dict_的形式将该对象的数据以字典的形式输出
class C:
def get_self(self):
print(self)
c = C()
d = C()
c.x = 520
d.x = 250
d.y = 660
d.__dict__ # {'x': 250, 'y': 660}
👉在绑定后,在对象c中的c=250,对象d=100,只是通过函数去修改了本身的数据,并不改变类的数据内容
👉不建议使用类.属性的方式去修改类的数据
class C:
x = 100
def set_x(self,v):
self.x = v
c = C()
d = C()
print(c.x) # 100
c.set_x(250)
print(c.x) # 250
d.x # 100
⭕一个旁门左道的小技巧:创建最小的类
💡通过类来创建字典
class C:
pass
c = C()
c.x = 250
c.y = "兄阿吉阿语"
c.z = [1,2,3]
c.__dict__ # {'x': 250, 'y': '兄阿吉阿语', 'z': [1, 2, 3]}
五、重写(多态)
(一)重写父类方法
💡调用未绑定的父类方法,很鲁莽,会造成钻石继承的问题
class D(C):
def __init__(self, x, y,z):
C.__init__(x, y) # 自动继承父类属性
self.z = z # 添加新的属性
def add(self):
return C.add(self) + self.z
def mul(self):
return C.mul(self) * self.z
d = D(2,3,4)
d.add() # 9
d.mul() # 24
💡鸭子类型:比如一只鸟有鸭子的行为动作,那它就是鸭子;同理,对于一个类与其他的类,都有一样的函数,这就是python的多态性
👉animal函数具有多态性, 让不同对象作为参数,且在不检查其类型的情况下执行它的方法
(二)钻石继承
💡在继承时使用类名去进行自动继承会导致钻石继承,最上面的父类会被调用两次
👉在这个例子中,类A会调用一次构造函数,因为类C同时继承B1、B2,会再次调用类A
class A():
def __init__(self) -> None:
print("你好,我是A")
class B1(A):
def __init__(self) -> None:
A.__init__(self)
print("你好,我是B1")
class B2(A):
def __init__(self) -> None:
A.__init__(self)
print("你好,我是B2")
class C(B1,B2):
def __init__(self) -> None:
B1.__init__(self)
B2.__init__(self)
print("你好,我是C")
c = C()
'''
你好,我是A
你好,我是B1
你好,我是A
你好,我是B2
你好,我是C
'''
(三)解决方法
💡使用super().__init__(父类初始参数)
👉自动继承父类的所有方法
class A():
def __init__(self) -> None:
print("你好,我是A")
class B1(A):
def __init__(self) -> None:
super().__init__()
print("你好,我是B1")
class B2(A):
def __init__(self) -> None:
super().__init__()
print("你好,我是B2")
class C(B1,B2):
def __init__(self) -> None:
super().__init__()
print("你好,我是C")
c = C()
'''
你好,我是A
你好,我是B2
你好,我是B1
你好,我是C
'''
(四)MRO方法:方法解析顺序(Method Resolution Order)
C.mro() # [__main__.C, __main__.B1, __main__.B2, __main__.A, object]
B1.mro() # [__main__.B1, __main__.A, object]
六、“私有变量”与__slots__
(一)“私有变量”
1、名字改编
💡私有变量:通过某种手段,使得对象中的属性或方法无法被外部所访问
👉这种私有变量在python中其实并不存在,因为python对程序员是绝对信任的,仅仅是引入了name mangling(名称修饰)的机制
👉定义name mangling:在名字前面加两个连续的下横线,这样就是一个“私有变量”了
class C:
def __init__(self,x) -> None:
self.__x = x
def set_x(self,x):
self.__x = x
def get_x(self):
print(self.__x)
c = C(555)
# c.__x # 因为设置为“私有变量”,就是避免从外部直接访问,通过内部函数来
c.get_x() # 通过指定的接口来访问,555
c.set_x(520)
c.get_x() # 520
👉如果执意要直接访问类的属性,就可以通过对象._类名__属性名的方式来访问
c.__dict__ # {'_C__x': 555}
c._C__x # 555
👉对于方法也是同样的道理
👉尽量不要这样访问私有变量、函数
class D:
def __func(self):
print("Hello word!")
d = D()
d._D__func() # Hello word!
👉从外部新添加变量对象.__变量名的话,并不会方式名字改编
👉名字改编只会在类实例化对象的时候发生
c.__y = 250
c.__dict__ # {'_C__x': 555, '__y': 250}
2、_单个下横线开头的变量
👉仅供内部使用,属于约定俗成的规则
👉对于这样的变量不要随意访问,更不要去修改它
3、单个下横线结尾的变量
👉对于一个关键字参数,加_来使得其变成一个变量
(二)提升效率之道:__slots__
💡对象的属性都是以字典的形式保存在_dict_里面的,舍弃空间来换取时间效率
👉对于不需要动态添加属性的类来说,以字典的形式保存属性就是一种浪费,使用_slots_可以避免
👉在添加了__slots__属性后,无论是在类的内部还是外部添加属性,都是不允许的
class C:
__slots__ = ['x','y'] # 希望实例化对象只有x、y两个属性
def __init__(self,x):
self.x = x
c = C(250)
c.x # 250
c.y = 520
c.y # 520
c.z = 666
c.z # 不能访问
class C:
__slots__ = ['x','y'] # 希望实例化对象只有x、y两个属性
def __init__(self,x,y,z):
self.x = x
self.y = y
self.z = z
c = C(3,1,4) # AttributeError: 'C' object has no attribute 'z'
⭕在添加了__slots__属性后,对象会划分一个固定的空间来存放指定的属性,__dict__属性就不需要了,节省了空间;牺牲了python动态语言的灵活性,相对的
👉继承自父类的__slots__属性是不会在子类中生效的,python只会关注在每个类中定义的__slots__属性
class C:
__slots__ = ['x','y'] # 希望实例化对象只有x、y两个属性
def __init__(self,x):
self.x = x
class E(C):
pass
e = E(250)
e.x
e.y = 520
e.z = 666 # 可以添加属性
e.__slots__ # ['x', 'y']
e.__dict__ # {'z': 666} 只有新添加的属性会添加进字典中
七、魔法方法
(一)构建对象和销毁对象
1、__init__()
2、__new__(cls[,...])
💡 在__init__之前被调用
👉对象的诞生流程:由__new__方法创建一个实例,再传递给__init__方法,再进行个性化定制
- 需要重写 __new__() 方法的情况:在元类中去定制类、在继承不可变数据类型的时候(想要修改它)
👉对不可变数据类型进行修改:在实例化对象被创建之前进行拦截,然后再调用__new__去进行创建真正的实例
class CapStr(str):
def __new__(cls,string):
string = string.upper() # 将字符串变为大写字母
return super().__new__(cls,string)
cs = CapStr("Fishc")
cs # 'FISHC'
3、__del__(self)
💡在对象即将被销毁的时候所自动调用的
class C:
def __init__(self):
print("我来了")
def __del__(self):
print("我走了")
c = C()
del c
c = C()
d = c # 创建引用
del c
del d # 只有当最后一个引用被删除,对象才会被销毁
4、对象重生之旅
💡对象复活(在对象被销毁之前将self送出去):__del__() 方法可以(尽管不推荐!)通过创建对实例的新引用来推迟实例的销毁
👉全局变量:声明x为全局变量,令x = self
👉闭包:在对象销毁前,让该对象调用一个函数,将self作为参数传递出去
class D:
def __init__(self,name) -> None:
self.name = name
def __del__(self):
global x
x = self
d = D("小甲鱼")
d # <__main__.D at 0x252a4c3a350>
d.name # '小甲鱼'
del d
d # d已经删除,再访问会报错
x # <__main__.D at 0x252a51c7950> x = self 可以访问d对象的属性
x.name # '小甲鱼'
class E():
def __init__(self,name,func) -> None:
self.name = name
self.func = func
def __del__(self):
self.func(self) # 这里是为了保存self
def outer(): # 定义闭包是为人让self保存在外部函数的x中
x = 0
def inner(y=None):
nonlocal x
if y:
x = y
else:
return x
return inner
f = outer() # f = inner
e = E("小甲鱼",f) # self.inner(self) x = self
e # <__main__.E at 0x252a5258c10>
e.name # '小甲鱼'
del e
# e # 报错
g = f() # 这里的y是None,返回x,即self
g # <__main__.E at 0x252a5270ad0>
g.name # '小甲鱼'
(二)运算相关
1、算数运算相关的魔法方法
💡不需要背,需要用到就去查
👉希望两个字符串相加的结果不是拼接,而是统计两者的字符个数之和。重写对象的__add__魔法方法,实现对加法运算的拦截
class S(str):
def __add__(a, b):
return len(a) + len(b)
s1 = S("FishC")
s2 = S("Python")
s1+s2 # 11 s1 + s2 就相当于 s1.__add__(s2)
2、反算术运算相关的魔法方法
💡“r” 开头的版本,跟上面的算术运算是一一对应的
👉当两个对象相加的时候,如果左侧的对象和右侧的对象不同类型,并且左侧的对象没有定义 __add__() 方法,或其 __add__() 返回 NotImplemented,那么 Python 就会去右侧的对象中找查找是否有 __radd__() 方法的定义
class S1(str):
def __add__(self, other):
return NotImplemented
class S2(str):
def __radd__(self,other):
return len(self) + len(other)
s1 = S1("Apple")
s2 = S2("Banana")
s1 + s2 # 11
👉这里能够成功调用到 __radd__() 方法:
- 首先当然是因为 S2 实现了 __radd__() 方法
- 其次,s1 和 s2 是两个基于不同类的对象
- 再有一个条件,就是 S1 必须不能实现 __add__() 方法,不然还是会优先去执行左侧对象的 __add__() 方法(这里我们 __add__() 方法返回 NotImplemented,含义就是明确表示这个方法未实现;如果我们在 S1 中直接不去定义 __add__() 方法,也可以得到相同的结果)
3、增强赋值运算相关的魔法方法
💡“i”开头的版本,跟上面的算术运算也是对应的,执行的是运算兼赋值的操作
👉s1 += s2,就相当于 s1 = s1.__iadd(s2)👉有一个自我赋值的操作,也就是说它会修改对象自身
class S1(str):
def __iadd__(self, other):
return len(self) + len(other)
class S2(str):
def __radd__(self,other):
return len(self) + len(other)
s1 = S1("Apple")
s2 = S2("Banana")
s1 += s2 # 11
s1
type(s1) # int
👉如果增强赋值运算符的左侧对象没有实现相应的魔法方法,比如 += 的左侧对象没有实现 __iadd__() 方法,那么将退而求其次,使用相应的 __add__() 方法和 __radd__() 方法来实现 (字符串的拼接)
4、一些内置函数也有相应的魔法方法
💡 int() 函数,它相应的魔法方法是 __int__()
👉通过拦截了 __int__() 魔法方法,让它在原来的基础上添加了对中文数字的识别
class ZH_INT:
def __init__(self, num):
self.num = num
def __int__(self):
try:
return int(self.num)
except ValueError:
zh = {"零":0, "一":1, "二":2, "三":3, "四":4, "五":5, "六":6, "七":7, "八":8, "九":9,
"壹":1, "贰":2, "叁":3, "肆":4, "伍":5, "陆":6, "柒":7, "捌":8, "玖":9, }
result = 0
for each in self.num:
if each in zh:
result += zh[each]
else:
result += int(each)
result *= 10
return result // 10
n = ZH_INT("五贰零")
int(n)
5、位运算
💡位运算包含按位与(&)、按位或(|)、按位非(~),还有按位异或(^),左移(<<)和右移(>>)运算符
👉通过bin获得数的二进制形式
👉& 是按位进行 “与” 运算,就是只有当相同的位的值均为 1 的情况下,那么结果才为 1
👉按位非则是将每个位进行取反,就是将 1 变成 0,0 变成 1
👉按位异或就是当两个相同的二进制位的值,不同时,结果对应的二进制位的值为 1
👉按位移动:左移(<<)和右移(>>)运算符,运算符的左侧是运算对象,运算符的右侧是指定移动的位数(右移相当于除2^n,是地板除,左移相当于乘2^n)
(1)补码
(2)移位计数不能为负数,否则会引发 ValueError 异常
(3)优先级
💡在同一个表达式里面,按位或、按位异或、按位与、还有移位,它们的优先级是依次递增的,然后按位非是和正、负号处于同一个优先级行列。
6、math 模块相关的魔法方法
💡__index__(self)方法,当对象作为索引值或参数的时候,才会触发
class C:
def __index__(self):
print("被拦截了~")
return 3
c = C() # 3
s = "FishC"
s[c] # 'h'
bin(c) # '0b11'
(三)属性访问相关
1、hasattr()
💡检测对象是否存在某个属性,如果 object 对象存在 name 属性,则返回 True,否则返回 False。
hasattr(object, name, /)
# object 指定一个对象
# name 指定一个字符串,用于表示属性的名称
class C:
def __init__(self, name):
self.name = name
c = C("FishC")
hasattr(c, "name") # True
2、getattr()
💡返回对象中指定属性的值,getattr(c, "name")等价于c.name
getattr(object, name[, default])
# object 指定一个对象
# name 指定一个字符串,用于表示属性的名称
# default 可选参数,用于当指定的属性不存在的时候,返回该参数值
class C:
def __init__(self, name, age):
self.name = name
self.__age = age
c = C("小甲鱼", 18)
getattr(c, "name") # '小甲鱼'
getattr(c, "age") # 没有该属性,报错
getattr(c, "age", "没有这个属性~") # 给定默认值,不会报错
getattr(c, "_C__age") # 以私有变量的对应方式去访问 18
3、setattr()()
💡与 getattr() 函数相对应,用于设置对象中指定属性的值
👉setattr(c, "name", "小甲鱼")等价于c.name = "小甲鱼"
setattr(object, name, value, /)
# object 指定一个对象
# name 指定一个字符串,用于表示属性的名称
# value 指定一个将被赋值到属性的值
class C:
def __init__(self, name, age):
self.name = name
self.__age = age
c = C("小甲鱼", 18)
setattr(c, "_C__age", 19)
getattr(c, "_C__age") # 19
4、delattr()
💡与 setattr() 函数相对应,用于删除对象中指定属性的值
👉delattr(c, "name")等价于del c.name
delattr(object, name, /)
# object 指定一个对象
# name 指定一个字符串,用于表示属性的名称
class C:
def __init__(self, name, age):
self.name = name
self.__age = age
c = C("小甲鱼", 18)
delattr(c, "name")
hasattr(c, "name") # False
5、对应的魔法方法
💡__getattribute__()
class C:
def __init__(self, name, age):
self.name = name
self.__age = age
def __getattribute__(self,attrname):
print("拿来把你")
return super().__getattribute__(attrname)
c = C("小甲鱼",18)
getattr(c,"name") # 拿来把你 "小甲鱼"
c.Fish
'''
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[28], line 13
11 c = C("小甲鱼",18)
12 getattr(c,"name")
---> 13 c.Fish
Cell In[28], line 8, in C.__getattribute__(self, attrname)
6 def __getattribute__(self,attrname):
7 print("拿来把你")
----> 8 return super().__getattribute__(attrname)
AttributeError: 'C' object has no attribute 'Fish'
'''
💡__getattr__() 当用户试图去获取一个不存在的属性时,才会被触发
class C:
def __init__(self, name, age):
self.name = name
self.__age = age
def __getattribute__(self,attrname):
print("拿来把你")
return super().__getattribute__(attrname)
def __getattr__(self,attrname):
if attrname == "FishC":
print("I Love FishC")
else:
raise AttributeError(attrname)
c = C("小甲鱼", 18)
c.FishC
'''
拿来把你
I Love FishC
'''
c.x
'''
拿来把你
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[30], line 22
17 c.FishC
---> 18 c.x
Cell In[30], line 13, in C.__getattr__(self, attrname)
11 print("I Love FishC")
12 else:
---> 13 raise AttributeError(attrname)
AttributeError: x
'''
💡__setattr__()需要使用字典来避免死循环
class D():
def __setattr__(self, name, value):
self.__dict__[name] = value
d = D()
d.name = "小甲鱼"
d.name
💡__setattr__()
class D():
def __setattr__(self, name, value):
self.__dict__[name] = value
def __delattr__(self, name):
del self.__dict__[name]
d = D()
d.name = "小甲鱼"
d.__dict__ # {'name': '小甲鱼'}
del d.name
d.__dict__ # {}
(四)索引、切片、迭代协议
1、索引、切片拦截方法:__getitem__()、__setitem__()
💡拦截切片、索引、获取值的操作
class C():
def __getitem__(self, index):
print(index)
c = C()
c[2] # 2
c[2:8] # slice(2, 8, None)
s = "I Love Python"
s[2:6] # 'Love'
s[slice(2,6)] # 'Love'
s[7:] # 'Python'
s[slice(7,None)] # 'Python'
s[::4] # 'Ivyn'
s[slice(None,None,4)] # 'Ivyn'
👉获取值的操作
class D():
def __init__(self, data) -> None:
self.data = data
def __getitem__(self,index):
return self.data[index] * 2
d = D([1,2,3,4,5])
for i in d:
print(i,end=' ') # 2 4 6 8 10
class D():
def __init__(self, data) -> None:
self.data = data
def __getitem__(self,index):
return self.data[index]
def __setitem__(self,index,value):
self.data[index] = value
d = D([1,2,3,4,5])
d[1] # 2
d[1] = 1
d[1] # 1
d[2:4] = [2,3]
d[:] # [1, 1, 2, 3, 5]
2、针对可迭代对象更好的拦截方法:__iter__(self)、__next__(self)
💡对应的BIF函数iter()、next()
👉根据python的迭代协议,如果一个对象定义了__iter__()魔法方法,那么它就是可迭代对象;如果一个可迭代对象定义了__next__()魔法方法,那么它就是迭代器
x = [1,1,2,3,5]
dir(x) # 有iter(),没有next()
for i in x:
print(i, end=" ") # 1 1 2 3 5
# 模拟循环的实现流程
_ = iter(x)
while True:
try:
i = _.__next__()
except StopAsyncIteration:
break
print(i, end = " ") # 1 1 2 3 5
class Double():
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self): # 如果自己是迭代器就返回自己
return self
def __next__(self): # 迭代器有__next__(self)
if self.value == self.stop:
raise StopAsyncIteration
self.value += 1
return self.value * 2
d = Double(1, 5)
for i in d:
print(i, end=" ") # 2 4 6 8 10
(五)代偿
⭕主要讲的是魔法方法的代偿机制,所谓代偿,就是替代+补偿,指的是当本该发挥作用的魔法方法没有被定义时,Python便会退而求其次,寻找类似的相关方法来调用,如用__getitem__()作为__iter__()和__next__()的代偿,__iter__()和__next__()作为__contains__()的代偿,__len__()作为__bool__()的代偿等,看起来有点奇怪,其实也算是一种“不得已而为之”的折衷方案吧……
1、 魔法方法__contains__
💡魔法方法__contains__()用于实现成员关系的检测,当使用in和not in进行成员关系判断时便会触发该方法
class C():
def __init__(self, data) -> None:
self.data = data
def __contains__(self, item):
print("嗨")
return item in self.data
c = C([1,2,3,4,5])
3 in c # 3对应魔法方法里的item参数
'''
嗨
True
'''
👉找不到__contains__()魔法方法,用__iter__()和__next__()作为__contains__()的代偿
class C:
def __init__(self,data):
self.data = data
def __iter__(self):
print("Iter",end = " -> ")
self.i = 0
return self
def __next__(self):
print("Next",end = " -> ")
if self.i == len(self.data):
raise StopIteration
item = self.data[self.i]
self.i += 1
return item
c = C([1,2,3,4,5])
3 in c # Iter -> Next -> Next -> Next -> True
6 in c # Iter -> Next -> Next -> Next -> Next -> Next -> Next -> False
👉__getitem__()作为__iter__()和__next__()的代偿
class C:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
print("Getitem", end= " -> ")
return self.data[index]
c = C([1, 2, 3, 4, 5])
3 in c # Getitem -> Getitem -> Getitem -> True
2、 魔法方法__bool__()
💡魔法方法__bool__()则与布尔测试bool()相对
class D:
def __bool__(self):
print("Bool")
return True
d = D()
bool(d) # Bool True
👉 如果没有定义__bool__()魔法方法,python会查找__len__()作为__bool__()的代偿,这个魔法方法返回值为非零表示True,否则是False
class D:
def __init__(self, data):
self.data = data
def __len__(self):
print("Len")
return len(self.data)
d = D("FishC")
bool(d) # Len 长度非零则直接返回True
d1 = D("")
bool(d1) # False 字符长度为0
(六)比较运算相关的魔法方法
💡比较运算相关的魔法方法包括:__lt__()(<)、__le__()(<=)、__gt__()(>)、__ge__()(>=)、__eq__()(==)、__ne__()(!=)
👉通过重写这些魔法方法,可以实现对两个字符串长度而非编码值的快速比较
class S(str):
def __lt__(self, other):
return len(self) < len(other)
def __gt__(self, other):
return len(self) > len(other)
def __eq__(self, other):
return len(self) == len(other)
s1 = S('FishC')
s2 = S('fishc')
print(s1 < s2) # False
print(s1 > s2) # False
print(s1 == s2) # True
print(s1 != s2) # True
#__eq__()只会拦截等值判断(==),返回长度比较结果,不会拦截不等值判断(!=),故不等于号两边比较的依然是字符串ASCII码是否相同
print(s1 <= s2) # True
print(s1 >= s2) # False
👉当不想让某个魔法方法生效时,只需将其赋值为None,此时若该魔法方法被触发,程序便会报错
👉通过这种手段,可以阻止某些不希望的情形(如成员关系判断等)出现,抑制了Python可能引发误会的代偿机制,进而避免了难以调试的bug
class S(str):
def __lt__(self, other):
return len(self) < len(other)
def __gt__(self, other):
return len(self) > len(other)
def __eq__(self, other):
return len(self) == len(other)
__le__ = None
__ge__ = None
__ne__ = None
s1 = S('FishC')
s2 = S('fishc')
try:
s1 != s2
except TypeError as e:
print(e) #'NoneType' object is not callable
👉这种做法也适用与代偿实现,定义第一个魔法方法为None后可以使代偿不实现
👉将__contains__定义为none,则后续的代偿也不能实现
class C:
def __init__(self, data):
self.data = data
def __iter__(self):
print('iter', end='->')
self.i = 0
return self
def __next__(self):
print('next', end='->')
if self.i == len(self.data):
raise StopIteration
item = self.data[self.i]
self.i += 1
return item
__contains__ = None # __contains__ -> __iter__() + __next__() -> __getitem__()
c = C([1, 2, 3, 4, 5])
try:
print(3 in c)
except TypeError as e:
print(e) # 'C' object is not a container
(七)可调用对象的魔法方法__call__()
💡__call__()在对象中既可以有属性又可以有方法,只要定义了__call__()方法,Python便可以像调用函数一样去调用该对象
class C:
def __call__(self):
print('hello')
c = C()
c() # hello
👉不仅支持位置参数和关键字参数,还可以用作闭包的简易实现
class C:
def __call__(self, *args, **kwargs):
print(f'位置参数 -> {args}\n关键字参数 -> {kwargs}')
c = C()
c(1, 2, 3, x=250, y=520)
# 位置参数 -> (1, 2, 3)
# 关键字参数 -> {'x': 250, 'y': 520}
class Power:
def __init__(self, exp):
self.exp = exp
def __call__(self, base):
return base ** self.exp
square = Power(2)
cube = Power(3)
print(square(5)) # 25
print(cube(3)) # 27
(八)跟字符串相关的魔法方法__str__()和__repr__()
💡str()函数将参数转换为字符串对象,是给人看的;repr函数将对象转换为程序可执行的字符串,是给程序看的
👉被repr()转化后的字符串可以被内置函数eval()去引号后执行,其结果往往就是原参数,故eval()可以理解为repr()的反函数
print(repr("print('crazy')")) # "print('crazy')"
print(str("print('crazy')")) # print('crazy')
# repr()返回的带引号,str()返回的不带引号
eval("print('crazy')") # crazy
eval(repr(print('crazy'))) # crazy
👉__str__()魔法方法定义的,只能应用于对象出现在打印操作的顶层,即如果把多个对象放到一个列表中,然后打印该列表的话,那么就没办法访问到对应的字符串了
class C:
def __str__(self):
return 'i love you'
cs = [C(), C(), C()]
for each in cs:
print(each)
# i love you
# i love you
# i love you
print(cs) # [<__main__.C object at 0x000001699FF0AC70>, <__main__.C object at 0x000001699FF0AC40>, <__main__.C object at 0x000001699FF0ACD0>]
class C:
def __repr__(self):
return 'i love you'
cs = [C(), C(), C()]
for each in cs:
print(each)
# i love you
# i love you
# i love you
print(cs) # [i love you, i love you, i love you]
👉魔法方法__str__()和__repr__()分别对应str()和repr(),二者均返回字符串,且__repr__()可作为__str__()的代偿(反之不成立)
class C:
def __repr__(self):
return 'i love you'
c = C()
print(repr(c)) # i love you
print(str(c)) # i love you
(九)property()函数
💡返回一个property属性对象,使之成为“托管属性”,全权代理该类中的私有属性,从而对其进行访问或修改,propert()函数第一个优点是简化类似遮遮掩掩的操作
class C:
def __init__(self):
self._x = 250
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx)
c = C()
print(c.x) # 250
c.x = 520
print(c.__dict__) # {'_x': 520}
del c.x
print(c.__dict__) # {}
👉利用__getattr__()、__setattr__()、__delattr__()实现相同目的
class D:
def __init__(self):
self._x = 250
def __getattr__(self, name):
if name == 'x':
return self._x
else:
super().__getattr__(name)
def __setattr__(self, name, value):
if name == 'x':
super().__setattr__('_x', value)
else:
super().__setattr__(name, value)
def __delattr__(self, name):
if name == 'x':
super().__delattr__('_x')
else:
super().__delattr__(name)
d = D()
print(getattr(d, 'x')) # 250
d.x = 520
print(d.__dict__) # {'_x': 520}
del d.x
print(d.__dict__) # {}
👉将property()函数做装饰器使用,会让创建只读属性的工作变得极为简单
class E:
def __init__(self):
self._x = 250
@property # 创建只读属性
def x(self):
return self._x
e = E()
print(e.x) # 250
try:
e.x = 520 # 不能修改
except AttributeError as e:
print(e)#can't set attribute
👉改为正常的,就是在property函数的位置参数传x对应第一个参数fget,其他的参数为none,表示不支持写入和删除,所以只读
class E:
def __init__(self):
self._x = 250
def x(self):
return self._x
x = property(x)
👉想要继续传入其他两个参数,property属性对象(由property函数返回,根据语法糖解析是传给了x)提供了getter、setter和deleter三个方法,这些方法对应property()函数的三个参数接口
class E:
def __init__(self):
self._x = 250
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
e = E()
print(e.x) # 250
e.x = 520
print(e.__dict__) # {'_x': 520}
del e.x
print(e.__dict__) # {}
八、类方法和静态方法
(一)类方法
💡类方法是指用于绑定类的方法,用@classmethod装饰器来定义,绑定的是类而非实例对象
# elf和cls均为约定俗成的写法,更换了也没有错
class C:
def funA(self):
print(self)
@classmethod
def funB(cls):
print(cls)
c = C()
c.funA() # <__main__.C object at 0x000001AAF996CFA0> 绑定的是一个对象
c.funB() # <class '__main__.C'> 绑定的是类C
class C:
count = 0
def __init__(self): # 构造函数
C.count += 1
@classmethod
def get_count(cls):
print(f'该类一共实例化了{cls.count}个对象')
c1 = C()
c2 = C()
c3 = C()
c1.get_count() # 该类一共实例化了3个对象
#如果在一个对象中创建一个跟类属性同名的实例属性,则后者就会覆盖类属性,但这里由于get_count是类方法,所以就算实例属性覆盖了类属性,也不会改变类属性
c3.count = 1
print(c3.count) # 1
c3.get_count() # 该类一共实例化了3个对象
print(C.count) # 3
# 虽然直接使用类名来访问类属性也可以,但如果涉及到继承问题,那么使用类方法会有更大的优势
(二)静态方法
💡静态方法则是指放在类中的函数,用@staticmethod装饰器来定义,即在类里边定义一个不需要绑定对象(self)的函数
class C:
@staticmethod
def funC():
print('i love you')
c = C()
c.funC() # i love you
C.funC() # i love you
class C:
count = 0
def __init__(self):
C.count += 1
@staticmethod
def get_count():
print(f'该类一共实例化了{C.count}个对象')
c1 = C()
c2 = C()
c3 = C()
c1.get_count() # 该类一共实例化了3个对象
c3.count = 1
print(C.count) # 3
(三)两者的使用
💡使用类方法和静态方法都不必担心实例属性覆盖类属性的问题。实际中要根据应用场景“因地制宜”,当操作不涉及类属性或实例属性引用时使用静态方法比较合适,如果像统计实例对象这种任务时,使用类方法实现更好
class C:
count = 0
@classmethod
def add(cls):
cls.count += 1
def __init__(self):
self.add()
@classmethod
def get_count(cls):
print(f'该类一共实例化了{cls.count}个对象')
class D(C):
count = 0
class E(C):
count = 0
c1 = C()
d1, d2 = D(), D()
e1, e2, e3 = E(), E(), E()
c1.get_count() # 该类一共实例化了1个对象
d1.get_count() # 该类一共实例化了2个对象
e1.get_count() # 该类一共实例化了3个对象
👉当代码涉及到继承的时候,实例对象的数量统计就变得复杂了,此时类方法的优势便体现出来了。独立出一个add()的类方法,这样做是为了实现自动化,让构造函数调用add()类方法,就不用管他是子类还是父类,谁去调用谁就会自动把对应的类传递进去,那么递增的也就是该类对应的count类属性,相当于各回各家各找各妈。我们要做的就是在继承的时候覆盖一下对应类的count属性,就可以轻松实现各个类实例的对象数量统计。
九、描述符
💡描述符是property()函数、类方法和静态方法背后的实现原理
(一)描述符协议
💡只要是实现了__get__()/__set__()/__delete__()三个中任何一个或多个方法的类,称为描述符
1、描述符
class D: # 类D 为描述符,在另一个类里将他的实例化对象赋值给想要管理的属性就能启用他
def __get__(self, instance, owner):
print(f'get~\nself -> {self}\ninstance -> {instance}\nowner -> {owner}')
def __set__(self, instance, value):
print(f'set~\nself -> {self}\ninstance -> {instance}\nvalue -> {value}')
def __delete__(self, instance): # 不要混淆__del__()
print(f'delete~\nself -> {self}\ninstance -> {instance}')
2、在另一个类C里将类D的实例化对象赋值给想要管理的属性就能启用类D
class C:
x = D()
c = C()
c.x = 250
'''
set~
self -> <__main__.D object at 0x00000217DB3CCB90>
instance -> <__main__.C object at 0x00000217DB3CD1D0>
value -> 250
'''
c.x
'''
get~
self -> <__main__.D object at 0x00000217DB3CCB90>
instance -> <__main__.C object at 0x00000217DB3CD1D0>
owner -> <class '__main__.C'>
'''
del c.x
'''
delete~
self -> <__main__.D object at 0x00000217DB3CCB90>
instance -> <__main__.C object at 0x00000217DB3CD1D0>
'''
👉self参数对应的是描述符这个类的实例对象
👉instance对应的是被描述符拦截的属性所在的类的实例对象
👉owner参数对应被描述符拦截的属性所在的类
(二)将案例修改为描述符的实现方式
1、案例
class C:
def __init__(self):
self._x = 250
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx)
c = C()
print(c.x) # 250
c.x = 520
print(c.x) # 520
del c.x
print(c.__dict__) # {}
2、使用描述符改写
class D:
def __get__(self, instance, owner):
return instance._x
def __set__(self, instance, value):
instance._x = value
def __delete__(self, instance):
del instance._x
class C:
def __init__(self, x=250):
self._x = x
x = D()
c = C()
print(c.x) # 250
c.x = 520
print(c.x) # 520
del c.x
print(c.__dict__) # {}
💡代码虽然能实现但不是一段健康的代码,类D中使用了类C中的属性_x
3、用描述符写一个自己的property函数
class PropertyL:
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, owner):
return self.fget(instance)
def __set__(self, instance, value):
self.fset(instance, value)
def __delete__(self, instance):
self.fdel(instance)
class C:
def __init__(self):
self._x = 250
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
p = PropertyL(getx, setx, delx)
c = C()
print(c.p) # 250
c.p = 520
print(c.p) # 520
del c.p
print(c.__dict__) # {}
4、用描述符实现getter()、setter()、deleter()方法
class PropertyL:
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, owner):
return self.fget(instance)
def __set__(self, instance, value):
self.fset(instance, value)
def __delete__(self, instance):
self.fdel(instance)
def getter(self, func):
self.fget = func
return self
def setter(self, func):
self.fset = func
return self
def deleter(self, func):
self.fdel = func
return self
class D:
def __init__(self):
self._x = 250
@PropertyL
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
d = D()
print(d.x) # 250
d.x = 520
print(d.__dict__) # {'_x': 520}
del d.x
print(d.__dict__) # {}
5、描述符只能应用于类属性,而把它应用于对象属性,使用不会打印get~
class D:
def __get__(self, instance, owner):
print('get~')
class C:
def __init__(self):
self.x = D()
c = C()
print(c.x) # <__main__.D object at 0x0000020AD520EC10>
class C:
x = D()
c = C()
c.x # get~
(三)描述符的细致分类:数据描述符、非数据描述符
⭕当发生属性访问时,数据描述符、实例对象属性、非数据描述符、类属性的优先级从高到低依次递减
1、数据描述符
💡指实现了__set__()或__delete__()中任意一个方法的描述符
👉数据描述符优先级大于实例对象属性
👉拥有__set__()方法后D为数据描述符,访问对象属性依然执行优先级较高的__get__()
class D:
def __get__(self, instance, owner):
print('get~')
def __set__(self, instance, value):
print('set~')
class C:
x = D()
c = C()
c.x = 'FishC' # set~
c.x # get~
print(c.__dict__) # {}
c.__dict__['x'] = 'FishC'
print(c.__dict__) # {'x': 'FishC'}
c.x # get~
2、非数据描述符
💡指仅实现了__get__()方法的描述符
👉D为非数据描述符,优先级小于实例对象属性,所以可以更改实例对象的属性
class D:
def __get__(self, instance, owner):
print('get~')
class C:
x = D()
c = C()
c.x = 'fishc'
print(c.x) # fishc
C.x # get~
3、描述符的第四个魔法方法:__set_name__(self, name, owner)
💡 优雅编程
class D:
def __init__(self, name):
self.name = name # name的作用就是记住这个描述符拦截的属性名
def __get__(self, instance, owner):
print('get~')
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
print('set~')
instance.__dict__[self.name] = value
class C:
x = D('x')
c = C()
c.x # get~ __get__()魔法方法拦截成功,此时属性x里没有内容,所以没有返回
print(c.__dict__) # {}
c.x = 250 # set~
print(c.__dict__) # {'x': 250}
print(c.x)
# get~
# 250
👉 class C中代码x = D('x')不够优雅,传入的属性x是以字符串'x'的形式传入
class D:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
print('get~')
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
print('set~')
instance.__dict__[self.name] = value
class C:
x = D() # 优雅~~~
c = C()
c.x # get~
print(c.__dict__) # {}
c.x = 250 # set~
print(c.__dict__) # {'x': 250}
print(c.x)
# get~
# 250
(四)函数、方法、静态方法、类方法的底层实现原理
1、函数和方法本质上是一个东西,python在底层分辨函数和方法的原理 -> 描述符
💡函数和方法如出一辙,实现对象绑定的函数叫方法,没有实现绑定的是普通的函数
👉Python底层通过描述符来辨别函数和方法
👉函数是一个非数据描述符,利用__get__()方法进行鉴别,instance参数为None的是函数,不为None的是方法
class C:
def func(self, x):
return x
c = C()
print(c.func) # <bound method C.func of <__main__.C object at 0x0000020AD51F9C10>>
print(C.func) # <function C.func at 0x0000020AD53498A0>
💡官方文档:当定义函数时,其实是实例化了一个叫做Function的类(底层为C语言形式实现),包含一个__get__()方法。
👉当数定义一个类,再在类里边定义一个函数的时候,就会用到__get__()方法(obj对应instance,objtype对应owner)。先判断第二个参数obj是否为None,是则返回自己,否则返回MethodType
class Function:
...
def __get__(self, obj, objtype=None):
'''Simulate func_descr_get() in Objects/funcobject.c'''
if obj is None:
return self
return MethodType(self, obj) # return MethodType其实就相当于return self(obj)
# MethodType其实也是一个类,其官方文档
# 当它的实例化对象被当做函数调用的时候,__call__()魔法方法就会被执行,将传递进去的func和obj两个参数进行整合并且返回,即将传递进来的第一个实参func作为函数名,第二个实参作obj为该函数的第一个参数,整合之后返回
class MethodType:
'''Emulate PyMethod_Type in Objects/classobject.c'''
def __init__(self, func, obj):
self.__func__ = func
self.__self__ = obj
def __call__(self, *args, **kwargs):
func = self.__func__
obj = self.__self__
return func(obj, *args, **kwargs)
👉官方代码相当于:
class D: def __get__(self, instance, owner): if instance is None: print('函数~') else: print('方法~') class C: x = D() c = C() c.x # 方法~ C.x # 函数~
2、静态方法
💡只要被StaticMethod装饰器碰过的对象就会变成StaticMethod类的对象,当这个对象被作为函数调用的时候,会直接调用传递进来的函数自身,省去类和对象的中间环节
👉静态方法是StaticMethod类的对象,作为函数调用时直接调用传递进来的函数自身,故无需绑定类和对象
# 静态方法,官方文档:
class StaticMethod:
'''Emulate PyStaticMethod_Type() in Objects/funcobject.c'''
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
def __call__(self, *args, **kwargs):
return self.f(*args, **kwargs)
3、类方法
💡类方法是ClassMethod类的对象,将参数klass即owner调整为新函数的第一个参数,以此来绑定类
# 类方法,官方文档:
class ClassMethod:
'''Emulate PyClassMethod_Type() in Objects/funcobject.c'''
def __init__(self, f):
self.f = f
def __get__(self, obj, cls=None):
if cls is None:
cls = type(obj)
if hasattr(type(self.f), '__get__'):
return self.f.__get__(cls, cls)
return MethodType(self.f, cls)
# type()如果传入一个对象,那么他会返回该对象所属的类
class C:
pass
c = C()
print(type(c)) # <class '__main__.C'>
print(type(c) is C) # True
👉调用方法的形式
# 代码hasattr(type(self.f), '__get__')为的是让类方法能够和其他装饰器串联起来使用,如; class C: @classmethod def __doc__(cls): return f'i love you -- from class {cls.__name__}' c = C() print(c.__doc__()) # i love you -- from class C print(C.__doc__()) # i love you -- from class C
👉属性访问的形式
class G: @classmethod @property def __doc__(cls): return f'A doc for {cls.__name__!r}' g = G() print(g.__doc__) # A doc for 'G' print(G.__doc__) # A doc for 'G'
👉将官方文档复制下来,并添加一些print语句
class MethodType: def __init__(self, func, obj): self.__func__ = func self.__self__ = obj def __call__(self, *args, **kwargs): func = self.__func__ obj = self.__self__ print('小白') return func(obj, *args, **kwargs) class ClassMethod: def __init__(self, f): self.f = f def __get__(self, obj, cls=None): if cls is None: print('旺财') cls = type(obj) if hasattr(type(self.f), '__get__'): print(f'来福,type(self.f) -> {type(self.f)}') return self.f.__get__(cls, cls) return MethodType(self.f, cls) class D: @ClassMethod # 注意不是classmethod @property def __doc__(cls): return f'i love you. -- from class {cls.__name__}' d = D() d.__doc__# 来福,type(self.f) -> <class 'property'> # 如果多个装饰器串联,那么传入这个参数的类type(self.f)就是property,相当于__doc__是property的实例对象__doc__ = property(__doc__),然后到@ClassMethod,运行到hasattr分支,type(self.f)运行的结果是property类,包含'__get__'方法,然后if里对__get__进行改造 # self.f.__get__(cls, cls)调用的是propert类里的__get__方法,并将instance对象参数的位置替换成了cls类参数 class D: @ClassMethod # 注意不是classmethod def __doc__(cls): return f'i love you. -- from class {cls.__name__}' d = D() d.__doc__ # 来福,type(self.f) -> <class 'function'> # python万物皆对象,函数也是对象,这里type(self.f)指的是<class 'function'>类
十、类装饰器
💡类装饰器就是将装饰器作用在类上面,在类被实例化对象之前对其进行拦截和干预,用法与装饰函数的装饰器非常相似
👉函数可以用来装饰类(类装饰器),反过来,类也可以用作装饰器来装饰函数,使函数成为类的实例化对象,调用函数便会触发类的__call__()等一系列魔法方法
👉当类作装饰器用于装饰另一个类时,被装饰类的多次实例化对象实则为装饰类的同一个对象,不过是将其作为函数调用了若干次,因而会导致属性覆盖问题。解决方法是将装饰器类“套一层外壳”,放在一个函数中,由函数返回该类,再将该函数用作装饰器来装饰被装饰类即可
def report(cls):
def oncall(*args, **kwargs):
print('开始实例化对象')
_ = cls(*args, **kwargs)
print('实例化对象完成')
return _
return oncall
@report
class C:
pass
c = C()
# 开始实例化对象
# 实例化对象完成
@report
class C:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
print('构造函数被调用')
c = C(1, 2, 3)
# 开始实例化对象
# 构造函数被调用
# 实例化对象完成
print(c.__dict__)
# {'x': 1, 'y': 2, 'z': 3}
# 类装饰器的作用就是在类被实例化对象之前对其进行拦截和干预
👉让类做装饰器来装饰函数
class Counter: def __init__(self): self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f'已经被调用了{self.count}次') c = Counter() c() # 已经被调用了1次 c() # 已经被调用了2次 c() # 已经被调用了3次 class Counter: def __init__(self, func): self.count = 0 self.func = func def __call__(self, *args, **kwargs): self.count += 1 print(f'已经被调用了{self.count}次') return self.func(*args, **kwargs) @Counter def say_hi(): print('hi~') print(say_hi) # <__main__.Counter object at 0x000001E41134AFD0> # 函数say_hi已经被掉包成Counter的对象 say_hi() # 已经被调用了1次 # hi~ say_hi() # 已经被调用了2次 # hi~ say_hi() # 已经被调用了3次 # hi~
👉用类做装饰器来装饰类
class Check: def __init__(self, cls): self.cls = cls def __call__(self, *args, **kwargs): self.obj = self.cls(*args, **kwargs) return self def __getattr__(self, item): print(f'正在访问{item}....') return getattr(self.obj, item) @Check class C: def say_hi(self): print(f'hi~ -> {self.name}') def say_hey(self): print(f'hey~ -> {self.name}') # 将类C改写 @Check class C: def __init__(self, name): self.name = name def say_hi(self): print(f'hi~ -> {self.name}') def say_hey(self): print(f'hey~ -> {self.name}') c1 = C('c1') c2 = C('c2') print(c1.name) # 正在访问name.... # c2 print(c2.name) # 正在访问name.... # c2 c1.say_hi() # 正在访问say_hi.... # hi~ -> c2 c2.say_hey() # 正在访问say_hey.... # hey~ -> c2 # c1的name属性被c2的name属性覆盖 print(c1)# <__main__.Check object at 0x000001DB1AB5EF70> print(c2)# <__main__.Check object at 0x000002A74A5DEF70> # c1和c2其实是Check类的实例对象,其实传递给的是Check类的__call__()魔法方法 # 对象在@Check时就诞生了,class C在__call__()魔法方法中的第一个语句完成实例化,并将实例化对象传递给了self.obj属性,但返回的是self(Check类的对象自身,而非类C的对象) # c1.name、c2.name实际上访问的是Check类对象的name属性,但Check没有name属性,那么他就会试图去查找__getattr__(self, name)这个魔法方法,Check中定义了此方法,返回的是类C的实例化对象 # Check仅在做装饰器(@Check)时被实例化了一次,那么c1 = C('c1')、c2 = C('c2')只是把对象当函数调用了两次,从而访问了两次相应的__call__()魔法方法,self.obj第一次保存的是name为c1的类C实例化对象,而第二次调用被name为c2的类实例化对象所覆盖
👉解决bug的方法:
def report(cls): class Check: def __init__(self, *args, **kwargs): self.obj = cls(*args, **kwargs) def __getattr__(self, item): print(f'正在访问{item}....') return getattr(self.obj, item) return Check @report class C: def __init__(self, name): self.name = name def say_hi(self): print(f'hi~ -> {self.name}') def say_hey(self): print(f'hey~ -> {self.name}') c1 = C('c1') c2 = C('c2') print(c1.name) # 正在访问name.... # c2 print(c2.name) # 正在访问name.... # c2 c1.say_hi() # 正在访问say_hi.... # hi~ -> c1 c2.say_hey() # 正在访问say_hey.... # hey~ -> c2 # 由于report返回的是Check类,被@report装饰过的class C其实是被替换为class Check,执行cx = C('cx')时就是在实例化Check类,调用构造函数__init__(),实例化report装饰器装饰过的类class C,然后把实例化的对象保存在self.obj属性中 # Check被实例化了两次,没有出现属性覆盖的bug
十一、type()函数和__init_subclass__()
(一)type()函数
1、 type()第一种用法
💡用于检测对象的类型,返回对象相应的类。检测对象类型推荐使用isinstance()函数更合适,因为其会考虑到子类的情况
print(int) # <class 'int'>
print(float) # <class 'float'>
print(set) # <class 'set'>
print(type('l') is str) # True
print(type(250)('520')) # int('520' ) 520
print(type(type(250)('520'))) # <class 'int'> type(250)('520')相当于int('520')
print(type([])('fishc')) # ['f', 'i', 's', 'h', 'c']
print(type({}).fromkeys('python', 250)) # {'p': 250, 'y': 250, 't': 250, 'h': 250, 'o': 250, 'n': 250}
👉type()就是python万物的起点:python万物皆对象,包括生成对象的类自身也是对象,其是由type()生成而来的对象,而type()本身也是自己的对象
class C:
def __init__(self, x):
self.x = x
c = C(250)
print(c) # <__main__.C object at 0x000001C1DB9A7FD0>
print(type(c)) # <class '__main__.C'>
d = type(c)(520)
print(d) # <__main__.C object at 0x000001FB43FC7F70>
print(d.__class__) # <class '__main__.C'>
print(type(C)) # <class 'type'>
print(type(type)) # <class 'type'>
2、type()第二种用法
💡可根据传入的三个参数,返回一个新的类型(type对象)
👉参数一name指定类名;参数二base指定父类,用元组收集;参数三dict指定属性和方法,用字典收集
👉对象的__class__属性用于查看该对象的类型,类的__bases__属性用于查看该类的父类
class C:
pass
C = type('C', (), {}) # 参数一name指定类名;参数二base指定父类,用元组收集;参数三dict指定属性和方法,用字典收集
c = C()
print(c.__class__) # <class '__main__.C'>
print(C.__bases__) # (<class 'object'>,)
D = type('D', (C,), {})
print(D.__bases__) # (<class '__main__.C'>,)
E = type('E', (C,), dict(x=250, y=520))
print(E.__dict__) # {'x': 250, 'y': 520, '__module__': '__main__', '__doc__': None}
# 创建方法:
def func(self, name='fishc'):
print(f'hello {name}')
F = type('F', (), dict(say_hi=func))
f = F()
f.say_hi() # hello fishc
f.say_hi('python') # hello python
# 第四个参数(可选)kwds收集参数:当且仅当需要时,该收集参数将被传递给适当的元类机制(通常为__init_subclass__)
(二)__init_subclass__()
💡用于加强父类对子类的管理
👉子类D定义完成后,父类C就会触发__init_subclass__()方法 。直接输出dad loves you
👉在类定义完成后触发,所以子类的属性被覆盖。得到520
class C: # 父类
def __init_subclass__(cls):
print('dad loves you')
cls.x = 520
class D(C): # 子类
x = 250
# dad loves you
print(D.x) # 520
👉当使用type()函数构造想class D这种继承了定义过__init_subclass__()的父类时,如果需要给__init_subclass__()传递参数(如value),就可以通过第四个参数接力
class C:
def __init_subclass__(cls, value):
print('dad loves you')
cls.x = value
class D(C, value=520): # 注意这种写法
x = 250
# dad loves you
print(D.x) # 520
D = type('D', (C,), dict(x=250), value=520) # dad loves you
print(D.x) # 520
👉第四个参数是收集参数
class C:
def __init_subclass__(cls, value1, value2):
print('dad loves you')
cls.x = value1
cls.y = value2
D = type('D', (C,), dict(x=250), value1=520, value2=666)
# dad loves you
print(D.x) # 520
print(D.y) # 666
class C:
def __init_subclass__(cls, **kwargs):
print('dad loves you')
cls.x = list(kwargs.values())[0]
cls.y = list(kwargs.values())[1]
D = type('D', (C,), dict(x=250), value1=520, value2=666)
# dad loves you
print(D.x) # 520
print(D.y) # 666
十二、元类
(一)元类的定义
💡创造类的模板,type就是一个元类,所有元类都继承自type
👉未指定元类的话是<class 'type'>
class MetaC(type):
pass
class C(metaclass=MetaC): # 引入刚创建好的元类
pass
c = C()
print(type(c)) # <class '__main__.C'>
print(type(C)) # <class '__main__.MetaC'>
print(type(MetaC))# <class 'type'>
👉元类里的__new__()方法是在类C定义完成的那一刻触发
class MetaC(type):
def __new__(mcls, name, bases, attrs):
print('__new__() in MetaC~')
return type.__new__(mcls, name, bases, attrs)
def __init__(cls, name, bases, attrs):
print('__init__() in MetaC~')
type.__init__(cls, name, bases,attrs)
class C(metaclass=MetaC): # 定义完成就会触发元类的__new__()方法
def __new__(cls):
print('__new__() in C~')
return super().__new__(cls) # 调用的是object的__new__()
def __init__(self):
print('__init__() in C~')
# __new__() in MetaC~
# __init__() in MetaC~
👉实例化对象后,类里面的__init__()、__new()__方法才会触发
c = C()
# __new__() in C~
# __init__() in C~
⭕object是所有类的父类,元类比类高一级别,元类继承自type
👉元类两个方法中各参数的作用
class MetaC(type):
def __new__(mcls, name, bases, attrs):
print(f'mcls={mcls}, name={name}, bases={bases}, attrs={attrs}')
return type.__new__(mcls, name, bases, attrs)
def __init__(cls, name, bases, attrs):
type.__init__(cls, name, bases,attrs)
print(f'cls={cls}, name={name}, bases={bases}, attrs={attrs}')
class C(metaclass=MetaC):
pass
# mcls=<class '__main__.MetaC'>, name=C, bases=(), attrs={'__module__': '__main__', '__qualname__': 'C'}
# cls=<class '__main__.C'>, name=C, bases=(), attrs={'__module__': '__main__', '__qualname__': 'C'}
# 各参数作用跟上节type中各参数作用一致
👉把__call__()方法定义在元类里,拦截类实例化对象的操作
class MetaC(type):
def __call__(cls, *args, **kwargs):
print('__call__() in MetaC~')
class C(metaclass=MetaC):
pass
c = C() # __call__() in MetaC~
⭕绝大多数元类用到的就是__new__()、__init__()、__call__()三个魔法方法
(二)元类的应用
1、给所有的类添加一个属性
class MetaC(type):
def __new__(mcls, name, bases, attrs):
attrs['author'] = 'fishc'
return type.__new__(mcls, name, bases, attrs)
class MetaC(type):
def __init__(cls, name, bases, attrs):
cls.author = 'fishc'
return type.__init__(cls, name, bases, attrs)
class C(metaclass=MetaC):
pass
class D(metaclass=MetaC):
pass
c = C()
d = D()
print(c.author, d.author) # fishc fishc
2、对类名的定义规范做限制
class MetaC(type):
def __init__(cls, name, bases, attrs):
if not name.istitle():
raise TypeError('类名必须为大写字母开头!')
type.__init__(cls, name, bases, attrs)
try:
class mycls(metaclass=MetaC):
pass
except TypeError as e:
print(e) # 类名必须为大写字母开头!
3、修改对象的属性值 -> 如:把对象的所有字符串属性值都变成大写
def __call__(cls, *args, **kwargs):
new_args = [each.upper() for each in args if isinstance(each, str)]
return type.__call__(cls, *new_args, **kwargs)
class C(metaclass=MetaC):
def __init__(self, name):
self.name = name
c = C('FishC')
print(c.name) # FISHC
# 当然如果传入的不是str就不显示
class C(metaclass=MetaC):
def __init__(self, *names):
self.names = names
c = C('FishC', 'Python', 1223)
print(c.names) # ('FISHC', 'PYTHON')
4、限制类实例化时的传参方式 -> 如:要求类在实例化对象时只能关键字参数传参
class MetaC(type):
def __call__(cls, *args, **kwargs):
if args:
raise TypeError('仅支持关键字参数~')
return type.__call__(cls, *args, **kwargs)
class C(metaclass=MetaC):
def __init__(self, name):
self.name = name
try:
c = C('fishc')
except TypeError as e:
print(e) # 仅支持关键字参数~
c = C(name='fishc')
print(c.name) # fishc
5、禁止一个类被实例化
class NoInstances(type):
def __call__(self, *args, **kwargs):
raise TypeError('该类禁止实例化!')
class C(metaclass=NoInstances):
pass
try:
c = C()
except TypeError as e:
print(e) # 该类禁止实例化!
# 禁止实例化对象也可以使用静态方法和类方法
class C(metaclass=NoInstances):
@staticmethod
def static_ok():
print('静态方法允许使用~')
@classmethod
def class_ok(cls):
print('类方法也允许使用~')
C.static_ok() # 静态方法允许使用~
C.class_ok() # 类方法也允许使用~
6、只允许实例化一个对象
class OnlyInstance(type):
def __init__(cls, *args, **kwargs):
cls.__instance = None
type.__init__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = type.__call__(cls, *args, **kwargs)
return cls.__instance
else:
return cls.__instance
class C(metaclass=OnlyInstance):
pass
c1 = C()
c2 = C()
print(c1 is c2)# True
# c1和c2为同一个对象
print(dir(C))
# ['_OnlyInstance__instance', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
# 对象被保存在类里边的_OnlyInstance__instance这个属性中
十三、抽象基类
(一)不建议类被实例化
1、Mixin类
💡在不修改原有结构的基础上编写一个新的父类,使之被继承以实现新功能,类似于游戏外挂思路
2、抽象基类:检测类方法的实现
(二)抽象基类
💡抽象基类不能被直接实例化,只能被继承使用
👉子类必须实现抽象基类中定义的抽象方法,否则子类无法实例化对象
👉abc模块(AbstractBaseClasses) -> ABCMeta & abstractmethod
# 从abc模块中导入ABCMeta & abstractmethod
# 指定类的元类为ABCMeta,类被定义为抽象基类
from abc import ABCMeta, abstractmethod
class Fruit(metaclass=ABCMeta):
def __init__(self,name):
self.name = name
# 继承抽象基类的子类,只有实现被"@abstractmethod"装饰的抽象方法,才可以实例化对象
@abstractmethod
def good_for_health(self):
pass
class Banana(Fruit):
def good_for_health(self):
print("只要吃香蕉人就会变得开心~")
banana = Banana("香蕉")
banana.good_for_health() # 只要吃香蕉人就会变得开心~
(三)抽象基类是“鸭子类型”的补充
from abc import ABCMeta,abstractmethod
class Animal(metaclass=ABCMeta):
def __init__(self,name,age):
self.name = name
self.age = age
@abstractmethod
def intro(self):
pass
@abstractmethod
def say(self):
pass
class Cat(Animal):
def intro(self):
print(f"我是一只猫咪,我叫{self.name},今年{self.age}岁~")
def say(self):
print("mua~")
class Dog(Animal):
def intro(self):
print(f"我是一只小狗,我叫{self.name},今年{self.age}岁~")
def say(self):
print("哟吼~")
class Pig(Animal):
def intro(self):
print(f"我是一只小猪,我叫{self.name},今年{self.age}岁~")
def say(self):
print("oink~")
c = Cat("web",4)
d = Dog("布布",7)
p = Pig("大肠",5)
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!