零基础学习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
相关推荐
秃头佛爷31 分钟前
Python学习大纲总结及注意事项
开发语言·python·学习
dayouziei3 小时前
java的类加载机制的学习
java·学习
dsywws6 小时前
Linux学习笔记之vim入门
linux·笔记·学习
晨曦_子画7 小时前
3种最难学习和最容易学习的 3 种编程语言
学习
城南vision7 小时前
Docker学习—Docker核心概念总结
java·学习·docker
ctrey_8 小时前
2024-11-1 学习人工智能的Day20 openCV(2)
人工智能·opencv·学习
十年之少8 小时前
由中文乱码引来的一系列学习——Qt
学习
u0101526589 小时前
STM32F103C8T6学习笔记2--LED流水灯与蜂鸣器
笔记·stm32·学习
王俊山IT9 小时前
C++学习笔记----10、模块、头文件及各种主题(二)---- 预处理指令
开发语言·c++·笔记·学习
慕卿扬10 小时前
基于python的机器学习(二)—— 使用Scikit-learn库
笔记·python·学习·机器学习·scikit-learn