Part VI. Metaprogramming 地四部分 元编程
Chapter 19. Dynamic attributes and properties 第十九章 动态属性和特性
The crucial importance of properties is that their existence makes it perfectly safe and indeed advisable for you to expose public data attributes as part of your class’s public interface[186].
— Alex Martelli Python contributor and book author
Data attributes and methods are collectively known as attributes in Python: a method is just an attribute that is callable. Besides data attributes and methods, we can also create properties, which can be used to replace a public data attribute with accessor methods (i.e. getter/setter), without changing the class interface. This agrees with the Uniform access principle:
数据属性和方法在Python中统称为属性:方法只是一个可调用的属性。除了数据属性和方法,我们也可以创建特性,它用于使用访问器方法(例如,getter/setter)替换公共的数据属性,而不用改变类的接口。此做法与统一访问原则一致:
All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation[187].
所有由模块提供的服务都应该对统一标记可用,不论它们是通过存储还是通过计算实现它都不会背叛这一原则
Besides properties, Python provides a rich API for controlling attribute access and implementing dynamic attributes. The interpreter calls special methods such as `__getattr__` and `__setattr__` to evaluate attribute access using dot notation, eg. obj.attr. A user-defined class implementing `__getattr__` can implement “virtual attributes” by computing values on the fly whenever somebody tries to read a nonexistent attribute like `obj.no_such_attribute`.
除了特性,Python为了控制属性访问以及执行动态属性而提供了一个富API。解释器使用点号,比如`obj.attr`调用特殊方法比如`__getattr__`和`__setattr__`去计算属性访问。用户定义的类通过快速计算值,执行`__getattr__`可以实现“虚拟属性”,不论合适有人尝试读取一个不存在的属性,就像`obj.no_such_attribute`
Coding dynamic attributes is the kind of metaprogramming that framework authors do. However, in Python the basic techniques are so straightforward that anyone can put them to work, even for everyday data wrangling tasks. That’s how we’ll start this chapter.
编写动态属性也是框架作者所做的元编程的一种。不过,在Python中基础技术都是简洁明了的,
## Data wrangling with dynamic attributes
In the next few examples we’ll leverage dynamic attributes to work with a JSON data feed published by O’Reilly for the OSCON 2014 conference[188].
Example 19-1. Sample records from osconfeed.json; some field contents abbreviated.
{ "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": null, "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" } ] } }
Example 19-1 shows 4 out of the 895 records in the JSON feed. As you can see, the entire data set is a single JSON object with the key "Schedule", and its value is another mapping with four keys: "conferences", "events", "speakers" and "venues". Each of those four keys is paired with a list of records. In Example 19-1 each list has one record, but in the full dataset those lists have dozens or hundreds of records—except "conferences" which holds just the single record shown. Every item in those four lists has a "serial" field which is a unique identifier within the list.
The first script I wrote to deal with the OSCON feed simply downloads the feed, avoiding unnecessary traffic by checking if there is a local copy. This makes sense because OSCON 2014 is history now, so that feed will not be updated.
There is no metaprogramming in Example 19-2, pretty much everything boils down to this expression: json.load(fp), but that’s enough to let us explore the dataset. The osconfeed.load function will be used in the next several examples.
*Example 19-2. osconfeed.py: Downloading osconfeed.json. Doctests are in Example 19-3.*
```python
from urllib.request import urlopen
import warnings
import os
import json
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) `1`with urlopen(URL) as remote, open(JSON, 'wb') as local: `2`local.write(remote.read())
with open(JSON) as fp:
return json.load(fp)
1
Issue a warning if a new download will be made.