您的位置:首页 > 汽车 > 时评 > 零基础学习Python(五)

零基础学习Python(五)

2024/11/16 14:22:24 来源:https://blog.csdn.net/weixin_46628668/article/details/140834519  浏览:    关键词:零基础学习Python(五)

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

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

class D:def __get__(self, instance, owner):print("~get")class C:def __init__(self):self.x = D()

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

class C:x = D()

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

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

将D改为数据描述符:

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__魔法方法,也就没描述符什么事了:

class C:x = D()def __getattribute__(self, name):print("~aha")

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

class D:def __set_name__(self, owner, name):self.name = namedef __get__(self, instance, owner):print("~get")return instance.__dict__[self.name]def __set__(self, instance, value):print("~set")instance.__dict__[self.name] = valueclass C:x = D()

 

2. 类装饰器

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

 

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

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

class Counter:def __init__(self, func):self.count = 0self.func = funcdef __call__(self, *args, **kwargs):self.count += 1print(f'已经被调用了{self.count}次~')return self.func(*args, **kwargs)@Counter
def say_hi():print("嗨~")

 

也可以用类装饰类:

class Check:def __init__(self, cls):self.cls = clsdef __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 = namedef 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类改为:

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 = namedef 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函数隐藏的一个更强大的功能就是创造类:

C = type("C", (), {})

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

class C:pass

 

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

 

创建带有属性的类:

 

创建带有方法的类:

def funC(self, name):print(f'Hello {name}')F = type(F, (), dict(say_hi=funC))

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

class C:def __init_subclass__(cls, value):print("父爱如山~")cls.x = valueclass D(C, value=250):x = 520

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

上述代码用type函数实现:

D = type("D", (C,), dict(x=250), value=520)

4. 元类

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

class MetaType(type):passclass 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__魔法方法,那么在普通类实例化对象的过程中就会拦截:

class MetaType(type):def __call__(self, *args, **kwargs):print("__call() in MetaC~")class C(metaclass=MetaType):pass

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com