内置的property经常用作装饰器,但它其实是一个类。在Python中,函数和类通常可以互换,因为二者都是可调用对象,而且没有实例化的new运算符,所以调用构造方法和调用工厂函数没有区别,只要能返回新的可调用对象,代替被装饰的函数,二者都可用作装饰器

property初始化方法(__init__)的完整签名如下:

property(fget=None, fset=None, fdel=None, doc=None)

所有的参数都是可选的,如果没有把函数传给某个参数,那么得到的特性对象就不允许执行相应的操作

class LineItem:

    def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price def subtotal(self):
return self.weight * self.price def get_weight(self): # <1>
return self.__weight def set_weight(self, value): # <2>
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0') weight = property(get_weight, set_weight) # <3>

  

  1. 普通的读值方法
  2. 普通的设值方法
  3. 构建property对象,然后赋值给公开的类属性

测试LineItem这个类

line = LineItem("Golden raisins", 10, 6.5)
print("<1>:", line.subtotal()) # <1>
line.weight = -9

  

运行结果:

<1>: 65.0
Traceback (most recent call last):
……
ValueError: value must be > 0

  

当然,我们可以写成另外一种形式,也是可以通过方法来读取和设置公开属性的

class LineItem:

    def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price def subtotal(self):
return self.weight * self.price @property
def weight(self):
return self.__weight @weight.setter
def weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')

  

特性都是类属性,但是特性管理其实是实例属性的存取,如果实例和所属的类有同名的属性,那么在获取实例读取属性时,实例属性会覆盖类属性

class Class:
data = "the class data attr" @property
def prop(self):
return "the prop value" obj = Class()
print("<1>:", vars(obj)) # <1>
print("<2>:", obj.data) # <2>
obj.data = "bar" # <3>
print("<4>:", vars(obj)) # <4>
print("<5>:", obj.data) # <5>
print("<6>:", Class.data) # <6>

  

运行结果:

<1>: {}
<2>: the class data attr
<4>: {'data': 'bar'}
<5>: bar
<6>: the class data attr

  

  1. vars()返回obj.__dict__属性,表明实例没有属性
  2. 读取obj.data,获取的是Class.data的值
  3. 为obj.data赋值,为实例创建一个属性
  4. 再次用vars()查看实例的属性
  5. 现在读取obj.data,实例属性覆盖类属性
  6. 打印类属性Class.data

下面,尝试覆盖obj的prop特性

print("<1>:", Class.prop)  # <1>
print("<2>:", obj.prop) # <2>
obj.prop = "hello world"

    

运行结果:

<1>: <property object at 0x00000000004805E8>
<2>: the prop value
Traceback (most recent call last):
obj.prop = "hello world"
AttributeError: can't set attribute

  

  1. 直接从Class读取prop特性,获取的是特性对象本身,不会运行特性的读值方法
  2. 读取obj.prop会执行特性的读值方法
  3. 尝试设置pro实例属性,结果失败
obj.__dict__["prop"] = "hello world"  # <1>
print("<2>:", vars(obj)) # <2>
print("<3>:", obj.prop) # <3>
Class.prop = "baz" # <4>
print("<5>:", obj.prop) # <5>

  

运行结果:

<2>: {'prop': 'hello world'}
<3>: the prop value
<5>: hello world

  

  1. 上一个例子,通过设置obj.prop会抛出异常,于是我们尝试通过obj.__dict__设置实例属性
  2. 可以看到上一个步骤设置成功
  3. 然而,读取prop时仍会运行特性的读值方法,特性没被实例属性覆盖
  4. 覆盖Class.prop特性,销毁特性对象
  5. 现在,obj.prop读取的是实例属性。Class.prop不是特性了,因此不会再覆盖obj.prop

定义一个特性工厂函数

我们将定义一个名为quantity的特性函数,替我们管理之前LineItem这个类中的weight和price这两个属性,要求这两个属性都必须大于0

def quantity(storage_name):
def qty_getter(instance): # <3>
return instance.__dict__[storage_name] def qty_setter(instance, value): # <4>
if value > 0:
instance.__dict__[storage_name] = value
else:
raise ValueError('value must be > 0') return property(qty_getter, qty_setter) class LineItem:
weight = quantity('weight') # <1>
price = quantity('price') def __init__(self, description, weight, price):
self.description = description
self.weight = weight # <2>
self.price = price def subtotal(self):
return self.weight * self.price

    

  1. 使用工厂函数把weight和price定义为类属性
  2. 这里,特性已经激活,确保不能把weight设置为负数或零
  3. qty_getter函数的第一个参数可以命名为self,但是这样做很奇怪,因为qty_getter函数不在类的定义体中,instance指代LineItem实例,在qty_getter中,从LineItem实例的__dict__获取想要的属性,然后返回
  4. 定义qty_setter函数,第一个参数instance也是LineItem实例,然后将值存入instance.__dict__中,这也是为了跳过特性,避免递归循环最后栈溢出

测试工厂函数

nutmeg = LineItem("Moluccan nutmeg", 8, 13.95)
print("<1>:", nutmeg.weight, nutmeg.price) # <1>
print("<2>:", sorted(vars(nutmeg).items())) # <2>

  

运行结果:

<1>: 8 13.95
<2>: [('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 8)]

  

定义特性时,可以使用@attr.deleter装饰器包装一个方法,负责删除特性管理的属性

class BlackKnight:

    def __init__(self):
self.members = ['an arm', 'another arm',
'a leg', 'another leg']
self.phrases = ["'Tis but a scratch.",
"It's just a flesh wound.",
"I'm invincible!",
"All right, we'll call it a draw."] @property
def member(self):
print('next member is:')
return self.members[0] @member.deleter
def member(self):
text = 'BLACK KNIGHT (loses {})\n-- {}'
print(text.format(self.members.pop(0), self.phrases.pop(0)))

  

测试删除特性

b = BlackKnight()
del b.member
del b.member
del b.member
del b.member

  

运行结果:

BLACK KNIGHT (loses an arm)
-- 'Tis but a scratch.
BLACK KNIGHT (loses another arm)
-- It's just a flesh wound.
BLACK KNIGHT (loses a leg)
-- I'm invincible!
BLACK KNIGHT (loses another leg)
-- All right, we'll call it a draw.

  

  

Python动态属性和特性(二)的更多相关文章

  1. Python动态属性和特性(一)

    在Python中,数据的属性和处理数据的方法统称为属性.其实,方式只是可调用的属性.除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法(即读取值和设置值方法) ...

  2. Python 基础 面向对象之二 三大特性

    Python 基础 面向对象之二 三大特性 上一篇主要介绍了Python中,面向对象的类和对象的定义及实例的简单应用,本篇继续接着上篇来谈,在这一篇中我们重点要谈及的内容有:Python 类的成员.成 ...

  3. Python的高级特性7:闭包和装饰器

    本节跟第三节关系密切,最好放在一起来看:python的高级特性3:神奇的__call__与返回函数 一.闭包:闭包不好解释,只能先看下面这个例子: In [23]: def outer(part1): ...

  4. Python基础学习(二)

    前一段时间学习了Python数据类型,语句和函数,目前书写python的新特性,继续练手!!!! 一.切片 之前我们从python的list 或者 tuple中取得元素都是这样写,显然不够灵活 lis ...

  5. python的高级特性:切片,迭代,列表生成式,生成器,迭代器

    python的高级特性:切片,迭代,列表生成式,生成器,迭代器 #演示切片 k="abcdefghijklmnopqrstuvwxyz" #取前5个元素 k[0:5] k[:5] ...

  6. 进击的Python【第十二章】:mysql介绍与简单操作,sqlachemy介绍与简单应用

    进击的Python[第十二章]:mysql介绍与简单操作,sqlachemy介绍与简单应用 一.数据库介绍 什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,每个数 ...

  7. python基础篇(二)

    PYTHON基础篇(二) if:else,缩进 A:if的基础格式和缩进 B:循环判断 C:range()函数和len()函数 D:break,contiue和pass语句 for,while循环 函 ...

  8. python异步IO编程(二)

    python异步IO编程(二) 目录 开门见山 Async IO设计模式 事件循环 asyncio 中的其他顶层函数 开门见山 下面我们用两个简单的例子来让你对异步IO有所了解 import asyn ...

  9. Python的单元测试(二)

    title: Python的单元测试(二) date: 2015-03-04 19:08:20 categories: Python tags: [Python,单元测试] --- 在Python的单 ...

随机推荐

  1. 解决mysql本地数据库不能用ip访问的问题

    [转]http://gone18611.blog.163.com/blog/static/1851943120104705244116/ MYSQL数据库缺省安装后,其默认用户名ROOT如果只能以&l ...

  2. where whereis locate find 的用法

    1.where :where ifconfig.用来搜索命令,显示命令是否存在以及路径在哪 2.whereis:whereis vim .用来搜索程序名,而且只搜索二进制文件(参数-b).man说明文 ...

  3. Eucalyptus——EC2的开源实现(转载)

    Eucalyptus[22]是加利福尼亚大学的 Daniel Nurmi 等人实现的,是一个用于实现云计算的开源软件基础设施.Eucalyptus 是 Amazon EC2 的一个开源实现,它与 EC ...

  4. iOS Programming Autorotation, Popover Controllers, and Modal View Controllers

    iOS Programming Autorotation, Popover Controllers, and Modal View Controllers  自动旋转,Popover 控制器,Moda ...

  5. mongodb安全整理

    本文大都网上参考的,我只是整理了一下 一默认配置情况 1.MongoDB安装时不添加任何参数,默认是没有权限验证的,任何用户都可以登录进来,而且登录的用户可以对数据库任意操作而且可以远程访问数据库,需 ...

  6. Bot Framework:Activity类简明指南

    Bot Framework相关文档:https://docs.botframework.com/en-us/csharp/builder/sdkreference/attachments.html B ...

  7. python基础教程总结11——图形用户界面GUI

    1. 丰富的平台 工具包 描述 Tkinter 使用Tk平台.很容易得到.半标准. wxpython 基于wxWindows.跨平台越来越流行. PythonWin 只能在Windows上使用.使用了 ...

  8. java中的String对象的创建及堆栈的解释

    java中的string真的是很令人头疼呢!!! 请看这里 看这里

  9. ubuntu k8s 命令补全

    apt install bash-completion // locate bash_completion source /usr/share/bash-completion/bash_complet ...

  10. TCP/UDP 协议介绍

    TCP/IP五层网络结构模型 物理层:物理层建立在物理通信介质的基础上,作为系统和通信介质的接口,用来实现数据链路实体间透明的比特 (bit) 流传输.只有该层为真实物理通信,其它各层为虚拟通信 数据 ...