Python 面向对象(五) 描述器
使用到了__get__,__set__,__delete__中的任何一种方法的类就是描述器
描述器的定义
一个类实现了__get__,__set__,__delete__中任意一个,这个类就是描述器。
如果只实现了__get__,就叫非数据描述器non-data descriptor
如果同时实现了__get__,__set__,那就叫数据描述器data descriptor。
如果一个类的类属性的值设置为了描述器,那这个类就称为owner(属主)。
回顾下就会发现我们曾用过@property(property是一个类)来将一个方法转为属性,其实property就是一个描述器。
class A:
def __init__(self):
self.a1 = 'a1' class B:
x = A()
def __init__(self):
pass print(B.x.a1)
------运行结果-----
a1
看下执行流程
class A:
def __init__(self):
print('A.init')
self.a1 = 'a1' class B:
x = A()
def __init__(self):
print('B.init')
pass print(B.x.a1)
-----运行结果———
A.init
a1 # B根本就没有实例化
一步一步加,慢慢观察实例的初始化顺序
class A:
def __init__(self):
print('A.init')
self.a1 = 'a1' class B:
x = A()
def __init__(self):
print('B.init')
self.x = 100 print(B.x.a1) b = B()
print(B.x.a1)
print(b.x.a1) # 抛出下面的AttributeError异常,因为按属性的mro搜索顺序来说,b.x也就是实例的x=100,100没有a1属性
-----运行结果-----
A.init
a1
B.init
a1
Traceback (most recent call last):
File "C:/python/1117/description_test1.py", line 16, in <module>
print(b.x.a1)
AttributeError: 'int' object has no attribute 'a1'
实例中可以覆盖非数据描述器
class A:
def __init__(self):
print('A.init')
self.a1 = 'a1'
def __get__(self, instance, owner):
print(self,instance,owner) class B:
x = A()
def __init__(self):
print('B.init')
# self.x = 100 # print(B.x.a1)
print(B.x) # 返回的是NOne b = B()
print(B.x)
print(b.x) #描述器跟类属性有关系
———运行结果——————————
A.init
<__main__.A object at 0x00000271CBDA1668> None <class '__main__.B'>
None
B.init
<__main__.A object at 0x00000271CBDA1668> None <class '__main__.B'>
None
<__main__.A object at 0x00000271CBDA1668> <__main__.B object at 0x00000271CBDA18D0> <class '__main__.B'>
None
class A:
def __init__(self):
print('A.init')
self.a1 = 'a1'
def __get__(self, instance, owner):
print(self,instance,owner) class B:
x = A()
def __init__(self):
print('B.init')
# self.x = 100
self.x= A() # print(B.x.a1)
# print(B.x) # 返回的是NOne b = B()
print(B.x)
print(b.x) #描述器跟类属性有关系
print(b.x.a1)
————运行结果—————
A.init
B.init
A.init
<__main__.A object at 0x000001A6D1031668> None <class '__main__.B'>
None
<__main__.A object at 0x000001A6D1031860>
a1
第一种情况:
A类的类属性的值等于B类的实例,且B类实现了__get__,__set__,__delete__三种方法中任意一个时,也就是说B类是一个描述器,就会触发定义的方法(如__get__) 第二种情况:
如果A类的实例的值等于B类的实例,即使B类是一个描述器,也不会触发其中的三种方法
__set__
class A:
def __init__(self):
print('A.init')
self.a1 = 'a1'
def __get__(self, instance, owner):
print(self,instance,owner)
return self
def __set__(self, instance, value):
print(self,instance,value) class B:
x = A()
def __init__(self):
print('B.init')
self.x = 100 #实例的属性
# self.x= A() b = B()
print(B.x)
print(b.x.a1) print(b.__dict__)
print(B.__dict__)
——————运行结果——————
A.init
B.init
<__main__.A object at 0x00000193299127F0> <__main__.B object at 0x00000193299128D0> 100
<__main__.A object at 0x00000193299127F0> None <class '__main__.B'>
<__main__.A object at 0x00000193299127F0>
<__main__.A object at 0x00000193299127F0> <__main__.B object at 0x00000193299128D0> <class '__main__.B'>
a1
{} #添加了__set__方法之后,b实例的字典没有任何属性
{'__module__': '__main__', '__doc__': None, '__dict__': <attribute '__dict__' of 'B' objects>, '__init__': <function B.__init__ at 0x0000019329902E18>, '__weakref__': <attribute '__weakref__' of 'B' objects>, 'x': <__main__.A object at 0x00000193299127F0>}
添加了一个__set__方法后结果就发生了变化。
结论:
如果一个类的类属性是一个数据描述器的话,你对它的同名属性的操作,相当于操作类属性。
官方的说明是说:
如果一个类的类属性是一个数据描述器的话,在实例中定义了同名的属性时,类属性的查找顺序优先级优于实例属性。
然而,我们在上例中打印的__dict__字典中可以看到,根本不是什么优先级关系,实例的字典中根本就不存在同名的属性,同名的属性只存在于类的__dict__字典中。
所以实例的属性名与类属性名同名时,其实就是在操作类属性。
在实例中定义一个不同名的属性举例测试下:
class A:
def __init__(self):
print('A.init')
self.a1 = 'a1'
def __get__(self, instance, owner):
print(self,instance,owner)
return self
def __set__(self, instance, value):
print(self,instance,value) class B:
x = A()
def __init__(self):
print('B.init')
self.y = 100 #不与类属性同名的属性
# self.x= A() b = B()
print(B.x)
print(b.x.a1)
print(b.y) print(b.__dict__)
print(B.__dict__)
————运行结果—————
A.init
B.init
<__main__.A object at 0x0000012BEE4C17F0> None <class '__main__.B'>
<__main__.A object at 0x0000012BEE4C17F0>
<__main__.A object at 0x0000012BEE4C17F0> <__main__.B object at 0x0000012BEE4C18D0> <class '__main__.B'>
a1
100
{'y': 100} #不与类属性同名时,不会改变属性搜索顺序
{'__init__': <function B.__init__ at 0x0000012BEE4B2E18>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'B' objects>, 'x': <__main__.A object at 0x0000012BEE4C17F0>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'B' objects>}
练习:
1.实现StaticMethod装饰器,完成staticmethod装饰器的功能
2.实现ClassMethod装饰器,完成classmethod装饰器的功能
from functools import partial class StaticMethod: #防止冲突改名
def __init__(self,fn):
self.fn = fn
def __get__(self, instance, owner):
print(self,instance,owner)
return self.fn class ClassMethod:
def __init__(self,fn):
print(fn)
self.fn = fn
def __get__(self, instance, owner):
print(self,instance,owner)
# return self.fn(owner)
return partial(self.fn,owner) #partail偏函数固定某个参数,就返回了一个newfunction新函数 class A:
@StaticMethod
def foo(): #foo = StaticMethod(foo)
print('foo')
@ClassMethod
def bar(cls): #bar=ClassMethod(bar)
print(cls.__name__) f = A.foo
print(f)
f()
print('~~~~~~~~~~~~~')
b = A.bar
print(b)
b() #需要调用必须是函数
-----------运行结果---------
<function A.bar at 0x000001956B9D4840>
<__main__.StaticMethod object at 0x000001956B8728D0> None <class '__main__.A'>
<function A.foo at 0x000001956B9D47B8>
foo
~~~~~~~~~~~~~
<__main__.ClassMethod object at 0x000001956B872940> None <class '__main__.A'>
functools.partial(<function A.bar at 0x000001956B9D4840>, <class '__main__.A'>)
A
与对象绑定的叫方法
与function绑定的就叫函数
普通函数就用@staticmethod
def stcmth()
不想传参就用静态方法
类的方法就用@classmethod
def clsmtd(cls)
类或实例的类(解释器隐含a.__class__)传入cls
传入的是类或实例的类
类方法即使不实例化,通过类也可以直接调用。
实例化之后通过实例也可以调用。
传参时对参数类型进行检查:
第一种方法:
传统方法,在__init__时做条件判断
class Person:
def __init__(self,name:str,age:int):
if not self.checkdata(((name,str),(age,int))):
raise TypeError()
self.name = name
self.age = age
def checkdata(self,params):
print(params)
for dt,tp in params:
# print(dt,tp)
if not isinstance(dt,tp): #第一种
return False
else:
return True
# if type(dt) == tp: #第二种
# continue
# else:
# return False
# else:
# return True p1 = Person('zhangsan',18) #当传入类型不符时抛出TypeError异常,比如传入'18'字符串格式的age就会触发异常
print(p1.__dict__)
print(p1.name)
print(p1.age)
————运行结果————————
(('zhangsan', <class 'str'>), (18, <class 'int'>))
{'name': 'zhangsan', 'age': 18}
zhangsan
18
第二种:
使用数据描述器
class Typed:
def __init__(self):
pass def __get__(self, instance, owner):
pass def __set__(self, instance, value):
print('Typed.__set__:',self,instance,value) class Person:
name = Typed()
age = Typed() def __init__(self,name:str,age:int):
self.name = name
self.age = age p1 = Person('jerry',18)
————运行结果——————
Typed.__set__: <__main__.Typed object at 0x000002852A70FA58> <__main__.Person object at 0x000002852A7217B8> jerry
Typed.__set__: <__main__.Typed object at 0x000002852A721710> <__main__.Person object at 0x000002852A7217B8> 18
将类型作为参数传入Typed类,在__set__方法中做检查:
class Typed:
def __init__(self,type):
self.type = type
pass def __get__(self, instance, owner):
pass def __set__(self, instance, value):
print('Typed.__set__:',self,instance,value)
if not isinstance(value,self.type):
raise ValueError(value) class Person:
name = Typed(str)
age = Typed(int) def __init__(self,name:str,age:int):
self.name = name
self.age = age p1 = Person('jerry',18) #运行正常,说明传入的参数类型正确
------运行结果-------
Typed.__set__: <__main__.Typed object at 0x00000218FDBEFA58> <__main__.Person object at 0x00000218FDC01668> jerry
Typed.__set__: <__main__.Typed object at 0x00000218FDC01390> <__main__.Person object at 0x00000218FDC01668> 18
当传入的参数不正确时就抛出ValueError异常:
p1 = Person('jerry','18')
———运行结果—————
Typed.__set__: <__main__.Typed object at 0x00000261222116A0> <__main__.Person object at 0x0000026122211710> jerry
Traceback (most recent call last):
Typed.__set__: <__main__.Typed object at 0x0000026122211668> <__main__.Person object at 0x0000026122211710> 18
File "C:/python/1117/params_check_test3.py", line 23, in <module>
p1 = Person('jerry','18')
File "C:/python/1117/params_check_test3.py", line 21, in __init__
self.age = age
File "C:/python/1117/params_check_test3.py", line 13, in __set__
raise ValueError(value)
ValueError: 18
在设置值时,Typed类__set__方法拦截到之后判断传入的value和type类型是否一致。
第三种:
装饰器加数据描述器
class Typed:
def __init__(self,name,type):
self.name = name
self.type = type def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self def __set__(self, instance, value):
print(self,instance,value)
if not isinstance(value,self.type):
raise ValueError(value)
instance.__dict__[self.name] = value
# return self import inspect
def typeassert(cls):
params = inspect.signature(cls).parameters
print(params)
for name,param in params.items():
print(param.name,param.annotation)
if param.annotation != param.empty:
setattr(cls,name,Typed(name,param.annotation))
return cls @typeassert
class Person:
# name = Typed(str)
# age = Typed(int) def __init__(self,name:str,age:int):
self.name = name
self.age = age p1 = Person('zhangsan',19)
print(p1.__dict__)
print(p1.name,p1.age)
——————运行结果————————
OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])
name <class 'str'>
age <class 'int'>
<__main__.Typed object at 0x000001E818D91828> <__main__.Person object at 0x000001E818D91898> zhangsan
<__main__.Typed object at 0x000001E818D91940> <__main__.Person object at 0x000001E818D91898> 19
{'name': 'zhangsan', 'age': 19}
zhangsan 19
Python 面向对象(五) 描述器的更多相关文章
- Python中的描述器
21.描述器:Descriptors 1)描述器的表现 用到三个魔术方法.__get__() __set__() __delete__() 方法签名如下: object.__get__(self ...
- python类:描述器Descriptors和元类MetaClasses
http://blog.csdn.net/pipisorry/article/details/50444769 描述器(Descriptors) 描述器决定了对象属性是如何被访问的.描述器的作用是定制 ...
- Python面向对象-@property装饰器
python中,我们可以直接添加和修改属性的值: >>> class Student(object): ... pass ... >>> s = Student() ...
- python 面向对象(五)约束 异常处理 MD5 日志处理
###############################总结###################### 1.异常处理 raise:抛出异常 try: 可能出现错误代码 execpt 异常类 a ...
- python 面向对象五 获取对象信息 type isinstance getattr setattr hasattr
一.type()函数 判断基本数据类型可以直接写int,str等: >>> class Animal(object): ... pass ... >>> type( ...
- Python描述器引导(转)
原文:http://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html 1. Python描述器引导(翻译) 作者: Raymond ...
- Pthon魔术方法(Magic Methods)-描述器
Pthon魔术方法(Magic Methods)-描述器 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.描述器概述 1>.描述器定义 Python中,一个类实现了&quo ...
- (转)面向对象(深入)|python描述器详解
原文:https://zhuanlan.zhihu.com/p/32764345 https://www.cnblogs.com/aademeng/articles/7262645.html----- ...
- Python入门之面向对象编程(四)Python描述器详解
本文分为如下部分 引言——用@property批量使用的例子来引出描述器的功能 描述器的基本理论及简单实例 描述器的调用机制 描述器的细节 实例方法.静态方法和类方法的描述器原理 property装饰 ...
随机推荐
- jquery事件使用方法总结
jquery提供了许多的事件处理函数,学习前端一段时间了,下面对其总结一下,梳理一下知识点. 一.鼠标事件 1. click():鼠标单击事件 $div = $("div") $d ...
- plsql经验之谈
工具你用熟悉了,可以解决实际生活得问题,且是快速的.比如我们要需要表的字段,和注释的时候,直接右击,详情,可以复制. 我们讲个熟悉的场景或者案例,比如,我们需要把已经建立的源表,需要一定的加工和处理, ...
- Nhibernate/Hibernate 使用多参数存儲過程 出現could not execute query,Could not locate named parameter等錯誤解決
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns=" ...
- win10 uwp 获取按钮鼠标左键按下
我们可以使用PointerPressed获得鼠标右键按下,但是我们如何获得左键? 其实UWP已经没有MouseLeftButtonDown,于是我们可以使用一个简单方法去获取鼠标左键按下. 我们在xa ...
- win10 uwp 圆角按钮
本文讲的是如何做圆角按钮,我们在UWP本来的按钮都是矩形,圆角Radius没有,所以本文就用简单方法去做圆角按钮. 我们按钮需要圆角,而自带没有,其实做一个很简单,把原来的按钮变为背景透明,然后使用矩 ...
- mysql5.7在windows不能启动的方法及查看数据库大小命令
1.将mysql目录下的my-default.ini改为my.ini 2.cmd进入mysql的bin目录下 3.执行mysqld --initialize进行初始化(如果mysql目录下已经存在da ...
- [ACdream]女神教你字符串——导字符串
Problem Description 正如大家知道的,女神喜欢字符串,而在字符串中,女神最喜欢回文字符串,但是不是所有的字符串都是回文字符串,但是有一些字符串可以进行“求导”来变成回文字符串. 字符 ...
- 理解JDK1.5的自动装箱拆箱
JDK1.5的升级引入了装箱和拆箱概念,简单说就是为了简化书写. JDK1.5之前,创建Integer对象是需要这么写的 Integer i = new Integer("3") ...
- Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) 解决方案
1.命令行用maven编译项目失败,提示 Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compi ...
- Spring Cloud官方文档中文版-Spring Cloud Config(下)-客户端等
官方文档地址为:http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#_serving_alternative_formats 文中例子我做了 ...