当前位置:   article > 正文

玩转Python魔法_猪猪侠代码

猪猪侠代码

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岁了,很高心认识大家

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这是一段再平常不过的代码,Python中一切皆对象,平常使用过程中大家会很自然的点出对象的属性或者方法,但是你有考虑过Python是怎么实现的吗。
在Python3中所有的自定义类都会默认继承自object类,换句话将,object类是所有类的祖先,

# 类对象的__bases__属性保存类的继承关系
print(Foo.__bases__) # (<class 'object'>,)
print(int.__bases__) # (<class 'object'>,)
  • 1
  • 2
  • 3

通过上面的代码输出,可以得出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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在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'>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这是说一下对象的__class__属性表示这个对象是由那个类实例化出来的,可以用于普通对象和类对象,而__bases__表示类对象之间的继承关系,所以只能用于类对象。

上面的示例代码也是很普通的应用,为什么list,tuple,dict对象可以通过[]取值呢,本质还是魔法方法,还是用代码演示:

class Foo2(object):
    pass
foo = Foo2()
foo['key'] # TypeError

  • 1
  • 2
  • 3
  • 4
  • 5

上面的代码报错,可以推导出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__']
  • 1
  • 2
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

Foo2类的实例具备了和字典一样的功能,我们可以通过Python这种魔法方法自定义需要的数据类型,具有较高的灵活性。

3 自定义上下文管理器

with语句等同于try…except…finally… with可以通过简短的代码实现捕获异常和善后工作,常见的with使用场景是文件处理:

with open("1.txt") as f:
	f.open()
  • 1
  • 2

即使在读取的过程中出现错误,with语句也能够关闭文件句柄,避免浪费系统资源造成内存泄漏,当然也可以使用try。except捕获异常

f = None 
try:
	f = open()
	f.read()
except Exception as e:
	print(e)
finally:
	f.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当我们自定义的类在初始化时候需要建立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>
断开链接,释放资源
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

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
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

5 Python类的灵魂中的灵魂

__instancecheck__、__subclasscheck__、__subclasshook__ 这三个魔法方案用的不多,
但是当你掌握他们三个你会对Python类有一个不一样的认知
  • 1
  • 2

了解这个概念之前首先要了解元类,类对象实例化后得到实例对象,类对象是通过元类对象实例化出来的,元类就是创造类的对象,反过来说,元类创造类,类创造实例

__ 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

__ 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__方法
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

无论是__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__还可以继承
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
class E(C):
    pass


if __name__ == '__main__':
    print(issubclass(D,E)) # True
    print(isinstance(D(),E)) # True
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号