"""

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

#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. java和python中的string和int数据类型的转换

    未经允许,禁止转载!!! 在平时写代码的时候经常会用到string和int数据类型的转换 由于java和python在string和int数据类型转换的时候是不一样的 下面总结了java和python ...

  2. photoshop打造超酷炫火焰人像效果

    效果图看上去非常的酷.制作方法跟火焰字过程差不多.唯一不同的是前期的处理,需要用滤镜把人物轮廓路径找出来,去色后再用制作火焰的过程制作.最后把最好的火焰叠加到人物上面,适当用蒙版控制区域即可.原图 最 ...

  3. CXF创建webservice客户端和服务端

    转 一.CXF的介绍 Apache CXF是一个开源的WebService框架,CXF大大简化了Webservice的创建,同时它继承了XFire的传统,一样可以和spring天然的进行无缝的集成.C ...

  4. <转>ORACLE EBS中查看某个Request的Output File

    由于某些权限的限制,有时候哪怕System Administrator职责也只能看到某个Request信息,但是不能查看它的Output File(在“Requests Summary”窗口中“Vie ...

  5. VS2010/MFC编程入门之三十二(常用控件:标签控件Tab Control 上)

    前面两节鸡啄米讲了树形控件Tree Control,本节开始讲解标签控件Tab Control,也可以称为选项卡控件. 标签控件简介 标签控件也比较常见.它可以把多个页面集成到一个窗口中,每个页面对应 ...

  6. 解决nginx下不能require根目录以外的文件

    我们常规的做法是将统一入口文件.css.js这些放在网站根木,其他php文件放到根目录外部,这个时候nginx访问是require不到的,需要设定一下 1.vi  /usr/local/nginx/c ...

  7. HDU 6390 GuGuFishtion

    题意: 计算: \[\sum\limits_{a = 1}^{m}\sum\limits_{b = 1}^{n} \frac{\varphi(ab)}{\varphi(a)\varphi(b)} (\ ...

  8. 20154312 曾林 ExpFinal CTF Writeup

    0.写在前面 1.不合理的验证方式 2.加密与解密的对抗 3.一个SQL引发的血案 4.管理员的诟病 5.备份信息的泄露 6.svn信息泄露 7.coding 8.平衡权限的威胁 9.文件上传的突破 ...

  9. VS相关设置

    1.显示行号 工具-〉选项-〉文本编辑器-〉语言(比如C#)-〉显示-〉行号 2.“解决方案资源管理器”被拖出来了,无法还原 两种方法:1.窗口-->重置窗口布局2.工具-->导入和导出设 ...

  10. python进程join()函数理解

    Join()是主程序等我这个进程执行完毕了,程序才往下走