• Python描述符


    描述符的定义

    描述符是Python中一种特殊的语法,它是对多个属性利用相同存取逻辑的一种方式。描述符是实现了特定协议的类,这个协议包括实现__get__,__set__和__delete__方法,不过也可以只实现一部分协议。

    描述符的简单例子

    class Quantity(object):
    	def __init__(self, name):
    		self.name = name
    	
    	def __get__(self, instance, owner):
    		return instance.__dict__[self.name]
    	
    	def __set__(self, instance, val):
    		instance.__dict__[self.name] = val 
    	
    
    class Obj(object):
    	weight = Quantity("weight")
    	price = Quantity("price")
    	
    	def __init__(self, weight, price):
    		self.weight = weight
    		self.price = price
    		
    obj = Obj(1,2)
    print obj.weight, obj.__dict__
    

    输出为:
    1 {'price': 2, 'weight': 1}
    在以上的代码中,Quantity就是一个描述符,它实现了__get__、__set__方法,它其实是一个类属性。在访问类的对象时,会优先访问同名描述符的对象,并调用相应的__get__和__set__方法。
    在__get__函数中,参数instance是类的对象,owner是类本身;__set__函数中,instance是类对象,val则是传入的值。描述符中存储了对象成员的名称,通过instance的__dict__来访问和存取相应的对象成员。

    有人会觉得上图中给描述符命名的方法略显繁琐,毕竟名称可能会输入错误,那么有没有不用手动给描述符传入名称的初始化方法呢?当然有咯!直接上代码:

    class Quantity(object):
    	count=0
    	def __init__(self):
    		self.name = '{}'.format(Quantity.count)
    		Quantity.count += 1
    	
    	def __get__(self, instance, owner):
    		return getattr(instance, self.name)
    	
    	def __set__(self, instance, val):
    		setattr(instance, self.name, val) 
    	
    
    class Obj(object):
    	weight = Quantity()
    	price = Quantity()
    	
    	def __init__(self, weight, price):
    		self.weight = weight
    		self.price = price
    		
    obj = Obj(1,2)
    print obj.weight, obj.__dict__, Obj.__dict__
    

    输出为:

    1 {'1': 2, '0': 1} {'__module__': '__main__', 'weight': <__main__.Quantity object at 0x03C79450>, 'price': <__main__.Quantity object at 0x03C79470>, '__dict__': <attribute '__dict__' of 'Obj' objects>, '__weakref__': <attribute '__weakref__' of 'Obj' objects>, '__doc__': None, '__init__': <function __init__ at 0x03C7DD30>}
    

    从中可以看到,我们能给描述符传入任意的名称,当我们访问obj的weight时,会触发Obj的weight描述符的__get__和__set__函数,实际操作的则是obj __dict__中的‘0’号对象。
    另外,通过查看Obj的__dict__对象,我们也能了解其实描述符就是一种类属性。只不过它的函数能传入类的instance作为参数,以此来访问类对象的属性而已。

    覆盖型描述符与非覆盖型描述符

    描述符分为覆盖型和非覆盖型的,覆盖型与非覆盖型的区别在于是否实现了__set__函数。
    实现了__set__函数的话,我们对类对象的属性赋值时,就会优先访问它相应描述符的__set__函数;没有实现__set__函数的话,对类对象成员进行赋值则会将描述符遮盖:

    class Quantity(object):
    	count=0
    	def __init__(self):
    		self.name = '{}'.format(Quantity.count)
    		Quantity.count += 1
    	
    	def __get__(self, instance, owner):
    		print "666"
    		return 2
    	
    	def __set__(self, instance, val):
    	      setattr(instance, self.name, val) 
    	
    
    class Obj(object):
    	weight = Quantity()
    	price = Quantity()
    	
    	def __init__(self, weight, price):
    		pass
    		
    obj = Obj(1,2)
    obj.weight = 3
    print obj.weight, obj.__dict__
    

    输出为:

    666
    2 {'0': 3}
    

    可以看到,当存在覆盖型描述符时,对weight的赋值会直接调用weight描述符的__set__方法,实际更改的是__dict__中的"0"号对象。
    让我们再看看非覆盖型的:

    class Quantity(object):
    	count=0
    	def __init__(self):
    		self.name = '{}'.format(Quantity.count)
    		Quantity.count += 1
    	
    	def __get__(self, instance, owner):
    		print "666"
    		return 2
    	
    
    class Obj(object):
    	weight = Quantity()
    	price = Quantity()
    	
    	def __init__(self, weight, price):
    		pass
    		
    obj = Obj(1,2)
    print obj.weight
    obj.weight = 3
    print obj.weight, obj.__dict__
    

    输出为:

    666
    2
    3 {'weight': 3}
    

    可以看到,当我们只是访问weight时,会调用描述符的__get__方法;而当我们为weight赋值时,程序会在对象的__dict__里创建一个属性,并覆盖描述符。

    类的方法也是一种描述符

    值得一提的是,类中定义的方法其实也是一种描述符。方法自己是function对象,定义了__get__函数(没有定义__set__,因此它们是非覆盖型描述符),当我们调用类对象的方法成员时,__get__函数调用,并返回一个绑定的方法对象(bound method),该对象的__func__成员即是真正引用的原始函数。通过调用绑定方法对象的__call__方法,来调用__func__,最终将函数执行起来。

    最后感谢刘佳卉大佬建议我用Markdown来写博客。

  • 相关阅读:
    “epoll errno (EINTR)4” —— epoll遭遇EINTR(Interrupted system call)
    linux普通用户无法登录mysql,管理员用户却不用密码就能登录
    三小时快速入门Python第五篇--异常处理与迭代器
    三小时快速入门Python第四篇--函数与对象
    三小时快速入门Python第三篇--控制流分支与循环
    三小时快速入门Python第二篇--变量与集合
    三小时快速入门Python第一篇--原始数据类型与操作
    Go语言学习笔记十八-练习篇-Go语言版学生信息管理系统
    Go语言学习笔记十七--Go语言面向对象struct的基本使用
    Go语言学习笔记十六-Go语言init函数
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/11913593.html
Copyright © 2020-2023  润新知