python高级编程——描述符Descriptor详解(补充篇)——python描述符实现一些底层高级功能

python高级编程——描述符Descriptor详解(补充篇)——python描述符实现一些底层高级功能_第1张图片

python高级编程——描述符Descriptor详解(补充篇)——python描述符实现一些底层高级功能_第2张图片

本文声明:python的描述符descriptor,这是属于python高级编程的一些概念和实现方法,可能有很多的小伙伴还并没有用到过,但是在Python的面试过程中有可能会出现,究竟什么是python描述符,有什么作用,使用有什么意义,它的诞生背景是什么,很少有文章专门介绍这一块,有的文章介绍的太过粗浅,以至于看过之后依然不能够理解描述符的本质。鉴于此,我寻思着出一期专门讲解python描述符的系列文章,跟前面的python装饰器系列文章一样,因为涉及到的内容偏多,本文依然是分为上、中、下、补充篇四个系列部分进行讲解,本文是第四篇——补充篇,介绍Python的描述符怎么实现一些python的高级功能,比如自定义实现@staticmethod、@property等功能。

上一篇文章中已经讲到,python描述符是可以实现大部分python类特性中的底层魔法,本文将以此为根基,实现几个基本的底层实现。

一、python描述符descriptor实现底层原理

1、实现底层@classmethod

众所周知,在python语言的面向对象中,使用@classmethod修饰的方法是类方法,类方法可以通过实例名称访问,也可以通过类名称访问,我们平时在使用的过程中,都是这么实用的,从来没有想过这在语言底层到底怎么实现呢?

先看一个简单的例子:

class Person:
    def __init__(self):
        pass
    def study(cls):
        print('我会搞学习!')  

print(Person.study())

运行结果错误,显示TypeError: study() missing 1 required positional argument: 'cls'

我们都知道错误的原因是什么,我要定义类方法,我需要给study方法使用@classmethod装饰器,现在我自定义一个装饰器,我不使用原本的@classmethod,依然让它达到相同的效果。代码如下:

class NewDefine_classmethod:
    """
    使用“描述符”和“装饰器”结合起来,模拟@classmethod
    """
    def __init__(self, function):
        self.function = function

    def __get__(self, instance, owner):
        #对传进函数进行加工,最后返回该函数
        def wrapper(*args, **kwargs):   #使用不定参数是为了匹配需要修饰的函数参数
            print("给函数添加额外功能")
            self.function(owner, *args, **kwargs)
        return wrapper

class Person:
    name='我有姓名'
    def __init__(self):
        pass

    @NewDefine_classmethod
    def study_1(cls):
        print(f'我的名字是:{cls.name},我会搞学习!')

    @NewDefine_classmethod
    def study_2(cls,score):
        print(f'我的名字是:{cls.name},我会搞学习!,而且这次考试考了 {score} 分')
    
print(Person.study_1())
print(Person.study_2(99))

运行结果为:

给函数添加额外功能
我的名字是:我有姓名,我会搞学习!
None
给函数添加额外功能
我的名字是:我有姓名,我会搞学习!,而且这次考试考了 99 分
None

那到底是怎么运行的呢?结合前面的装饰其原理,我们可以分这样几步分析:

第一步:@NewDefine_classmethod本质上是一个“类装饰器”,从它的定义可知,它的定义为

class NewDefine_classmethod(function).我们发现,python系统定义的@classmethod其实它的定义也是一样的,如下,

class classmethod(function) .怎么样?它们二者的定义是不是一样?

第二步:NewDefine_classmethod本质上又是一个描述符,因为在它的内部实现了__get__协议,由此可见,NewDefine_classmethod是“集装饰器-描述符”于一身的。

第三步:运行过程分析,因为study_1=NewDefine_classmethod(study_1),所以,study_1本质上是一个NewDefine_classmethod的对象,又因为NewDefine_classmethod本质上是实现了描述符的,所以,study_1本质上是一个定义在类中的描述符属性。

第四步:因为study_1本质上是一个定义在类中的描述符属性。所以在执行Person.study_1的时候,相当于是访问类的描述符属性,所以会进入到描述符的__get__方法。

现在是不是觉得原来python描述符还有这样神奇的使用呢?

注意:如果修饰的函数本身是具有返回值的,在__get__里面所定义的wrapper里面一定要返回,即return self.function(owner, *args, **kwargs)。

还有一个地方需要注意的是,因为这是自定义的底层实现,所以一些集成IDE可能会显示有语法错误,但是这没有关系,这正是python灵活多变的地方,运行并不会出现错误。

2、实现底层 @staticmethod

  staticmethod方法与classmethod方法的区别在于classmethod方法在使用需要传进一个类的引用作为参数。而staticmethod则不用。它们所遵循的原理是大致一样的,参见代码如下:

class NewDefine_staticmethod:
    """
    使用“描述符”和“装饰器”结合起来,模拟@classmethod
    """
    def __init__(self, function):
        self.function = function

    def __get__(self, instance, owner):
        #对传进函数进行加工,最后返回该函数
        def wrapper(*args, **kwargs):   #使用不定参数是为了匹配需要修饰的函数参数
            print("给函数添加额外功能")
            self.function(*args, **kwargs)
        return wrapper

class Person:
    name='我有姓名'
    def __init__(self):
        pass

    @NewDefine_staticmethod
    def study_1(math,english):
        print(f'我数学考了 {math} 分,英语考了 {english} 分,我会搞学习!')

    @NewDefine_staticmethod
    def study_2(history,science):
        print(f'我历史考了 {history} 分,科学考了 {science} 分,我会搞学习!')
    
print(Person.study_1(99,98))
print(Person.study_2(88,89))

运行结果为:

给函数添加额外功能
我数学考了 99 分,英语考了 98 分,我会搞学习!
None
给函数添加额外功能
我历史考了 88 分,科学考了 89 分,我会搞学习!
None

整个函数的执行原理与上面所实现的是一样的,但是有一些小的细节需要注意。

类方法classmethod必须第一个参数是cls,这个实际上就是判断所属的那个类,因此在__get__里面的function在调用的时候,第一个参数需要传递为owner,因为所属的“类cls等价于Person等价于owner”,但是因为静态方法不需要任何参数cls或者是self都不需要,因此在__get__实现的时候不能再传递owner参数,否则会显示参数错误。

3、实现底层 @property

  在使用描述符实现property的时候和前面稍有所区别,后面会进行分析,代码如下:

class NewDefine_property:
    """
    使用“描述符”和“装饰器”结合起来,模拟@classmethod
    """
    def __init__(self, function):
        self.function = function

    def __get__(self, instance, owner):
        print("给函数添加额外功能")
        return self.function(instance)

class Person:
    name='我有姓名'
    def __init__(self):
        self.__study=100

    @NewDefine_property
    def study_1(self):  #使用property装饰的函数一般不要用“参数”,因为它的主要功能是对属性的封装
        return self.__study

p=Person()
print(p.study_1)

运行结果为:

给函数添加额外功能
100

基本思想和前面分析的还是一样的,但是有几个地方有所区别,需要注意:

第一:@property的目的是封装一个方法,是这个方法可以被当做属性访问

第二:调用的方式与前面有所不同,__get__里面不能再定义wrapper了,否则不会调用wrapper。得不到想要的结果,为什么呢?

因为调用的方式不一样,根据前面的分析,study_1的本质是描述符属性,但是前面的调用均是使用的

Person.study_1()或者是p.study_1()的形式,还是当成方法去使用的。但是此处不一样了,直接就是当成属性去使用,

p.study_1 ,不再是方法调用,因此wrapper函数得不到调用。所以__get__方法得到了进一步简化。

4、使用描述符实现属性类型约束

众所周知,python是一门动态语言,属性的赋值可以是任意的,不像是静态语言那样,而我们前面讲过,描述符最核心的功能其实就是“属性代理”,即所谓的对属性进行加工改造,进行相关的约束。所以,如果想要对某一个属性的类型进行某种约束,描述符便可以很好地完成。实现的代码如下:

class Constraint_Property(object):
    def __init__(self,var_name,var_type,var_default_value=None):
        '''
        var_name:变量名称
        var_type:变量所要约束的类型,比如int、str、float等等
        var_default_value:变量的初始默认值
        '''
        self.name=var_name
        self.type=var_type
        self.default=var_type() if var_default_value is None else var_default_value
        #三元运算,如果使用了默认值就使用默认值,否则就是用某个类型的默认值,如int()、str()、float()

    def __get__(self,instance,owner):
        if self.default==None:
            return self.type()
        else:
            return self.default

    def __set__(self,instance,value):
        if not isinstance(value,self.type):
            raise TypeError("属性的值必须是: %s 类型",self.type)
        self.default=value

    def __delete__(self,instance):
        raise AttributeError("不能删除该属性")

class Student(object):
    name=Constraint_Property("name",str,"张三")  #str虽然是类名,也是可以作为参数的,因为一切皆对象
    age=Constraint_Property("age",int)           #int虽然是类名,也是可以作为参数的,因为一切皆对象
 
stu=Student()
print(stu.name)
print(stu.age)
stu.name="李四"
print(stu.name)
stu.age=25
print(stu.age)
print('===================================')
stu.name=100.0  #赋一个实数值

运行结果为:

张三
0
李四
25
===================================
Traceback (most recent call last):TypeError: ('属性的值必须是: %s 类型', )

从上面的实例可以看出,那么属性被限定为str,age的属性被限定为int。

小伙伴们,看了上面的这几个例子,是不是被python描述符的强大功能所折服呢?事实上python描述符的各种功能是非常强大的,这里就不一一实现了,有兴趣的小伙伴可以自己不断尝试哦!

你可能感兴趣的:(python,设计模式,白话python高级特性)