零基础学习Python(五)

1. 数据描述符与非数据描述符

首先,描述符只能作用于类属性,如果将描述符作用于对象属性,则不会生效。

python 复制代码
class D:
    def __get__(self, instance, owner):
        print("~get")

class C:
    def __init__(self):
        self.x = D()

应该将D对象赋值给类C的属性:

python 复制代码
class C:
    x = D()

所谓数据描述符,就是实现了__set__或者__delete__魔法方法,如果只实现了__get__魔法方法,就称为非数据描述符。通过给描述符分类,旨在说明数据访问的优先级:数据描述符 -> 对象的属性 -> 非数据描述符 -> 类属性。比如说上面的D的对象就是一个非数据描述符,它的优先级是低于对象属性的,所以如果给对象c的属性x赋新的值,然后查询c.x,不会打印"~get":

说明没有经过__get__魔法方法拦截,但是如果访问类的属性x,则会被拦截到:

将D改为数据描述符:

python 复制代码
class D:
    def __get__(self, instance, owner):
        print("~get")

   def __set__(self, instance, value):
        print("~set")

class C:
    x = D()

此时数据描述符的访问属性最高,对对象属性的访问,全部会被__get__魔法方法拦截(当然对象属性赋值的操作也会被__set__魔法方法拦截):

即使修改c.__dict__字典,仍然无效:

但是对于对象属性的访问,最终是会走__getattribute__魔法方法,因此其优先级最高,默认的__getattribute__魔法方法中就实现了上述优先级,如果重写__getattribute__魔法方法,也就没描述符什么事了:

python 复制代码
class C:
    x = D()

    def __getattribute__(self, name):
        print("~aha")

在描述符里操作实例对象的__dict__属性,以修改实例对象的属性:

python 复制代码
class D:
    def __set_name__(self, owner, name):
        self.name = name
    
    def __get__(self, instance, owner):
        print("~get")
        return instance.__dict__[self.name]

   def __set__(self, instance, value):
        print("~set")
        instance.__dict__[self.name] = value

class C:
    x = D()

2. 类装饰器

上述代码相当于C = report(C),相当于一个oncall函数,创建C的对象,就会调用oncall函数:

类装饰器的作用就是类被实例化之前进行一些拦截和干预。

不仅可以使用函数来装饰类,也可以使用类来装饰函数:

python 复制代码
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("嗨~")

也可以用类装饰类:

python 复制代码
class Check:
    def __init__(self, cls):
        self.cls = cls
    
    def __call__(self, *args, **kwargs):
        self.obj = cls(*args, **kwargs)

    def __getattr__(self, name):
        print(f'正在访问{name}')
        return getattr(self.obj, name)

@Check
class C:
    def __init__(self, name):
        self.name = name
    
    def say_hi(self):
        print(f'嗨{self.name}~')

    def say_hey(self):
        print(f'嘿{self.name}~')

上述例子中,c1的name属性被c2给覆盖了。这是因为类装饰器装饰类时,相当于C = Check(C),即C不再是一个类了,而是一个Check对象; c1 = C("c1"),相当于调用check对象的__call__方法,__call__方法里是在创建一个C类的对象,赋给了check对象的属性,而这个check对象是只有一个,即用类装饰器装饰类的时候生成的那一个,因此再次执行c2 = C("c2")相当于覆盖了之前同一个check对象的obj属性;执行c1.name,相当于去寻找check对象的name属性,显然check对象没有name属性,因此去调用__getattr__方法,即去寻找其obj属性(也就是C类对象,这里就是c2)的name属性。本质原因是共用了一个Check对象

为了解决这个问题,将Check类改为:

python 复制代码
def report(cls):
    class Check:
        def __init__(self, *args, **kwargs):
            self.obj = cls(*args, **kwargs)

        def __getattr__(self, name):
            print(f'正在访问{name}')
            return getattr(self.obj, name)
    return Check

@report
class C:
    def __init__(self, name):
        self.name = name
    
    def say_hi(self):
        print(f'嗨{self.name}~')

    def say_hey(self):
        print(f'嘿{self.name}~')

上述代码C不再是C类,而是C = report(C),是一个Check类,执行c1 = C("c1")相当于生成一个check对象,对象的obj属性是一个C类的对象,里面存了name属性。同理执行c2 = C("c2")相当于生成另一个check对象,同样obj属性保存了另一个C类的对象,里面存了name属性。此时Check类对象不再只只有一个,而是有两个,可以访问到各自的name属性,不会被覆盖。

3. type函数

type函数的常用的用法:

type函数的一些不常用的用法:

本质上就是因为type函数返回对象所属的类。那如果type函数传入的就是一个类,而不是一个对象,会发生什么?答案是会返回type类:

这是为什么?因为Python中万物皆对象,一个类也是对象,类是由type类衍生出来的对象。因type函数隐藏的一个更强大的功能就是创造类:

python 复制代码
C = type("C", (), {})

上面的代码是用type函数创建了一个类,第一个参数是类名,第二个参数是父类,参数类型是元祖,第三个参数是类的属性和方法。以上代码与以下代码等同:

python 复制代码
class C:
    pass

使用type函数创建D类,继承C类

创建带有属性的类:

创建带有方法的类:

python 复制代码
def funC(self, name):
    print(f'Hello {name}')

F = type(F, (), dict(say_hi=funC))

type函数还有第四个参数,用于给__init_subclass__魔法方法传递参数。首先看__init__subclass__魔法方法的作用:用于覆盖子类的类属性

python 复制代码
class C:
    def __init_subclass__(cls, value):
        print("父爱如山~")
        cls.x = value

class D(C, value=250):
    x = 520

一旦定义完类D,类C的__init_subclass__魔法方法就会被调用,打印"父爱如山~",并且D的类属性x的值为520:

上述代码用type函数实现:

python 复制代码
D = type("D", (C,), dict(x=250), value=520)

4. 元类

简单来说,继承type的类就是元类:

python 复制代码
class MetaType(type):
    pass

class C(metaclass=MetaType):
    pass

可哟看出,类C的类型:type(C)是我们刚写的元类MetaType,不再是type了。而MetaType的类型是type:

元类相当于创造类的模板,在用元类创造普通类的过程中,一旦普通类定义完成,其元类的__new__和__init__魔法方法就会被调用,但是在创建普通类的对象的过程中不会被调用:

由此可见,普通类相当于人,元类相当于神,而type则是众神之神。元类中的__new__方法的各参数含义:mcls------元类,name------普通类名,bases------普通类继承的父类,attrs------普通类的属性,__init__方法的各参数含义:cls------普通类,name------普通类名,bases------普通类继承的父类,attrs------普通类的属性:

如果在元类中定义__call__魔法方法,那么在普通类实例化对象的过程中就会拦截:

python 复制代码
class MetaType(type):
    def __call__(self, *args, **kwargs):
        print("__call() in MetaC~")

class C(metaclass=MetaType):
    pass
相关推荐
biter008837 分钟前
opencv(15) OpenCV背景减除器(Background Subtractors)学习
人工智能·opencv·学习
Code哈哈笑2 小时前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
QQ同步助手2 小时前
如何正确使用人工智能:开启智慧学习与创新之旅
人工智能·学习·百度
流浪的小新3 小时前
【AI】人工智能、LLM学习资源汇总
人工智能·学习
A懿轩A3 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
南宫生11 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__12 小时前
Web APIs学习 (操作DOM BOM)
学习
数据的世界0114 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐14 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
OopspoO16 小时前
qcow2镜像大小压缩
学习·性能优化