在普通的字典中访问属性中,不能使用.
运算符链式调用下去,现在实现__getattr__
协议,构建一个类,实现简单版本的json
数据链式访问,来说明动态属性的访问。
先来看原始的方式:
# -*- coding: utf-8 -*-
feed = {
"Schedule":
{
"conferences": [{"serial": 115}],
"events": [
{"serial": 34505,
"name": "Why Schools Don´t Use Open Source to Teach Programming",
"event_type": "40-minute conference session",
"time_start": "2014-07-23 11:30:00",
"time_stop": "2014-07-23 12:10:00",
"venue_serial": 1462,
"description": "Aside from the fact that high school programming...",
"website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505",
"speakers": [157509],
"categories": ["Education"]}
],
"speakers": [
{"serial": 157509,
"name": "Robert Lefkowitz",
"photo": None,
"url": "http://sharewave.com/",
"position": "CTO",
"affiliation": "Sharewave",
"twitter": "sharewaveteam",
"bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..."}
],
"venues": [
{"serial": 1462,
"name": "F151",
"category": "Conference Venues"}
]
}
}
if __name__ == '__main__':
print(feed['Schedule'].keys())
# dict_keys(['conferences', 'events', 'speakers', 'venues'])
print(feed['Schedule']['speakers'][-1]['name'])
# Robert Lefkowitz
示例很简单,但feed['Schedule']['speakers'][-1]['name']
这种句法很冗长。在 JavaScript
中,可以使用 feed.Schedule.speakers[-1].name
获取那个值。在 Python
中,可以实现一个近似字典的类(网上有大量实现),达到同样的效果。我自己实现了 FrozenJSON
类,比大多数实现都简单,因为只
支持读取
,即只能访问数据
。不过,这个类能递归
,自动处理嵌套的映射和列表
。
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from collections import abc
feed = {
"Schedule":
{
"conferences": [{"serial": 115}],
"events": [
{"serial": 34505,
"name": "Why Schools Don´t Use Open Source to Teach Programming",
"event_type": "40-minute conference session",
"time_start": "2014-07-23 11:30:00",
"time_stop": "2014-07-23 12:10:00",
"venue_serial": 1462,
"description": "Aside from the fact that high school programming...",
"website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505",
"speakers": [157509],
"categories": ["Education"]}
],
"speakers": [
{"serial": 157509,
"name": "Robert Lefkowitz",
"photo": None,
"url": "http://sharewave.com/",
"position": "CTO",
"affiliation": "Sharewave",
"twitter": "sharewaveteam",
"bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..."}
],
"venues": [
{"serial": 1462,
"name": "F151",
"category": "Conference Venues"}
]
}
}
class FrozenJSON:
"""一个只读接口,使用属性表示法访问JSON类对象"""
def __init__(self, mapping):
# 使用 mapping 参数构建一个字典。这么做有两个目的:(1) 确保传入的是字典(或者是
# 能转换成字典的对象);(2) 安全起见,创建一个副本。
self.__data = dict(mapping)
def __getattr__(self, name): # 仅当没有指定名称(name)的属性时才调用 __getattr__ 方法
if hasattr(self.__data, name):
# 如果 name 是实例属性 __data 的属性,返回那个属性。调用 keys 等方法就是通过这种
# 方式处理的。
return getattr(self.__data, name)
else:
# 否则,从 self.__data 中获取 name 键对应的元素,返回调用 FrozenJSON.build() 方法
# 得到的结果
return FrozenJSON.build(self.__data[name])
@classmethod
def build(cls, obj): # 这是一个备选构造方法,@classmethod 装饰器经常这么用
if isinstance(obj, abc.Mapping): # 如果 obj 是映射,那就构建一个 FrozenJSON 对象
return cls(obj)
elif isinstance(obj, abc.MutableSequence): # 如果是 MutableSequence 对象,必然是列表,
# 因此,我们把 obj 中的每个元素递归地传给 .build() 方法,构建一个列表
return [cls.build(item) for item in obj]
else: # 如果既不是字典也不是列表,那么原封不动地返回元素
return obj
if __name__ == '__main__':
# print(feed['Schedule'].keys())
# # dict_keys(['conferences', 'events', 'speakers', 'venues'])
# print(feed['Schedule']['speakers'][-1]['name'])
# # Robert Lefkowitz
new_feed = FrozenJSON(feed)
print(new_feed.Schedule.keys())
print(len(new_feed.Schedule.speakers))
# 1
print(new_feed.Schedule.speakers[-1].name)
# Robert Lefkowitz
print(new_feed.name)
# return FrozenJSON.build(self.__data[name])
# KeyError: 'name'
FrozenJSON 类的关键是 __getattr__
方法。我们要记住重要
的一点,仅当无法使用
常规的方式获取属性(即在实例
、类
或超类
中找不到
指定的属性
),解释器才会调用特殊的 __getattr__
方法。
print(new_feed.name)
一行揭露了这个实现的一个小问题:理论上,尝试读取不存在的属性应该抛出 AttributeError
异常,但这里只是说了__getattr__
使用,没有去做复杂的实现。