"""

#描述符实例是托管类的类属性;此外,托管类还有自己实例的同名属性

#20.1.1 LineItem类第三版:一个简单的描述符
#栗子20-1 dulkfood_v3.py 使用 Quantity 描述符管理 LineItem 的属性
class Quantity:# 描述符基于协议实现,无需创建子类。
def __init__(self,storage_name):
self.storage_name = storage_name
def __set__(self, instance, value): # instance是托管类实例,不用self是为了不和描述符实例冲突
if value > 0 :
instance.__dict__[self.storage_name] = value #这里,必须直接处理托管实例的 __dict__ 属性;如果使用内置的 setattr 函数,会再次触发 __set__ 方法,导致无限递归。
else:
raise ValueError('value must be > 0')
class LineItem:
weight = Quantity('weight')
price = Quantity('price')
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price truffle = LineItem('White truffle',100,0) #ValueError: value must be > 0 #20.1.2 LineItem第四版:自动获取储存属性的名称
#栗子20-2 bulkfood_v4.py:每个 Quantity 描述符都有独一无二的 storage_name
'''为了生成 storage_name,我们以 '_Quantity#' 为前缀,然后在后面拼接一个整数:
Quantity.__counter 类属性的当前值,每次把一个新的 Quantity 描述符实例依附到
类上,都会递增这个值。在前缀中使用井号能避免 storage_name 与用户使用点号创建
的属性冲突,因为 nutmeg._Quantity#0 是无效的 Python 句法。但是,内置的 getattr
和 setattr 函数可以使用这种“无效的”标识符获取和设置属性,此外也可以直接处理实
例属性 __dict__
'''
class Quantity:
__counter = 0 def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix,index) #
cls.__counter += 1
def __get__(self, instance, owner):
return getattr(instance,self.storage_name) #这里可以使用内置的高阶函数 getattr 和 setattr 存取值,无需使用instance.__dict__,因为托管属性和储存属性的名称不同,所以把储存属性传给getattr 函数不会触发描述符,不会像示例 20-1 那样出现无限递归
def __set__(self, instance, value):
if value > 0 :
setattr(instance,self.storage_name,value)
else:
raise ValueError('value must be > 0') class LineItem:
weight = Quantity()
price = Quantity()
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price cocounts = LineItem('Brazilian cocount',20,17.95)
print(getattr(cocounts,'_Quantity#0'),getattr(cocounts,'_Quantity#1')) #20 17.95
print(getattr(cocounts,'weight'),getattr(cocounts,'price')) #20 17.95
print(cocounts.weight,cocounts.price) #20 17.95
#print(cocounts._Quantity#0) #SyntaxError: unexpected EOF while parsing
''', __get__ 方法有三个参数: self、 instance 和 owner。 owner 参数是托管类(如
LineItem)的引用,通过描述符从托管类中获取属性时用得到。如果使用
LineItem.weight 从类中获取托管属性(以 weight 为例),描述符的 __get__ 方法接
本文档由Linux公社 www.linuxidc.com 整理收到的 instance 参数值是 None。因此,下述控制台会话才会抛出 AttributeError 异

抛出 AttributeError 异常是实现 __get__ 方法的方式之一,如果选择这么做,应该修
改错误消息,去掉令人困惑的 NoneType 和 _Quantity#0,这是实现细节。把错误消息
改成"'LineItem' class has no such attribute" 更好。最好能给出缺少的属性
名,但是在这个示例中,描述符不知道托管属性的名称,因此目前只能做到这样
'''
#print(LineItem.weight) #AttributeError: 'NoneType' object has no attribute '_Quantity#0' #示例 20-3 bulkfood_v4b.py(只列出部分代码):通过托管类调用时, __get__ 方法返回描述符的引用
class Quantity:
__counter = 0 def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix,index) #
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self #如果不是通过实例调用,返回描述符自身
else:
return getattr(instance, self.storage_name)
#这里可以使用内置的高阶函数 getattr 和 setattr 存取值,无需使用instance.__dict__,因为托管属性和储存属性的名称不同,所以把储存属性传给getattr 函数不会触发描述符,不会像示例 20-1 那样出现无限递归
def __set__(self, instance, value):
if value > 0 :
setattr(instance,self.storage_name,value)
else:
raise ValueError('value must be > 0') class LineItem:
weight = Quantity()
price = Quantity()
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price print(LineItem.weight) #<__main__.Quantity object at 0x0000000001ECA710>
br_nuts = LineItem('Brazil nuts',10,34.95)
print(br_nuts.price) #34.95 '''
特性工厂函数与描述符类比较
特性工厂函数若想实现示例 20-2 中增强的描述符类并不难,只需在示例 19-24 的基
础上添加几行代码。 __counter 变量的实现方式是个难点,不过我们可以把它定义
本文档由Linux公社 www.linuxidc.com 整理成工厂函数对象的属性,以便在多次调用之间持续存在,如示例 20-5 所示。
示例 20-5 bulkfood_v4prop.py:使用特性工厂函数实现与示例 20-2 中的描述符
类相同的功能
def quantity(): ➊
try:
quantity.counter += 1 ➋
except AttributeError:
quantity.counter = 0 ➌
storage_name = '_{}:{}'.format('quantity', quantity.counter) ➍
def qty_getter(instance): ➎
return getattr(instance, storage_name)
def qty_setter(instance, value):
if value > 0:
setattr(instance, storage_name, value)
else:
raise ValueError('value must be > 0')
return property(qty_getter, qty_setter)
❶ 没有 storage_name 参数。
❷ 不能依靠类属性在多次调用之间共享 counter,因此把它定义为 quantity 函数
自身的属性。
❸ 如果 quantity.counter 属性未定义,把值设为 0。
❹ 我们也没有实例变量,因此创建一个局部变量 storage_name,借助闭包保持它
的值,供后面的 qty_getter 和 qty_setter 函数使用。
❺ 余下的代码与示例 19-24 一样,不过这里可以使用内置的 getattr 和 setattr 函
数,而不用处理 instance.__dict__ 属性。
那么,你喜欢哪个?示例 20-2 还是示例 20-5 ?
我喜欢描述符类那种方式,主要有下列两个原因。
描述符类可以使用子类扩展;若想重用工厂函数中的代码,除了复制粘贴,很难
有其他方法。
与示例 20-5 中使用函数属性和闭包保持状态相比,在类属性和实例属性中保持
状态更易于理解。
此外,解说示例 20-5 时,我没有画机器和小怪兽的动力。特性工厂函数的代码不依
赖奇怪的对象关系,而描述符的方法中有名为 self 和 instance 的参数,表明里面
涉及奇怪的对象关系。
本文档由Linux公社 www.linuxidc.com 整理总之,从某种程度上来讲,特性工厂函数模式较简单,可是描述符类方式更易扩展,
而且应用也更广泛。
''' #20.1.3 LineItem类第5版:一种新型描述符 [避免商品信息为空,导致无法下单]
#几个描述符类的层次结构。 AutoStorage 基类负责自动存储属性; Validated 类做验证,把职责委托给抽象方法 validate; Quantity 和NonBlank 是 Validated 的具体子类 import abc class AutoStorage:
__counter = 0 def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix,index)
cls.__counter += 1 def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance,self.storage_name) def __set__(self, instance, value):
setattr(instance,self.storage_name,value) class Validated(abc.ABC,AutoStorage):
def __set__(self, instance, value):
value = self.validate(instance,value)
super().__set__(instance,value) @abc.abstractmethod
def validate(self,instance,value):
'''return validated value or raise ValueError''' class Quantity(Validated):
'''a number greater than zero'''
def validate(self,instance,value):
if value <= 0:
raise ValueError('value must be > 0')
return value class NonBlank(Validated):
'''a string with at least one non-space character''' def validate(self,instance,value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value class LineItem:
description = NonBlank()
weight = Quantity()
price = Quantity() def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price def subtotal(self):
return self.weight * self.price #20.2 覆盖型与非覆盖型描述符对比
'''
#Python 贡献者和作者讨论这些概念时会使用不同的术语。覆盖型描述符也叫数据描述符或强制描述符。非覆盖型描述符也叫非数据描述符或遮盖型描述符
#依附在类上的描述符无法控制为类属性赋值的操作。其实,这意味着为类属性赋值能覆盖描述符属性
''' ### 辅助函数,仅用于显示 ###
def cls_name(obj_or_cls):
cls = type(obj_or_cls)
if cls is type:
cls = obj_or_cls
return cls.__name__.split('.')[-1] def display(obj):
cls = type(obj)
if cls is type:
return '<class {}>'.format(obj.__name__)
elif cls in [type(None), int]:
return repr(obj)
else:
return '<{} object>'.format(cls_name(obj)) def print_args(name, *args):
pseudo_args = ', '.join(display(x) for x in args)
print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args)) ### 对这个示例重要的类 ###
class Overriding:
'''也称数据描述符或强制描述符''' def __get__(self, instance, owner):
print_args('get', self, instance, owner) def __set__(self, instance, value):
print_args('set', self, instance, value) class OverridingNoGet:
'''没有``__get__``方法的覆盖型描述符''' def __set__(self, instance, value):
print_args('set', self, instance, value) class NonOverriding:
'''也称非数据描述符或遮盖型描述符''' def __get__(self, instance, owner):
print_args('get', self, instance, owner) class Managed:
over = Overriding()
over_no_get = OverridingNoGet()
non_over = NonOverriding() def spam(self):
print('-> Managed.spam({})'.format(display(self)))
#覆盖型描述符
obj = Managed()
print(obj.over) #-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)
print(Managed.over) #-> Overriding.__get__(<Overriding object>, None, <class Managed>) 【解析】因为没有实例
obj.over = 7 #-> Overriding.__set__(<Overriding object>, <Managed object>, 7)
print(obj.over) #-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)
obj.__dict__['over'] = 8 #跳过描述符,直接通过 obj.__dict__ 属性设值,所以不打印任何内容
print(vars(obj)) #{'over': 8} 【解析】确认值在 obj.__dict__ 属性中,在 over 键名下
print(obj.over) #-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>) 【解析】然而,即使是名为 over 的实例属性, Managed.over 描述符仍会覆盖读取 obj.over这个操作 #没有 __get__ 方法的覆盖型描述符
print(obj.over_no_get) #<__main__.OverridingNoGet object at 0x000000000385F860> 【解析】这个覆盖型描述符没有 __get__ 方法,因此, obj.over_no_get 从类中获取描述符实例
print(Managed.over_no_get) #<__main__.OverridingNoGet object at 0x0000000001EE3A58> 【解析】直接从托管类中读取描述符实例也是如此
obj.over_no_get = 7 #-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)
print(obj.over_no_get) #<__main__.OverridingNoGet object at 0x0000000002203A58> 【解析】因为 __set__ 方法没有修改属性,所以在此读取 obj.over_no_get 获取的仍是托管类中的描述符实例
obj.__dict__['over_no_get'] = 9
print(obj.over_no_get) #9 【解析】现在, over_no_get 实例属性会遮盖描述符,但是只有读操作是如此
obj.over_no_get = 7 #-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)
print(obj.over_no_get) #9 【解析】但是读取时,只要有同名的实例属性,描述符就会被遮盖 # 非覆盖型描述符
obj = Managed()
print(obj.non_over) #-> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)
obj.non_over = 7
print(obj.non_over) #7
print(Managed.non_over) #-> NonOverriding.__get__(<NonOverriding object>, None, <class Managed>)
del obj.non_over
print(obj.non_over) #None #通过类可以覆盖任何描述符
obj = Managed()
Managed.over = 1
Managed.over_no_get = 2
Managed.non_over = 3
print(Managed.over,Managed.over_no_get,Managed.non_over) #1 2 3 【解析】揭示了读写属性的另一种不对等:读类属性的操作可以由依附在托管类上定义有 __get__ 方法的描述符处理,但是写类属性的操作不会由依附在托管类上定义有__set__ 方法的描述符处理
#...若想控制设置类属性的操作,要把描述符依附在类的类上,即依附在元类上。默认情况下,对用户定义的类来说,其元类是 type,而我们不能为 type 添加属性。不过在第 21 章,我们会自己创建元类 #方法是描述符
#方法是非覆盖性描述符
obj = Managed()
print(obj.spam) #<bound method Managed.spam of <__main__.Managed object at 0x000000000385F860>> 【解析】 obj.spam 获取的是绑定方法对象
print(Managed.spam) #<function Managed.spam at 0x00000000038D7B70> 【解析】但是 Managed.spam 获取的是函数
obj.spam = 7
print(obj.spam) #7 【解析】 函数没有实现 __set__ 方法,因此是非覆盖型描述符
'''
obj.spam 和 Managed.spam 获取的是不同的
对象。与描述符一样,通过托管类访问时,函数的 __get__ 方法会返回自身的引用。但
是,通过实例访问时,函数的 __get__ 方法返回的是绑定方法对象:一种可调用的对
象,里面包装着函数,并把托管实例(例如 obj)绑定给函数的第一个参数(即
self),这与 functools.partial 函数的行为一致
''' #20.3 方法是描述符 import collections
class Text(collections.UserString):
def __str__(self):
return 'Text({!r})'.format(self.data) def reverse(self):
return self[::-1] word = Text('forward')
print(word) #Text('forward')
print(word.reverse()) #Text('drawrof')
print(Text.reverse(word))#Text('drawrof') 【解析】在类上调用方法相当于调用函数
print(type(Text.reverse),type(word.reverse)) #<class 'function'> <class 'method'>
print(list(map(Text.reverse,['repaid',(10,20,30),Text('stressed')]))) #['diaper', (30, 20, 10), 'desserts']
print(Text.reverse.__get__(word)) #<bound method Text.reverse of 'forward'> 【解析】 函数都是非覆盖型描述符。在函数上调用 __get__ 方法时传入实例,得到的是绑定到那个实例上的方法
print(Text.reverse.__get__(None,word)) #<function Text.reverse at 0x0000000001E8CD08> 【解析】调用函数的 __get__ 方法时,如果 instance 参数的值是 None,那么得到的是函数本身。
print(word.reverse) #<bound method Text.reverse of 'forward'>
print(word.reverse.__self__) #Text('forward')
print(word.reverse.__func__ is Text.reverse) #True
'''绑定方法的 __func__ 属性是依附在托管类上那个原始函数的引用。
绑定方法对象还有个 __call__ 方法,用于处理真正的调用过程。这个方法会调用
__func__ 属性引用的原始函数,把函数的第一个参数设为绑定方法的 __self__ 属性。
这就是形参 self 的隐式绑定方式。
函数会变成绑定方法,这是 Python 语言底层使用描述符的最好例证。
''' #20.4 描述符用法建议
'''
下面根据刚刚论述的描述符特征给出一些实用的结论。
使用特性以保持简单
  内置的 property 类创建的其实是覆盖型描述符, __set__ 方法和 __get__ 方法都
实现了,即便不定义设值方法也是如此。特性的 __set__ 方法默认抛出
AttributeError: can't set attribute,因此创建只读属性最简单的方式是使用特
性,这能避免下一条所述的问题。
只读描述符必须有 __set__ 方法
  如果使用描述符类实现只读属性,要记住, __get__ 和 __set__ 两个方法必须都定
义,否则,实例的同名属性会遮盖描述符。只读属性的 __set__ 方法只需抛出
AttributeError 异常,并提供合适的错误消息。
Python 为此类异常提供的错误消息不一致。如果试图修改 complex 的 c.real 属性,那么得到的错误消息是
AttributeError: read-only attribute;但是,如果试图修改 c.conjugat(e complex 对象的方法),那么得到
的错误消息是 AttributeError: 'complex' object attribute 'conjugate' is read-only。
用于验证的描述符可以只有 __set__ 方法
  对仅用于验证的描述符来说, __set__ 方法应该检查 value 参数获得的值,如果有
效,使用描述符实例的名称为键,直接在实例的 __dict__ 属性中设置。这样,从实例中
读取同名属性的速度很快,因为不用经过 __get__ 方法处理。参见示例 20-1 中的代码。
仅有 __get__ 方法的描述符可以实现高效缓存
  如果只编写了 __get__ 方法,那么创建的是非覆盖型描述符。这种描述符可用于执
行某些耗费资源的计算,然后为实例设置同名属性,缓存结果。同名实例属性会遮盖描述
符,因此后续访问会直接从实例的 __dict__ 属性中获取值,而不会再触发描述符的
__get__ 方法。
非特殊的方法可以被实例属性遮盖
  由于函数和方法只实现了 __get__ 方法,它们不会处理同名实例属性的赋值操作。
因此,像 my_obj.the_method = 7 这样简单赋值之后,后续通过该实例访问
the_method 得到的是数字 7——但是不影响类或其他实例。然而,特殊方法不受这个问
题的影响。解释器只会在类中寻找特殊的方法,也就是说, repr(x) 执行的其实是
x.__class__.__repr__(x),因此 x 的 __repr__ 属性对 repr(x) 方法调用没有影响。
出于同样的原因,实例的 __getattr__ 属性不会破坏常规的属性访问规则。
实例的非特殊方法可以被轻松地覆盖,这听起来不可靠且容易出错,可是在我使用 Python
的 15 年中从未受此困扰。然而,如果要创建大量动态属性,属性名称从不受自己控制的
数据中获取(像本章前面那样),那么你应该知道这种行为;或许你还可以实现某种机
制,过滤或转义动态属性的名称,以维持数据的健全性。
 示例 19-6 中的 FrozenJSON 类不会出现实例属性遮盖方法的问题,因为那个
类只有几个特殊方法和一个 build 类方法。只要通过类访问,类方法就是安全的,
在示例 19-6 中我就是这么调用 FrozenJSON.build 方法的——在示例 19-7 中替换成
__new__ 方法了。 Record 类(见示例 19-9 和示例 19-11)及其子类也是安全的,因
为只用到了特殊的方法、类方法、静态方法和特性。特性是数据描述符,因此不能被
实例属性覆盖。
讨论特性时讲了两个功能,这里讨论的描述符还未涉及,结束本章之前我们来讲讲:文档
和对删除托管属性的处理
''' """

【Python】【元编程】【二】【描述符】的更多相关文章

  1. python高级编程之描述符与属性02

    # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #元描述符 #特点是:使用宿主类的一个或者多个方法来执行一个任务,可 ...

  2. python高级编程之描述符与属性03

    # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #属性Property #提供了一个内建描述符类型,它知道如何将一个 ...

  3. Python元编程

    简单定义"元编程是一种编写计算机程序的技术,这些程序可以将自己看做数据,因此你可以在运行时对它进行内审.生成和/或修改",本博参考<<Python高级编程>> ...

  4. python元编程(metaclass)

    Python元编程就是使用metaclass技术进行编程,99%的情况下不会使用,了解即可. Python中的类和对象 对于学习Python和使用Python的同学,你是否好奇过Python中的对象究 ...

  5. Python 元编程 - 装饰器

    Python 中提供了一个叫装饰器的特性,用于在不改变原始对象的情况下,增加新功能或行为. 这也属于 Python "元编程" 的一部分,在编译时一个对象去试图修改另一个对象的信息 ...

  6. python多进程编程(二)

    进程同步(锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理 part1:多个进 ...

  7. Python中属性和描述符的简单使用

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

  8. 如何正确地使用Python的属性和描述符

    关于@property装饰器 在Python中我们使用@property装饰器来把对函数的调用伪装成对属性的访问. 那么为什么要这样做呢?因为@property让我们将自定义的代码同变量的访问/设定联 ...

  9. Python 为什么要使用描述符?

    学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, Descriptor(描述符)特性可以排得上号. 描述符 是Python 语言独有的特性,它不仅在应用层使用,在语言的 ...

  10. Python并发编程二(多线程、协程、IO模型)

    1.python并发编程之多线程(理论) 1.1线程概念 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于 ...

随机推荐

  1. python中各种数据类型

    数字类型 整型int 作用:年纪,等级,身份证号,qq号等与整型数字有关 定义: age=10 #本质age=int(10) 浮点型float 作用:薪资,身高,体重等与浮点数相关 salary=3. ...

  2. latex中文模板

    \documentclass[UTF8,a4paper,10pt, twocolumn]{ctexart} \usepackage[left=2.50cm, right=2.50cm, top=2.5 ...

  3. 2018-2019-2 网络对抗技术 20165324 Exp3:免杀原理与实践

    2018-2019-2 网络对抗技术 20165324 Exp3:免杀原理与实践 免杀原理及基础问题回答 免杀 1. 一般是对恶意软件做处理,让它不被杀毒软件所检测.也是渗透测试中需要使用到的技术. ...

  4. iOS 网易彩票-6设置模块三(常用小功能)

    该篇文章中,用到很多iOS开发过程中常用的小功能,当前只是将这些功能集成到网易彩票的设置中.iOS-常用小功能介绍,请参考我的另一篇文章: iOS 常用小功能 总结:http://www.cnblog ...

  5. 剑指offer3

    输入一个链表,从尾到头打印链表每个节点的值. 思路:首先借助一个栈,遍历链表中的每一个值,然后存储到栈中,利用栈的先进后出特点,然后添加到数组中返回. package demo3; import ja ...

  6. swoole线程和进程

    pstree -a | grep php |   |       `-php server.php   主进程      |   |           |-php server.php   管理线程 ...

  7. 下载mysql的源码包

  8. django学习网站

    http://www.ziqiangxuetang.com/django/django-qrcode.html

  9. VS2010/MFC编程入门之三十七(工具栏:工具栏的创建、停靠与使用)

    鸡啄米在上一节教程中讲了工具栏资源及CToolBar类,本节继续讲解工具栏的相关知识,主要内容包括工具栏的创建.停靠与使用. 工具栏的使用 上一节中鸡啄米提到过,一般情况下工具栏中的按钮在菜单栏中都有 ...

  10. Root :: AOAPC I: Beginning Algorithm Contests (Rujia Liu) Volume 7. Graph Algorithms and Implementation Techniques

    uva 10803 计算从任何一个点到图中的另一个点经历的途中必须每隔10千米 都必须有一个点然后就这样 floy 及解决了 ************************************* ...