零基础学习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
相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习