赞
踩
先写一段很简单的代码:
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层面的源码剖析
还是先看一下示例代码
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这种魔法方法自定义需要的数据类型,具有较高的灵活性。
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> 断开链接,释放资源 """
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 """
__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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。