流畅的python,Fluent Python 第十九章笔记 (动态属性和特性)

19.1使用动态属性转换数据。

首先去oreilly网站下载一份标准的json数据。

from urllib.request import urlopen
import warnings
import os
import json
# import pprint


URL = 'http://www.oreilly.com/pub/sc/osconfeed'
JSON = 'data/osconfeed.json'


def load():
    # 查寻文件是否存在,不存在去下载。
    if not os.path.exists(JSON):
        msg = 'downloading {} to {}'.format(URL, JSON)
        warnings.warn(msg)
        # with使用了两个上下文管理器,分别用于读取读取和保存文件
        with urlopen(URL) as remote, open(JSON, 'wb') as local:
            local.write(remote.read())

    with open(JSON) as fp:
        return json.load(fp)


if __name__ == '__main__':
    print(load())
    # pprint.pprint(load())

 根据书中的一些方式对该数据进行一系列读取操作。

In [20]: feed = load()                                                                               

In [21]: sorted(feed['Schedule'].keys())                                                             
Out[21]: ['conferences', 'events', 'speakers', 'venues']

In [22]: for key, value in sorted(feed['Schedule'].items()): 
    ...:     print('{:3} {}'.format(len(value), key)) 
    ...:                                                                                             
  1 conferences
494 events
357 speakers
 53 venues

In [23]: feed['Schedule']['speakers'][-1]['name']                                                    
Out[23]: 'Carina C. Zona'

In [24]:                                                                                             

 这种读取,从我的理解为字典套字典套列表最后套字典。

使用动态属性访问json数据。

书中想通过编写一个特定的类,用.的方式读取数据,也就是通过读取对象属性的方法读取数据。

主要使用了__getattr__实现。

from collections import abc
from osconfeed import load

class FrozenJSON:

    def __init__(self, mapping):
        # 创建一个字典的副本,浅拷贝
        self.__data = dict(mapping)

       # 当对象没有该属性时候,进行调用。
    def __getattr__(self, name):
        # 查寻对象属性__data是否有那么属性,有直接返回
        if hasattr(self.__data, name):
            return getattr(self.__data, name)
        else:
            # 尝试读取name索引的value,并调用类方法。
            try:
                return FrozenJSON.build(self.__data[name])
            except KeyError as e:
                raise AttributeError(*e.args) from e

    @classmethod
    # 调用类方法对参数进行判断
    def build(cls, obj):
        '''
        JSON数据标准给格式就是字典与列表的集合类型 
        '''
        # 如果为映射类型返回FrozenJSON实例
        if isinstance(obj, abc.Mapping):
            return cls(obj)
        # 如果为列表,递归将每一个元素传给build
        elif isinstance(obj, abc.MutableSequence):
            return [cls.build(item) for item in obj]
        # 其它情况返回该数据
        else:
            return obj

if __name__ == '__main__':
    l = load()
    feed = FrozenJSON(l)
    print(feed.Schedul)

 这是按照书中的方法做了自己的解释的代码,除了最终数据或者列表能返回数据(已经处理过了),另外情况下,都是返回FrozenJSON的对象。按照书中要求进行一系列操作。

In [8]: from osconfeed import load                                                                   

In [9]: from explore0 import FrozenJSON                                                              

In [10]: raw_feed = load()                                                                           

In [11]: feed = FrozenJSON(raw_feed)                                                                 

In [12]: len(feed.Schedule.speakers)                                                                 
Out[12]: 357

In [13]: for key, value in sorted(feed.Schedule.items()): 
    ...:     print('{:3} {}'.format(len(value), key)) 
    ...:                                                                                             
  1 conferences
494 events
357 speakers
 53 venues

In [14]: feed.Schedule.speakers[-1].name                                                             
Out[14]: 'Carina C. Zona'

In [15]: talk = feed.Schedule.events[40]                                                             

In [16]: type(talk)                                                                                  
Out[16]: explore0.FrozenJSON

In [17]: talk.name                                                                                   
Out[17]: 'There *Will* Be Bugs'

In [18]: talk.speakers                                                                               
Out[18]: [3471, 5199]

In [19]: talk.flavor                                                                                 
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~/study/Fluent_Python/第19章/explore0.py in __getattr__(self, name)
     18             try:
---> 19                 return FrozenJSON.build(self.__data[name])
     20             except KeyError as e:

KeyError: 'flavor'

The above exception was the direct cause of the following exception:

AttributeError                            Traceback (most recent call last)
<ipython-input-19-abf3275fce15> in <module>
----> 1 talk.flavor

~/study/Fluent_Python/第19章/explore0.py in __getattr__(self, name)
     19                 return FrozenJSON.build(self.__data[name])
     20             except KeyError as e:
---> 21                 raise AttributeError(*e.args) from e
     22 
     23     @classmethod

AttributeError: flavor

In [20]: talk.keys()                                                                                 
Out[20]: dict_keys(['serial', 'name', 'event_type', 'time_start', 'time_stop', 'venue_serial', 'description', 'website_url', 'speakers', 'categories'])

In [21]: talk.items()                                                                                
Out[21]: dict_items([('serial', 33950), ('name', 'There *Will* Be Bugs'), ('event_type', '40-minute conference session'), ('time_start', '2014-07-23 14:30:00'), ('time_stop', '2014-07-23 15:10:00'), ('venue_serial', 1449), ('description', 'If you're pushing the envelope of programming (or of your own skills)... and even when you’re not... there *will* be bugs in your code.  Don't panic!  We cover the attitudes and skills (not taught in most schools) to minimize your bugs, track them, find them, fix them, ensure they never recur, and deploy fixes to your users.\r\n'), ('website_url', 'https://conferences.oreilly.com/oscon/oscon2014/public/schedule/detail/33950'), ('speakers', [3471, 5199]), ('categories', ['Python'])])

In [22]:                                                                                             

 重新包装后的对象,本身的属性方法都可以用,当返回的为非JSON数据包含的数据类型时,返回数据。

因为if hasattr(self.__data, name): return getattr(self.__data, name)的存在,可以很方便的调用自己字典类型原来的方法。

扫描二维码关注公众号,回复: 8954477 查看本文章

处理无效属性名

前面写的类,没有对名称为Python关键字的属性做特殊处理。

In [22]: grad = FrozenJSON({'name':'Jim Bo','class':1982})                                           

In [23]: grad.name                                                                                   
Out[23]: 'Jim Bo'

In [24]: grad.class                                                                                  
  File "<ipython-input-24-bb5c99ef29c5>", line 1
    grad.class
             ^
SyntaxError: invalid syntax

 可以通过getattr方法读取属性。

In [25]: getattr(grad, 'class')                                                                      
Out[25]: 1982

当然也可以在初始化数据的时候,查找是否有关键字属性,或者其它不符合要求的属性。

from collections import abc
import keyword

from osconfeed import load

class FrozenJSON:

    def __init__(self, mapping):
        self.__data = {}
        # 扫描所有的keys,发现是关键字,添加一个_
        for key, value in mapping.items():
            if keyword.iskeyword(key):
                key += '_'
            self.__data[key] = value

       # 当对象没有该属性时候,进行调用。
    def __getattr__(self, name):
        # print(name)
        # 查寻对象属性__data是否有那么属性,有直接返回.主要为字典本省的一些属性
        if hasattr(self.__data, name):
            return getattr(self.__data, name)
        else:
            # 尝试读取name索引的value,并调用类方法。
            try:
                return FrozenJSON.build(self.__data[name])
            except KeyError as e:
                raise AttributeError(*e.args) from e

    @classmethod
    # 调用类方法对参数进行判断
    def build(cls, obj):
        '''
        JSON数据标准给格式就是字典与列表的集合类型
        '''
        # 如果为映射类型返回FrozenJSON实例
        if isinstance(obj, abc.Mapping):
            return cls(obj)
        # 如果为列表,递归将每一个元素传给build
        elif isinstance(obj, abc.MutableSequence):
            return [cls.build(item) for item in obj]
        # 其它情况返回该数据
        else:
            return obj

if __name__ == '__main__':
    l = load()
    feed = FrozenJSON(l)
    print(feed.Schedul)
In [26]: from explore1 import FrozenJSON                                                             

In [27]: grad = FrozenJSON({'name':'Jim Bo','class':1982})                                           

In [28]: grad.class_                                                                                 
Out[28]: 1982

一些不符合Python标准的命名方式也不能拿来获取属性比如:

In [29]: x = FrozenJSON({'2be': 'or not'})                                                           

In [30]: x.2be                                                                                       
  File "<ipython-input-30-8694215ab5bd>", line 1
    x.2be
      ^
SyntaxError: invalid syntax

 可以通过s.isidentifier()方法判断该命名是否为正确的Python变量名,如果不符合可以修改它或者直接报错。

使用__new__方式以灵活的方式创建对象。

这个章节让我对__new__有了更加充分的认识,了解到创建对象的时候,其中的不定长参数就是准备实例化里面的参数

__new__不许返回一个实例,返回的实例会作为第一个参数(self)传给__init__方法.

因为调用__init__方法时要传入实例,而且静止返回任何值,所有__init__方法其实是"初始化犯法"

而且__new___返回的实例如果不是该类的实例,解释器不会调用__init__方法。

class T_New:
    # 真正的构造一个类的过程,其中的参数也可以获取到。
    def __new__(cls, *args, **kwargs):
        print(f'this __new__ args  =>{args!r}, kwargs  =>{kwargs!r}')
        count_args = len(args)
        if count_args < 2:
            print(args, kwargs)
            return 'a'
        else:
            return super().__new__(cls)
    # 这里才会实例化类,给实例属性,如果实例不是该类的实例,不会初始化
    def __init__(self,*attr):
        print('my attr is',attr)
        self.attr = attr

    def __repr__(self):
        if hasattr(self, 'attr'):
            return str(self.attr)
        else:
            return self



if __name__ == '__main__':
    t = T_New('xb',)
    print(t)
    t1 = T_New('xb1','xb2')
    print(t1)
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第19章/t.py
this __new__ args  =>('xb',), kwargs  =>{}
('xb',) {}
a
this __new__ args  =>('xb1', 'xb2'), kwargs  =>{}
my attr is ('xb1', 'xb2')
('xb1', 'xb2')

 这是我自己写的测试代码。

下面书中按照__new__的方法取代前面的类方法写法。

from collections import abc
from osconfeed import load

class FrozenJSON:

    def __new__(cls, obj):
        # 判断传入的对象,如果映射类型,正常创建实例
        if isinstance(obj, abc.Mapping):
            return super().__new__(cls)
        # 如果是列表递归调用新建实例
        elif isinstance(obj, abc.MutableSequence):
            return [cls(item) for item in obj]
        # 如果其它返回输入值
        else:
            return obj

    def __init__(self, mapping):
        self.__data = mapping

    def __getattr__(self, name):
        if hasattr(self.__data, name):
            return getattr(self.__data, name)
        else:
            try:
                # 尝试实例该数据,去测试该数据。
                return FrozenJSON(self.__data[name])
            except KeyError as e:
                raise AttributeError(*e.args) from e



if __name__ == '__main__':
    l = load()
    feed = FrozenJSON(l)
    # print(feed.Schedule.speakers[-1].name)

 非常厉害的一种考虑方式,避免了写类方法,直接调用__new__通过读取不同的数据传入类型判断实例输出对象。

猜你喜欢

转载自www.cnblogs.com/sidianok/p/12244524.html
今日推荐