python描述符学习

本文涉及的产品
访问控制,不限时长
简介: 目录一、对象属性的访问控制二、描述符基本理解三、基本使用四、使用描述符完成property、classmethod、staticmethod自定义实现1、property的自定义实现2、classmethod的自定义实现3、关于实例方法的思考4、实例方法的自定义实现5、静态方法的自定义实现五、总结一、对象属性的访问控制看一下这个例子,我们创建一个学生类,提供名字和年龄的属性,然后实例化一个对象,并显示他的信息。

目录

一、对象属性的访问控制

看一下这个例子,我们创建一个学生类,提供名字和年龄的属性,然后实例化一个对象,并显示他的信息。

class Student:

    def __init__(self, name, age):
        self.name = name
        self.age = age


stu = Student('zlw', 26)
print(stu.name)  # 名字是'zlw'
print(stu.age)  # 年龄是26

# 对象的名字和年龄被正常打印,good
# 但是,Student类没办法控制用户在实例化时的行为

stu = Student(26, 'zlw')
print(stu.name)  # 名字是26
print(stu.age)  # 年龄是'zlw'

我们可以这样做:


class Student:

    def __init__(self, name, age):
        # 检测每一次实例化时的参数类型
        if not isinstance(name, str):
            raise TypeError('name必须是str类型')
        if not isinstance(age, int):
            raise TypeError('age必须是int类型')

        self.name = name
        self.age = age

我们可以在init函数中控制实例化时的输入类型,不过这有2个问题:
1、只能控制实例化时的类型,对于已经实例化完成的对象,可以直接通过stu.name = 23的形式来重新赋值
2、实例化传递的参数越多,那么类型检测的代码也越多,而且类型检测代码和初始化代码写在一起,两者干的事情不一样,逻辑不是一个层面的,都写在init函数里并不合适
我们可以这样写:

# 省略了age的处理,方式是一样的

class Student:

    def __init__(self, name):
        if not isinstance(name, str):
            raise TypeError('name属性值类型必须是str类型')

        self.__name = name


    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError('name属性值类型必须是str类型')
        self.__name = value

# 这样写就可保证在实例化和已经完成实例化的对象赋值时的类型检查问题了。
# 不过还是有一个问题,就是属性越多,这个类型检查的代码就会不断增加,并且堆积在Student类中
# 我们应该将类型检查的代码单独隔离开,但同时又可以和Student类进行交互

使用python的描述符来处理。

二、描述符基本理解

python的描述符,有几个理解:

1、描述符是一个类
2、描述符的目的是对属性的访问控制
3、描述符将访问控制和业务类分离开,业务类处理业务逻辑,描述符处理属性访问控制
4、描述符是一个特殊类型的类,所谓特殊,就是自定义了__get____set____delete__方法的类,这种类就叫描述符
5、单独一个描述符没有啥意义,描述符要作用于某一个具体的业务类,为这个业务类提供属性访问控制的功能
6、属性访问可不仅仅是指数据属性的访问,对于函数属性的访问也可以控制。
7、使用类的__setattr____getattr____delattr__只能全局控制所有属性,而描述符可以有针对的控制部分属性

三、基本使用


class DescName:
    dic = {}

    def __init__(self, value_type):
        self.value_type = value_type

    def __get__(self, instance, owner):
        print('get running...')
        return self.dic.get(instance, None)

    def __set__(self, instance, value):
        print('set running...')
        if not isinstance(value, self.value_type):
            raise TypeError('名字必须是str类型')

        self.dic[instance] = value

class Student:
    name = DescName(str)

    def __init__(self, name, age):
        self.name = name
        self.age = age

stu1 = Student('小明', 26)
print(stu1.name, stu1.age)  # 小明,26
stu2 = Student('小红', 25)
print(stu2.name, stu2.age)  # 小红,25

上方代码中,我定义了一个Student类用于表示一个学生。学生有两个属性nameage,对于age我并没有设置任何的控制,对于name我要求属性值必须是str类型。我们可以定义一个DescName描述符类来单独针对name这个属性提供控制功能。DescName描述符会对name的值进行类型判定,如果是非str类型就会报错。
请注意DesnName__set__方法中的:self.dic[instance] = value这句代码。
考虑到Student类会实例化出很多不同的学生对象,这些学生对象是共享同一个描述符对象Student.name的,因为这是一个类属性,我们要在DescName描述符中保存不同对象的name值,就需要区分不同的对象,我这里使用字典,将每一个实例作为字典的key来保存对应实例的name值。
不过这里也会有一个问题,就是字典的key必须是可hash对象,如果实例是可变对象比如list,则无法使用这种方法保存属性值,可以考虑转换成一个不可变对象后处理,比如使用字符串表示不同的实例对象。

基本使用的代码中,每个实例对象的属性值实际是保存在共享描述符对象中的。

四、使用描述符完成property、classmethod、staticmethod自定义实现

1、property的自定义实现

# property是一个装饰器,所以底层原理就是把描述符当做一个装饰器并作为类属性存在

class DescName:  # 描述符,后续当做装饰器使用,是一个类装饰器,返回的对象是描述符对象
    def __init__(self, func):  # 首次装饰必须返回一个描述符对象,func是装饰的函数name
        self.get_func = func
        self.set_func = None
        self.delete_func = None  # 以上3个属性用于保存用户定义的属性访问逻辑代码

    def __get__(self, instance, owner):
        return self.get_func(instance)  # 访问get的时候返回用户定义的函数输出

    def setter(self, func):
        self.set_func = func
        return self  # 提供一个setter用于处理set逻辑,setter是一个函数装饰器

    def __set__(self, instance, value):
        return self.set_func(instance, value)  # 访问set的时候调用用户定义函数

    def deleter(self, func):  # 提供deleter函数装饰器处理delete逻辑
        self.delete_func = func
        return self

    def __delete__(self, instance):
        return self.delete_func(instance)  # 访问del的时候调用用户定义函数


class Student:  # 业务类
    def __init__(self, name, age):
        self.__name = name  # 在业务类中隐藏属性
        self.age = age

    @DescName  # 和propery是一样的,返回对象就是一个描述符对象,被name引用,且是类属性
    def name(self):
        return self.__name

    @name.setter  # set设置
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError('必须是str')
        self.__name = value

    @name.deleter  # 删除设置
    def name(self):
        print('你不能删除名字属性')

stu1 = Student('zlw', 26)  # 实例化的时候会自动调用描述符的set
print(stu1.name, stu1.age)  # 打印的时候会调用描述符的get

stu1.name = 'wj'
print(stu1.name, stu1.age)

del stu1.name  # 删除的时候调用描述符的delete

2、classmethod的自定义实现


class DescClassMethod:  # 描述符,用于作为装饰器,提供类方法功能
    def __init__(self, func):
        self.get_func = func
        self.cls = None  # 保存当前类对象

    def __get__(self, instance, owner):  # 被装饰的时候,保存当前类对象,返回描述符对象
        self.cls = owner
        return self

    def __call__(self, *args, **kwargs):  # 类方法一般直接运行,也就是描述符对象被运行
        return self.get_func(self.cls, *args, **kwargs)  # 传入当前类对象,用户就觉得是自动传入


class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def learn(self):
        print(f'{self.name} is learning...')

    @DescClassMethod  # 类方法描述符,让被装饰的函数变成类绑定方法,自动传入类对象
    def show_class(cls, school_name):
        print(f'this is class --> {cls.__name__}')
        print('输入的参数是:', school_name)

stu = Student('zlw', 26)
stu.learn()  # 实例方法
Student.show_class('北京大学')  # 类方法

3、关于实例方法的思考

# 这里是普通的类和一个实例方法定义,不同对象在调用的时候会将自己传入self

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def learn(self):
        print(f'{self.name} is learning...')

stu1 = Student('小明', 26)
stu2 = Student('小红', 25)

stu1.learn()  # '小明' is learning...
stu2.learn()  # '小红' is learning...

通过描述符,我们可以实现一个类似classmethod类方法的功能,这功能可以实现当我们调用类方法的时候,python会自动将类对象传入cls中。那这就引发另一个问题,对于实例方法,我们在通过实例对象调用的时候,也会将实例对象自己传入self中,那这个实现和类方法的实现,是不是一样的思路?
仔细查看描述符的__get__函数可以发现,此函数的参数有instanceowner,这个owner被我们用作cls,那么,instance不就代表实例对象吗?如果我们把istance传入call,不就可以实现类似实例方法的功能了吗?试试看:

4、实例方法的自定义实现

class SelfMethod:  # 描述符,用于处理实例方法功能
    def __init__(self, func):
        self.get_func = func
        self.instance = None  # 保存实例对象

    def __get__(self, instance, owner):
        self.instance = instance  # 保存实例对象
        return self

    def __call__(self, *args, **kwargs):
        return self.get_func(self.instance, *args, **kwargs)  # 将实例对象传入


class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @SelfMethod  # 使用描述符作为装饰器,让learn函数变成实例绑定方法
    def learn(self):
        print(f'{self.name} is learning...')

stu1 = Student('小明', 26)
stu2 = Student('小红', 25)

stu1.learn()  # '小明' is learning...  # 调用的时候,会传入stu1实例 
stu2.learn()  # '小红' is learning...  # 调用的时候,会传入stu2实例

print(stu1.__dict__)
print(Student.__dict__)  # 类中显示learn是一个描述符对象

以上通过描述符可以自定义类似实例方法的效果,自动传入实例给self。不过这种实现方式和真正的实例方法实现(暂时还不知道如何实现的)还是有些不同:

a、自定义实现需要@SelfMethod装饰器,而真正实现是不需要装饰器的,不知道是不是底层做了默认省略的操作
b、Student.__dict__的输出中,自定义的实现表示learn是一个描述符对象,而真正实现的表示是一个function对象
c、打印stu1.learn的时候,自定义实现显示是描述符对象,真正实现表示的是绑定方法对象

嗯。。暂时还没深入研究绑定方法的具体实现,不过可以通过描述符来大概判断出,绑定方法也应该是一个类似封装了实例对象和函数的对象,在被调用的时候也是以func(self, *args, **kw)的方式来调用的。

5、静态方法的自定义实现

class StaticMethod:  # 描述符,用于处理静态方法的类装饰器
    def __init__(self, func):
        self.get_func = func

    def __get__(self, instance, owner):
        return self

    def __call__(self, *args, **kwargs):
        return self.get_func(*args, **kwargs)  # 静态方法就是不提供instance和owner参数传入


class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @StaticMethod  # 描述函数为静态方法
    def show():
        print('这里是静态方法show')

stu1 = Student('小明', 26)
stu2 = Student('小红', 25)

stu1.show()  # 这里是静态方法show
stu2.show()  # 这里是静态方法show

五、总结

1.描述符可以用于处理类级别的属性的访问控制
2.如果描述符处理的是数据属性,那么核心关注点如下
>1、描述符的__get__,__set__,__delete__函数处理数据读取、删除
>2、处理数据属性的描述符对象,可以保存不同实例对象的属性值,使用字典,key是实例对象自身,不过要注意是否可hash
>3、将描述符作为类装饰器对函数进行装饰,可以自定义类似propertyget实现效果,不过注意描述符类中要提供setterdeleter用于处理setdel


描述符还可以用于处理函数属性,核心关注点有如下
>1、描述符类作为类装饰器对指定函数进行装饰并返回描述符对象
>2、返回的描述符对象除了处理描述符三大函数之外,还要提供__call__函数以便调用
>3、使用__call__函数的时候,传入的是instance,就类似实例方法,传入的是owner,就类似类方法classmethod,什么都不传入,就类似静态方法staticmethod


对象属性的解析顺序,比如打印stu.name
0、无条件优先被__getattribute__处理
1、获取对象所属类及MRO列表,自下而上获取name的第一个定义
2、判断此定义的类型
3、如果类型是数据描述符,直接调用数据描述符的__get__,否则下一步
4、如果不是数据描述符,判断是否为:实例属性,即stu.__dict__中是否存在,是的话返回,否则下一步
5、判断类型是否为非数据描述符(也是描述符),如果是,则返回非数据描述符的__get__返回值,否则下一步
6、判断类型是否是普通属性,比如单纯的一个类属性,如果是,则返回此属性值,否则下一步
7、执行__getattr__,期望解析顺序最后给出返回值,没定义就报错

相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
相关文章
|
2月前
|
机器学习/深度学习 前端开发 算法
学习Python需要多久?
【7月更文挑战第6天】学习Python需要多久?
53 5
|
28天前
|
程序员 测试技术 开发工具
豆瓣评分7.9!世界级讲师耗时5年整理出的Python学习手册!
Python是一门流行的开源编程语言,广泛用于各个领域的独立程序与脚本化应用中。它不仅免费、可移植、功能强大,同时相对简单,而且使用起来充满乐趣。从软件业界的任意一角到来的程序员,都会发现Python着眼于开发者的生产效率以及软件质量,因此无论你的项目是大还是小,选择Python都将带来战略性的优势。 今天给小伙伴们分享的这份手册讲述了完整的Python语言,力争满足“语言”和“原理”两个方面的需求,并拥有足够的深度以便实用。废话不多说,下面展示给大家。
|
29天前
|
数据采集 数据可视化 Ruby
GitHub星标破万!Python学习教程(超详细),真的太强了!
Python 是一门初学者友好的编程语言,想要完全掌握它,你不必花上太多的时间和精力。 Python 的设计哲学之一就是简单易学,体现在两个方面: 1. 语法简洁明了:相对 Ruby 和 Perl,它的语法特性不多不少,大多数都很简单直接,不玩儿玄学。 2. 切入点很多:Python 可以让你可以做很多事情,科学计算和数据分析、爬虫、Web 网站、游戏、命令行实用工具等等等等,总有一个是你感兴趣并且愿意投入时间的。
|
1月前
|
机器学习/深度学习 开发者 Python
Python 与 R 在机器学习入门中的学习曲线差异
【8月更文第6天】在机器学习领域,Python 和 R 是两种非常流行的编程语言。Python 以其简洁的语法和广泛的社区支持著称,而 R 则以其强大的统计功能和数据分析能力受到青睐。本文将探讨这两种语言在机器学习入门阶段的学习曲线差异,并通过构建一个简单的线性回归模型来比较它们的体验。
47 7
|
1月前
|
JSON API 开发者
Python学习Get方式通过商品 ID请求 获取拼多多商品详情数据接口
拼多多商品详情数据接口服务使开发者或商家能编程获取平台商品详情,涵盖标题、价格、销量等关键信息,助力市场分析与决策。使用前需注册开发者账号并获取API密钥;构造含商品ID等参数的请求URL后发送至API服务器;接口以JSON格式返回数据。应用场景包括商品销售分析、选品、品牌口碑挖掘及竞品分析,为商家提供强大数据支持。
|
1月前
|
算法 数据挖掘 大数据
深入学习Python的性能优化
【8月更文挑战第9天】深入学习Python性能优化涵盖设定明确目标、运用timeit与cProfile等工具诊断瓶颈、优化代码结构与算法、采用并行/并发技术、利用生成器与第三方库等策略。这是一个持续学习的过程,旨在全面提升代码效率与响应速度。
26 1
|
1月前
|
数据采集 人工智能 数据可视化
【2023年电工杯竞赛】B题 人工智能对大学生学习影响的评价 数学建模方案和python代码
本文介绍了2023年电工杯竞赛B题的数学建模方案和Python代码实现,详细阐述了如何分析调查问卷数据,建立评价指标体系,构建数学模型评估人工智能对大学生学习的影响,并提供了数据预处理、特征编码、可视化分析等代码示例。
37 0
【2023年电工杯竞赛】B题 人工智能对大学生学习影响的评价 数学建模方案和python代码
|
2月前
|
机器学习/深度学习 搜索推荐 TensorFlow
使用Python实现深度学习模型:智能教育与个性化学习
【7月更文挑战第29天】 使用Python实现深度学习模型:智能教育与个性化学习
116 9
|
23天前
|
存储 JSON 测试技术
Python中最值得学习的第三方JSON库
Python中最值得学习的第三方JSON库
|
1月前
|
机器学习/深度学习 人工智能 TensorFlow
神经网络不再是黑魔法!Python带你一步步拆解,让AI学习看得见
【8月更文挑战第3天】神经网络,曾被视为难以触及的黑魔法,现已在Python的助力下变得平易近人。以TensorFlow或PyTorch为“魔法杖”,仅需几行Python代码即可构建强大的AI模型。从零开始,我们将教导AI识别手写数字,利用经典的MNIST数据集。通过数据加载、预处理至模型训练与评估,每个步骤都如精心编排的舞蹈般清晰可见。随着训练深入,AI逐渐学会辨认每个数字,其学习过程直观展现。这不仅揭示了神经网络的奥秘,更证明了任何人都能借助Python创造AI奇迹,共同探索未来的无限可能。
32 2