1 普通对象为什么可以点出属性和方法
先写一段很简单的代码:
class Foo:
def __init__(self, name='', age=0):
self.name = name
self.age = age
def introduce(self):
print(f'大家好,我是{self.name},今年{self.age}岁了,很高心认识大家')
if __name__ == '__main__':
foo1 = Foo("猪猪侠",3)
print(foo1.name) # 猪猪侠
print(foo1.age) # 3
foo1.introduce() # 大家好,我是猪猪侠,今年3岁了,很高心认识大家
这是一段再平常不过的代码,Python中一切皆对象,平常使用过程中大家会很自然的点出对象的属性或者方法,但是你有考虑过Python是怎么实现的吗。
在Python3中所有的自定义类都会默认继承自object类,换句话将,object类是所有类的祖先,
# 类对象的__bases__属性保存类的继承关系
print(Foo.__bases__) # (<class 'object'>,)
print(int.__bases__) # (<class 'object'>,)
通过上面的代码输出,可以得出Python3中object类是所有内部类和自定义类的祖先类。所以一些自定义类的实例对象会具备一些object中已经为你准备好的方法,这就是Python中的魔法方法,也是Python与其他语言不一样的地方,其他语言不会给你开放出这些方法让你可以随意设计自己的类,语言描述会比较抽象,还是通过代码实验一下。
class Foo:
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls)
return instance
def __init__(self):
object.__setattr__(self, "storage", dict())
def __getattribute__(self, item):
try:
return object.__getattribute__(self, "storage")[item]
except KeyError:
return None
def __setattr__(self, key, value):
object.__getattribute__(self, "storage")[key] = value
def __delattr__(self, item):
object.__getattribute__(self, "storage").pop(item)
if __name__ == '__main__':
foo1 = Foo()
foo1.name = "张三"
print(foo1.name)
del foo1.name
在foo1 = Foo()这行打上一个断点:
foo1 = Foo()
这是一个创建对象的过程,创建对象分为两步,第一步是在堆中创建对象,返回对象的引用,第二步是对象初始化
foo1.name = “张三”
魔法方法起作用了
print(foo1.name)
del foo1.name
这就是Python层面可以使用点号的原因,如果再往深层次的原理上剖析,那么就应该去考虑CPython解释器了,本文章就写到这个程度,如果后面有机会我会更新CPython层面的源码剖析
2 列表,元组,字典为什么可以使用[]取值
还是先看一下示例代码
A = ["湖人","勇士","篮网","雄鹿","马刺"]
B = ("中国","北京","朝阳","国贸")
C = {"语文":89,"数学":34,"英语":100}
if __name__ == '__main__':
print(A[1]) # 勇士
print(B[-1]) # 国贸
print(C["语文"]) # 89
print(list.__bases__) # (<class 'object'>,)
print(tuple.__bases__) # (<class 'object'>,)
print(dict.__bases__) # (<class 'object'>,)
print(list.__class__) # <class 'type'>
print(tuple.__class__) # <class 'type'>
print(dict.__class__) # <class 'type'>
这是说一下对象的__class__属性表示这个对象是由那个类实例化出来的,可以用于普通对象和类对象,而__bases__表示类对象之间的继承关系,所以只能用于类对象。
上面的示例代码也是很普通的应用,为什么list,tuple,dict对象可以通过[]取值呢,本质还是魔法方法,还是用代码演示:
class Foo2(object):
pass
foo = Foo2()
foo['key'] # TypeError
上面的代码报错,可以推导出object中没有实现【】取值的方法
dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
class Foo2(object):
def __init__(self):
object.__setattr__(self, 'storage', {})
def __getitem__(self, item):
try:
return self.storage[item]
except KeyError:
print("没有想要获取的键")
return None
def __setitem__(self, key, value):
self.storage[key] = value
print("设置成功")
def __delitem__(self, key):
self.storage.pop(key)
if __name__ == '__main__':
foo = Foo2() # 创建对象
foo['k'] = "value1" # 设置成功
print(foo['k']) # value1
del foo['k']
print(foo['k']) # 没有想要获取的键 None
Foo2类的实例具备了和字典一样的功能,我们可以通过Python这种魔法方法自定义需要的数据类型,具有较高的灵活性。
3 自定义上下文管理器
with语句等同于try…except…finally… with可以通过简短的代码实现捕获异常和善后工作,常见的with使用场景是文件处理:
with open("1.txt") as f:
f.open()
即使在读取的过程中出现错误,with语句也能够关闭文件句柄,避免浪费系统资源造成内存泄漏,当然也可以使用try。except捕获异常
f = None
try:
f = open()
f.read()
except Exception as e:
print(e)
finally:
f.close()
当我们自定义的类在初始化时候需要建立socket链接或者占用其他系统资源,并且在出现异常的时候需要关闭资源,这种使用场景就可以考虑自定义上下文管理,看下面这段代码:
class Foo3:
def connet(self):
print("建立链接成功")
def disconnet(self):
print("断开链接,释放资源")
def work(self):
raise RuntimeError
def __enter__(self):
self.connet()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("exc_type:",exc_type)
print("exc_val:",exc_val)
print("exc_tb:",exc_tb)
self.disconnet()
if __name__ == '__main__':
with Foo3() as foo:
foo.work()
"""
即使出现了异常,一样会关闭资源
建立链接成功
exc_type: <class 'RuntimeError'>
exc_val:
exc_tb: <traceback object at 0x000001D82A5459C0>
断开链接,释放资源
"""
4 自定义迭代器、可迭代对象
class MyIter():
def __init__(self):
self.l = []
if __name__ == '__main__':
print(iter(MyIter())) # TypeError: 'MyIter' object is not iterable
"""
升级版:
"""
class MyIter():
def __init__(self):
self.l = [1,2,3,4,5]
def __iter__(self):
print("调用我")
return self
def __next__(self):
try:
return self.l.pop()
except IndexError:
raise StopIteration
if __name__ == '__main__':
m = MyIter()
i = iter(m)
for _ in i:
print(_)
"""
调用我
5
4
3
2
1
"""
5 Python类的灵魂中的灵魂
__instancecheck__、__subclasscheck__、__subclasshook__ 这三个魔法方案用的不多,
但是当你掌握他们三个你会对Python类有一个不一样的认知
了解这个概念之前首先要了解元类,类对象实例化后得到实例对象,类对象是通过元类对象实例化出来的,元类就是创造类的对象,反过来说,元类创造类,类创造实例
__ instancecheck__
内置函数isinstance(obj,class)本质上是调用的type(class) 即class元类的 __ instancecheck__
class Foo4:
def __instancecheck__(self, instance):
print(f"__instancecheck__魔法方法被调用")
return True
if __name__ == '__main__':
print(isinstance(123,int)) # True
print(isinstance(123,Foo4)) # False
"""
得到这个结果一点都不意外,123是int类的实例,不是Foo4类的实例,Foo4类中定义的__instancecheck__也没有被执行
因为这里调用的是type(Foo4)中的__instancecheck__
"""
print(isinstance(123,Foo4()))
# __instancecheck__魔法方法被调用
# True
"""
type(Foo4())就是Foo4类,此时会执行class Foo4中定义的__instancecheck__方法
isinstance的第二个参数必须是类或者是由多个类组成的元组,使用实例对象没有任何意义
"""
if __name__ == '__main__':
try:
isinstance(123,object())
except Exception as e:
print(e)
"""
print(isinstance(123,Foo4())) 没有报错是因为在Foo4类中实现了__instancecheck__方法
isinstance() arg 2 must be a type or tuple of types
__instancecheck__ 只有定义在元类中才有意义
"""
class MyType(type):
def __instancecheck__(self, instance):
# isinstance(obj,cls)
# instance是obj,也就是检测的实例对象,cls类的元类必须是MyType
if hasattr(instance, "name"):
return True
else:
return False
class A(metaclass=MyType):
pass
class B:
pass
if __name__ == '__main__':
print(isinstance(B(), A))
"""
False
保持MyType和A类不变,改变class B 给B类增加name属性,结果是True
"""
class B:
def __init__(self,name):
self.name = name
if __name__ == '__main__':
print(isinstance(B('s'), A))
"""
True
虽然B()是A类的实例对象,但是B类并不是A类的子类
"""
print(issubclass(B,A)) # False
__ subclasscheck__
这个特殊方法服务于issubclass(cls,cls),issubclass判断一个类是否是另一个类的子类,判断两个类之间是否存在继承关系,这个方法同样需要定义在元类中才有意义
class MyType(type):
def __subclasscheck__(self, subclass):
# 当调用issubclass(cls1, cls2)的时候,cls1就会传递给这里的subclass
# 但前提是cls2的元类是这里的MyType
print("我被执行了")
if hasattr(subclass, "name"):
return True
else:
return False
class A(metaclass=MyType):
pass
class B:
name = "ss"
if __name__ == '__main__':
print(issubclass(B,A))
"""
我被执行了
True
"""
如果我们不定义在元类中,看看会怎么样
class A:
def __subclasscheck__(self, subclass):
# 全部返回True
return True
# object居然是A的实例对象的子类。
print(issubclass(object, A())) # True
# A的实例对象压根就不是一个类,它居然摇身一变,成为了python中万物之父的类object的父类
# 究其原因就是因为A内部定义了__subclasscheck__,issubclass(object, A())的时候,会调用A的__subclasscheck__方法
无论是__instancecheck__,还是__subclasscheck__,它们都应该定义在元类里面,而不是类里面。如果定义在类里面,那么要想使这两个魔法方法生效,那么就必须使用该类的实例对象。而isinstance和issubclass的第二个参数接收的都是类(或者包含多个类的元组),我们传入实例对象理论上是会报错的,只不过生成该实例对象的类里面定义了相应的魔法方法,所以才不会报错。但即便如此,我们也不要这么做,因为这样没有什么意义。而且,如果你用的是pycharm这种智能的编辑器的话,也会给你标黄
__ subclasshook__
上面那两个魔法方法是属于预定义的,需要定义在元类中。但是__subclasshook__不是,它是定义在抽象基类(Iterable、Sized、Container)中。
class Iterable(metaclass=ABCMeta):
__slots__ = ()
# 如果想要继承Iterable,那么必须实现__iter__方法
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
# 重点来了,当我们调用issubclass(cls, Iterable)的时候
# 那么cls会传递给这里的C,注意这个方法是一个类方法,__subclasshook__里面cls指的是Iterable本身
# 而我们在调用issubclass(cls, Iterable)的时候,cls会传给这里的C
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
class Sized(metaclass=ABCMeta):
__slots__ = ()
# Sized,可以使用len方法的,那么内部必须实现__len__
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
# 和Iterable类似
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented
class Container(metaclass=ABCMeta):
__slots__ = ()
# 容器,内部必须实现__contains__方法,换句话说就是可以使用in
# 比如:if 1 in [1, 2, 3] 等价于 if [1, 2, 3].__contains__(1)
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
return _check_methods(C, "__contains__")
return NotImplemented
class C(metaclass=ABCMeta):
@classmethod
def __subclasshook__(self, C_):
print("我被执行了")
if hasattr(C_,'name'):
return True
return False
class D:
name = "haha"
if __name__ == '__main__':
print(issubclass(D,C)) # True
print(isinstance(D(),C)) # True
"""
如果定义了__subclasshook__,那么会同时作用于isinstance和issubclass。而__instancecheck__只作用于isinstance函数,__subclasscheck__只作用于issubclass函数。
__subclasshook__还可以继承
"""
class E(C):
pass
if __name__ == '__main__':
print(issubclass(D,E)) # True
print(isinstance(D(),E)) # True