python __getattr__动态属性访问

在普通的字典中访问属性中,不能使用.运算符链式调用下去,现在实现__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__使用,没有去做复杂的实现。

你可能感兴趣的:(python)