"""

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

#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. Selenium之Css Selector使用方法

    什么是Css Selector? Css Selector定位实际就是HTML的Css选择器的标签定位 工具 Css Selector的练习建议使用火狐浏览器,下载插件,FireFinder.Fire ...

  2. [sql]mysql管理手头手册,多对多sql逻辑

    各类dbms排名 cs模型 mysql字符集设置 查看存储引擎,字符集 show variables like '%storage_engine%'; show VARIABLES like '%ma ...

  3. python——asyncio模块实现协程、异步编程

    我们都知道,现在的服务器开发对于IO调度的优先级控制权已经不再依靠系统,都希望采用协程的方式实现高效的并发任务,如js.lua等在异步协程方面都做的很强大. Python在3.4版本也加入了协程的概念 ...

  4. HDU Today(自己的代码不知道哪里错了一直没A抄袭大神的)

    http://acm.hdu.edu.cn/showproblem.php?pid=2112 本题题目意思非常简单,麻烦的就是处理一下字符串,这是我能力欠缺的地方 ;} 先把我有乱有麻烦的错误代码贴上 ...

  5. XMR恶意挖矿案例简析

    前言 数字货币因其技术去中性化和经济价值等属性,逐渐成为大众关注的焦点,同时通过恶意挖矿获取数字货币是黑灰色产业获取收益的重要途径.本文简析通过蜜罐获取的XMR恶意挖矿事件:攻击者通过爆破SSH获取系 ...

  6. [LeetCode] 595. Big Countries_Easy tag: SQL

    There is a table World +-----------------+------------+------------+--------------+---------------+ ...

  7. 使用JDBC+POI把Excel中的数据导出到MySQL

    POI是Apache的一套读MS文档的API,用它还是可以比较方便的读取Office文档的.目前支持Word,Excel,PowerPoint生成的文档,还有Visio和Publisher的. htt ...

  8. 企业中如何批量更改mysql中表的存储引擎?

    一.首先必须熟悉Mysql中有哪些基本的数据库,在mysql中database等价于schema,默认的基本库有四个:mysql,information_schema,performance_sche ...

  9. Python 面试题集锦【315+道题】

    第一部分 Python基础篇(80题) 为什么学习Python? 通过什么途径学习的Python? Python和Java.PHP.C.C#.C++等其他语言的对比? 简述解释型和编译型编程语言? P ...

  10. redis安装 phpredis Jedis 扩展的实现及注意事项,php,java,python相关插件安装实例代码和文档推荐

    redis安装 phpredis Jedis 扩展的实现及注意事项,php,java,python相关插件安装实例代码和文档推荐 1.Redis 官方网站下载: http://redis.io/dow ...