动态属性和特性
在Python中,数据的属性和处理数据的方法统称属性(attribute)。其实,方法只是可调用的属性。除了这二者外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法(即读值方法和设值方法)修改数据属性。
除了特性,Python还提供丰富的API,用于控制属性的访问权限,以及实现动态属性。使用点号访问属性(obj.attr),Python解释器会调用特殊的方法(如__getattr__
和__setattr__
)计算属性。
使用动态属性转换数据
数据为:
{
"Schedule": {
"conferences": [
{
"serial": 115
}
],
"events":[
{
"serial": 33451,
"name": "Migrating to the Web Using Dart and Polymer - A Guide for Legacy OOP Developers",
"event_type": "40-minute conference session",
"time_start": "2014-07-23 17:00:00",
"time_stop": "2014-07-23 17:40:00",
"venue_serial": 1458,
"description": "The web development platform is massive. With tons of libraries...",
"website_url": "https://conferences.oreilly.com/oscon/oscon2014/public/schedule/detail/33451",
"speakers": [
149868
],
"categories": [
"Emerging Languages"
]
},
],
"speakers":[
{
"serial": 149868,
"name": "Faisal Abid",
"photo": "https://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_%40user_149868.jpg",
"url": "https://medium.com/@faisalabid",
"position": "Google Developer Expert. Senior Engineer at Zoom.ai. ",
"affiliation": "Zoom.ai",
"twitter": "FaisalAbid",
"bio": "\u003cp\u003eI am a software engineer, author, teacher and..."
},
],
"venues":[
{
"serial": 1448,
"name": "Portland Ballroom",
"category": "Conference Venues"
},
]
}
实现一个可以通过feed.Schedule.events[0].name
(feed为反序列后的dict对象)来获取feed['Schedule']['events'][0]['name']
的类。
import json
import keyword
from collections import abc
class FrozenJSON:
"""
一个只读接口,使用属性表示访问JSON对象
"""
def __new__(cls, arg):
if isinstance(arg, abc.Mapping):
return super().__new__(cls)
elif isinstance(arg, abc.MutableSequence):
return [cls(item) for item in arg]
else:
return arg
def __init__(self, mapping):
self.__data = {}
for key, value in mapping.items():
# 判断是否为python中的关键字
if keyword.iskeyword(key):
key += '_'
self.__data[key] = value
def __getattr__(self, name):
if hasattr(self.__data, name):
return getattr(self.__data, name)
else:
return FrozenJSON(self.__data[name])
def load():
with open('osconfeed.json', encoding="utf-8") as fp:
return json.load(fp)
if __name__ == '__main__':
raw_feed = load()
feed = FrozenJSON(raw_feed)
print(vars(feed))
print(feed.Schedule.events[0].name)
分析:
递归的将obj.attr
转换成obj.__data[attr]
使用特性验证属性
创建一个可以读写的特性
def quantity(storage_name):
def qty_getter(instance):
return instance.__dict__[storage_name]
def qty_setter(instance, value):
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')
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
分析:
weight和price都需要是正数,所以用特性工厂函数quantity
来创建特性,其值存储在instance.__dict__[storage_name]
中,内置的property虽然用作装饰器,但它是个类,如下所示:
class property(object):
"""
Property attribute.
fget
function to be used for getting an attribute value
fset
function to be used for setting an attribute value
fdel
function to be used for del'ing an attribute
doc
docstring
Typical use is to define a managed attribute x:
class C(object):
def getx(self): return self._x
def setx(self, value): self._x = value
def delx(self): del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
Decorators make defining new properties or modifying existing ones easy:
class C(object):
@property
def x(self):
"I am the 'x' property."
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
"""
特性会覆盖实例属性
特性都是类变量,但是特性管理的其实是实例属性的存取。 如果实例和所属的类有同名数据属性, 那么实例属性会覆盖(或称遮盖) 类属性——至少通过那个实例读取属性时是这样