一、对象属性的访问控制

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

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__,期望解析顺序最后给出返回值,没定义就报错

python描述符学习的更多相关文章

  1. python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解

     1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过google和阅读源码,现将自己的理解和心得记录下来,也为正在为了该问题 ...

  2. python描述符 descriptor

    descriptor 在python中,如果一个新式类定义了__get__, __set__, __delete__方法中的一个或者多个,那么称之为descriptor.descriptor通常用来改 ...

  3. 杂项之python描述符协议

    杂项之python描述符协议 本节内容 由来 描述符协议概念 类的静态方法及类方法实现原理 类作为装饰器使用 1. 由来 闲来无事去看了看django中的内置分页方法,发现里面用到了类作为装饰器来使用 ...

  4. 【转载】Python 描述符简介

    来源:Alex Starostin 链接:www.ibm.com/developerworks/cn/opensource/os-pythondescriptors/ 关于Python@修饰符的文章可 ...

  5. python描述符descriptor(一)

    Python 描述符是一种创建托管属性的方法.每当一个属性被查询时,一个动作就会发生.这个动作默认是get,set或者delete.不过,有时候某个应用可能会有 更多的需求,需要你设计一些更复杂的动作 ...

  6. Python描述符的使用

    Python描述符的使用 前言 作为一位python的使用者,你可能使用python有一段时间了,但是对于python中的描述符却未必使用过,接下来是对描述符使用的介绍 场景介绍 为了引入描述符的使用 ...

  7. Python描述符 (descriptor) 详解

    1.什么是描述符? python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有 __get__(), __set__(), 和__delete__().如 ...

  8. python描述符和属性查找

    python描述符 定义 一般说来,描述符是一种访问对象属性时候的绑定行为,如果这个对象属性定义了__get__(),__set__(), and __delete__()一种或者几种,那么就称之为描 ...

  9. Iterator Protocol - Python 描述符协议

    Iterator Protocol - Python 描述符协议 先看几个有关概念, iterator 迭代器, 一个实现了无参数的 __next__ 方法, 并返回 '序列'中下一个元素,在没有更多 ...

随机推荐

  1. kubernetes多节点的pod挂载同一个cephfs目录

    一.安装cephfs 方法一: 直接进入deploy目录,执行: ceph-deploy --overwrite-conf mds create ceph01:mds-daemon- 上面的ceph0 ...

  2. linux 下 WebSphere日志中文乱码

    管理控制台--->服务器--->应用程序服务器--->server1--->java和进程管理--->进程定义--->java虚拟机--->将通用jvm参数设 ...

  3. 一)get started with the Quartz project

    官网 http://www.quartz-scheduler.org/ 下载链接 http://www.terracotta.org/download/reflector.jsp?b=tcdistri ...

  4. line-height:150% 和 line-height:1.5

    line-height属性的细节与大多数CSS属性不同,line-height支持属性值设置为无单位的数字.有无单位在子元素继承属性时有微妙的不同. 有单位(包括百分比)与无单位之间的区别有单位时,子 ...

  5. SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)

    首先在shiro配置类中注入rememberMe管理器 /** * cookie对象; * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cooki ...

  6. yum初识

    yum仓库中的元数据文件: primary.xml.gz 所有RPM包的列表: 依赖关系: 每个RPM安装生成的文件列表: filelists.xml.gz 当前仓库中所有RPM包的所有文件列表: o ...

  7. WPF 新手引导

    参考了https://www.cnblogs.com/ZXdeveloper/p/8391864.html,自己随便实现了一个,记录下,比较丑 <Window x:Class="Use ...

  8. WPF自定义控件之列表滑动特效 PowerListBox

    列表控件是应用程序中常见的控件之一,对其做一些绚丽的视觉特效,可以让软件增色不少. 本人网上看过一个视频,是windows phone 7系统上的一个App的列表滚动效果,效果非常炫 现在在WPF上用 ...

  9. WC2019 全国模拟赛第一场 T1 题解

    由于只会T1,没法写游记,只好来写题解了... 题目链接 题目大意 给你一个数列,每次可以任取两个不相交的区间,取一次的贡献是这两个区间里所有数的最小值,求所有取法的贡献和,对 \(10^9+7\) ...

  10. jenkins+gitlab+robot framework 中遇到的坑

    问题一:拉Git源代码时提示无权限 原来之前用的ssh密钥一直都是自己的用户生成的.其实在Jenkins系统使用的都是Jenkins这个系统帐号的. 解决方法: 切换到jenkins这个帐号下生成个新 ...