【Python】【元编程】【一】动态属性和特性

#19.1 使用动态属性转换数据
"""
#栗子19-2 osconfeed.py:下载 osconfeed.json
from urllib.request import urlopen
import os
import warnings
import json
import sys

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

path = os.path.join(sys.path[0],JSON)
path = path.replace('\\','/')


def load():
if not os.path.exists(path):
msg = 'downloading {} to {}'.format(URL,path)
warnings.warn(msg)
with urlopen(URL) as remote,open(path,'wb') as local:
local.write(remote.read())

with open(path,'rb') as fp:

return json.load(fp)

feed = load()
print(sorted(feed['Schedule'].keys())) #['conferences', 'events', 'speakers', 'venues']
for key,value in sorted(feed['Schedule'].items()):
print('{:3} {}'.format(len(value),key))
'''
1 conferences
494 events
357 speakers
53 venues
'''
print(feed['Schedule']['speakers'][-1]['name']) #Carina C. Zona
print(feed['Schedule']['speakers'][-1]['serial']) #141590
print(feed['Schedule']['events'][40]['name']) #There *Will* Be Bugs
print(feed['Schedule']['events'][40]['speakers']) #[3471, 5199]


#栗子19-5 通过“点儿”来调用属性 把一个 JSON 数据集转换成一个嵌套着 FrozenJSON 对象、列表和简单类型的 FrozenJSON 对象
from urllib.request import urlopen
import os
import warnings
import json
import sys

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

path = os.path.join(sys.path[0],JSON)
path = path.replace('\\','/')


JSON1 = 'data/osconfeed1.json'

path1 = os.path.join(sys.path[0],JSON1)
path1 = path1.replace('\\','/')

def load():
if not os.path.exists(path):
msg = 'downloading {} to {}'.format(URL,path)
warnings.warn(msg)
with urlopen(URL) as remote,open(path,'wb') as local:
local.write(remote.read())

with open(path1,'rb') as fp:

return json.load(fp)

from collections import abc
class FrozenJSON:
def __init__(self, mapping):
self.__data = dict(mapping)



def __getattr__(self, name):
if hasattr(self.__data, name):
return getattr(self.__data, name)

else:
return FrozenJSON.build(self.__data[name])

@classmethod
def build(cls, obj):
if isinstance(obj, abc.Mapping):
return cls(obj)
elif isinstance(obj, abc.MutableSequence):
return [cls.build(item) for item in obj]
else:
return obj


raw_feed = load()
feed = FrozenJSON(raw_feed)
print(len(feed.Schedule.speakers)) #2
print(sorted(feed.Schedule.keys())) #['conferences', 'events', 'speakers', 'venues']
for key,value in feed.Schedule.items():
print('{:3} {}'.format(len(value),key))
'''
1 conferences
1 events
2 speakers
1 venues
'''
print(feed.Schedule.speakers[-1].bio) #dfsdf
print(type(feed.Schedule.events[0])) #<class '__main__.FrozenJSON'> events 列表中的 1 号元素是一个 JSON 对象,现在则变成一个 FrozenJSON 实例
print(feed.Schedule.events[0].aa) #KeyError: 'aa'



#19.1.2 处理无效属性名

from urllib.request import urlopen
import os
import warnings
import json
import sys

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

path = os.path.join(sys.path[0],JSON)
path = path.replace('\\','/')


JSON1 = 'data/osconfeed1.json'

path1 = os.path.join(sys.path[0],JSON1)
path1 = path1.replace('\\','/')

def load():
if not os.path.exists(path):
msg = 'downloading {} to {}'.format(URL,path)
warnings.warn(msg)
with urlopen(URL) as remote,open(path,'wb') as local:
local.write(remote.read())

with open(path1,'rb') as fp:

return json.load(fp)

from collections import abc
class FrozenJSON:
def __init__(self, mapping):
self.__data = dict(mapping)



def __getattr__(self, name):
if hasattr(self.__data, name):
return getattr(self.__data, name)

else:
return FrozenJSON.build(self.__data[name])

@classmethod
def build(cls, obj):
if isinstance(obj, abc.Mapping):
return cls(obj)
elif isinstance(obj, abc.MutableSequence):
return [cls.build(item) for item in obj]
else:
return obj

#grad = FrozenJSON({'name':'JIM Bob','class':1982})
#print(grad.class) #SyntaxError: invalid syntax 因为class是Python的保留字



#因此 初始函数作一下调整

#栗子
from urllib.request import urlopen
import os
import warnings
import json
import sys

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

path = os.path.join(sys.path[0],JSON)
path = path.replace('\\','/')


JSON1 = 'data/osconfeed1.json'

path1 = os.path.join(sys.path[0],JSON1)
path1 = path1.replace('\\','/')

def load():
if not os.path.exists(path):
msg = 'downloading {} to {}'.format(URL,path)
warnings.warn(msg)
with urlopen(URL) as remote,open(path,'wb') as local:
local.write(remote.read())

with open(path1,'rb') as fp:

return json.load(fp)

from collections import abc
import keyword
class FrozenJSON:
def __init__(self, mapping):
self.__data = {}
for key,value in mapping.items():
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.build(self.__data[name])

@classmethod
def build(cls, obj):
if isinstance(obj, abc.Mapping):
return cls(obj)
elif isinstance(obj, abc.MutableSequence):
return [cls.build(item) for item in obj]
else:
return obj


glas = FrozenJSON({'name':'wuxe','class':'1999'})
print(glas.class__) #1999

#19.1.3 使用__new__方法用灵活的方式创建对象
'''
我们通常把__init__称为构造方法,这是从其他语言借鉴过来的术语。其实,用于构建实栗的特殊方法是__new__,这是类方法(使用特殊方式处理,因此不必使用@classmethod装饰器)
。。。必须返回一个实栗。返回的实栗会作为第一个参数(self)传给__init__方法。因为调用 __init__ 方法时要传入实例,而且禁止返回任何
值,所以 __init__ 方法其实是“初始化方法”。真正的构造方法是 __new__。我们几乎不
需要自己编写 __new__ 方法,因为从 object 类继承的实现已经足够了

刚才说明的过程,即从 __new__ 方法到 __init__ 方法,是最常见的,但不是唯一
的。 __new__ 方法也可以返回其他类的实例,此时,解释器不会调用 __init__ 方法

# 构建对象的伪代码
def object_maker(the_class, some_arg):
new_object = the_class.__new__(some_arg)
if isinstance(new_object, the_class):
the_class.__init__(new_object, some_arg)
return new_object
# 下述两个语句的作用基本等效
x = Foo('bar')
x = object_maker(Foo, 'bar')

'''
#栗子19-7 explore2.py:使用 __new__ 方法取代 build 方法,构建可能是也可能不是 FrozenJSON 实例的新对象
from urllib.request import urlopen
import os
import warnings
import json
import sys
from collections import abc

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

path = os.path.join(sys.path[0],JSON)
path = path.replace('\\','/')


JSON1 = 'data/osconfeed1.json'

path1 = os.path.join(sys.path[0],JSON1)
path1 = path1.replace('\\','/')

def load():
if not os.path.exists(path):
msg = 'downloading {} to {}'.format(URL,path)
warnings.warn(msg)
with urlopen(URL) as remote,open(path,'wb') as local:
local.write(remote.read())

with open(path1,'rb') as fp:

return json.load(fp)

from collections import abc
import keyword
class FrozenJSON:


def __new__(cls, arg):
if isinstance(arg,abc.Mapping):
return super().__new__(cls) #默认的行为是委托给超类的 __new__ 方法。这里调用的是 object 基类的 __new__ 方法,把唯一的参数设为 FrozenJSON。
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():
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.build(self.__data[name])

@classmethod
def build(cls, obj):
if isinstance(obj, abc.Mapping):
return cls(obj)
elif isinstance(obj, abc.MutableSequence):
return [cls.build(item) for item in obj]
else:
return obj

'''
以上栗子的缺点:OSCON 的 JSON 数据源有一个明显的缺点:索引为 40 的事件,即名为 'There *Will*
Be Bugs' 的那个,有两位演讲者, 3471 和 5199,但却不容易找到他们,因为提供的是
编号,而 Schedule.speakers 列表没有使用编号建立索引。此外,每条事件记录中都有
venue_serial 字段,存储的值也是编号,但是如果想找到对应的记录,那就要线性搜索
Schedule.venues 列表。接下来的任务是,调整数据结构,以便自动获取所链接的记

'''

#shelve小插曲
'''
shelve类似于一个key-value数据库,可以很方便的用来保存Python的内存对象,其内部使用pickle来序列化数据,简单来说,使用者可以将一个列表、字典、或者用户自定义的类实例保存到shelve中,下次需要用的时候直接取出来,就是一个Python内存对象,不需要像传统数据库一样,先取出数据,然后用这些数据重新构造一遍所需要的对象
'''
import shelve
s = shelve.open('test')
s['x'] = ['a','b']
s['x'].append('c')
print(s['x']) #['a', 'b']

temp = s['x']
temp.append('c')
s['x'] = temp
print(s['x']) #['a', 'b', 'c']




#栗子19-9 schedule1.py:访问保存在 shelve.Shelf 对象里的 OSCON 日程数据 【注意】保证data存在,但是别先创建这个数据库文件
'''标准库中有个 shelve(架子)模块,这名字听起来怪怪的,可是如果知道 pickle(泡
菜)是 Python 对象序列化格式的名字,还是在那个格式与对象之间相互转换的某个模块
的名字,就会觉得以 shelve 命名是合理的。泡菜坛子摆放在架子上,因此 shelve 模块
提供了 pickle 存储方式。
shelve.open 高阶函数返回一个 shelve.Shelf 实例,这是简单的键值对象数据库,背
后由 dbm 模块支持,具有下述特点。
shelve.Shelf 是 abc.MutableMapping 的子类,因此提供了处理映射类型的重要
方法。
此外, shelve.Shelf 类还提供了几个管理 I/O 的方法,如 sync 和 close;它也是
一个上下文管理器。
只要把新值赋予键,就会保存键和值。
键必须是字符串。
值必须是 pickle 模块能处理的对象
'''

from urllib.request import urlopen
import os
import json
import sys
from collections import abc
import keyword
import warnings
import shelve

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

path = os.path.join(sys.path[0],JSON)
path = path.replace('\\','/')


def load():
if not os.path.exists(path):
msg = 'downloading {} to {}'.format(URL,path)
warnings.warn(msg)
with urlopen(URL) as remote,open(path,'wb') as local:
local.write(remote.read())

with open(path,'rb') as fp:

return json.load(fp)



DB_NAME = 'data/schedule1.db'
path1 = os.path.join(sys.path[0],DB_NAME)
path1 = path1.replace('\\','/')
CONFERENCE = 'conference.115'

class Record:
def __init__(self,**kwargs):
self.__dict__.update(kwargs)

def load_db(db):
raw_data = load()
warnings.warn('loading' + DB_NAME)
for collection,rec_list in raw_data['Schedule'].items():
record_type = collection[:-1]
for record in rec_list:
key = '{}.{}'.format(record_type,record['serial'])
record['serial'] = key
db[key] = Record(**record)


db = shelve.open(path1)
if CONFERENCE not in db:
load_db(db)

speaker = db['speaker.157509']
print(type(speaker)) #<class '__main__.Record'>
print(speaker.name) #Robert Lefkowitz



#19.1.5 使用特性获取链接的记录
import warnings
import inspect
import os,sys
DB_NAME = 'data/schedule2.db'
path1 = os.path.join(sys.path[0],DB_NAME)
path1 = path1.replace('\\','/')
CONFERENCE = 'conference.115'

class Record:
def __init__(self,**kwargs):
self.__dict__.update(kwargs) # Python中,可用参数来封装本类的属性

def __eq__(self, other):
if isinstance(other,Record):
return self.__dict__ == other.__dict__
else:
return NotImplemented

class MissingDatabaseError(RuntimeError):
'''需要数据库但没有指定数据库时候跑出。'''

class DbRecord(Record):
__db = None # __db 类属性存储一个打开的 shelve.Shelf 数据库引用
@staticmethod # set_db 是静态方法,以此强调不管调用多少次,效果始终一样。
def set_db(db):
DbRecord.__db = db #即使调用 Event.set_db(my_db), __db 属性仍在 DbRecord 类中设置
@staticmethod
def get_db():
return DbRecord.__db # get_db 也是静态方法,因为不管怎样调用,返回值始终是 DbRecord.__db 引用的对象。
@classmethod # fetch 是类方法,因此在子类中易于定制它的行为
def fetch(cls,ident):
db = cls.get_db()
try:
return db[ident]
except TypeError:
if db is None: #如果捕获到 TypeError 异常,而且 db 变量的值是 None,抛出自定义的异常,说明必须设置数据库
msg = "database not set; call '{}.set_db(my_db)'"
raise MissingDatabaseError(msg.format(cls.__name__))
else:
raise #否则,重新抛出 TypeError 异常,因为我们不知道怎么处理。

def __str__(self):
if hasattr(self,'serial'):
cls_name = self.__class__.__name__
return '<{} serial={!r}>'.format(cls_name,self.serial)
else:
return super().__str__()
__repr__ = __str__

class Event(DbRecord):
@property
def venue(self):
key = 'venue.{}'.format(self.venue_serial)
return self.__class__.fetch(key)

@property
def speakers(self):
if not hasattr(self,'_speaker_objs'):
spkr_serials = self.__dict__['speakers']
fetch = self.__class__.fetch
self._speaker_objs = [fetch('speaker.{}'.format(key)) for key in spkr_serials]
return self._speaker_objs

def __str__(self):
if hasattr(self,'name'):
cls_name = self.__class__.__name__
return '<{}{!r}>'.format(cls_name,self.name)
else:
return super.__str__()

__repr__ = __str__

from urllib.request import urlopen
import json

URL = 'http://www.oreilly.com/pub/sc/osconfeed'
JSON = 'data/osconfeed1.json'
path = os.path.join(sys.path[0],JSON)
path = path.replace('\\','/')


def load():
if not os.path.exists(path):
msg = 'downloading {} to {}'.format(URL,path)
warnings.warn(msg)
with urlopen(URL) as remote,open(path,'wb') as local:
local.write(remote.read())

with open(path,'rb') as fp:

return json.load(fp)



def load_db(db):
raw_data = load()
warnings.warn('loading ' + DB_NAME)
for collection,rec_list in raw_data['Schedule'].items():
record_type = collection[:-1]
cls_name = record_type.capitalize()
cls = globals().get(cls_name,DbRecord) #从模块的全局作用域中获取那个名称对应的对象;如果找不到对象,使用 DbRecord。
if inspect.isclass(cls) and issubclass(cls,DbRecord): # 如果获取的对象是类,而且是 DbRecord 的子类……
factory = cls # ……把对象赋值给 factory 变量。因此, factory 的值可能是 DbRecord 的任何一个子类,具体的类取决于 record_type 的值
else:
factory = DbRecord #否则,把 DbRecord 赋值给 factory 变量
for record in rec_list:
key = '{}.{}'.format(record_type,record['serial'])
record['serial'] = key
db[key] = factory(**record)

import shelve
db = shelve.open(path1)
if CONFERENCE not in db:
load_db(db)

DbRecord.set_db(db)
event = DbRecord.fetch('event.33950')
print(event) #<Event'There *Will* Be Bugs'>
print(event.venue) #<DbRecord serial='venue.1449'>
print(event.venue.name) #Portland 251
for spkr in event.speakers:
print('{0.serial}: {0.name}'.format(spkr))
'''
speaker.3471: Faisal Abid
speaker.5199: DJ Adams
'''
'''
同时这个文件夹下生成三个文件:schedule2.db.bak + schedule2.db.dat + schedule2.db.dir'''



#19.3 特性全解析
'''obj.attr 这样的表达式不会从 obj 开始寻找 attr,而是从
obj.__class__ 开始,而且,仅当类中没有名为 attr 的特性时, Python 才会在 obj 实
例中寻找。这条规则不仅适用于特性,还适用于一整类描述符——覆盖型描述符
(overriding descriptor)。第 20 章会进一步讨论描述符,那时你会发现,特性其实是覆盖
型描述符
'''
class LineItem:
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
@property
def weight(self):
return self.__weight
@weight.setter
def weight(self,value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')

class Class:
data = 'the class data attr'
@property
def prop(self):
return 'the prop value'
#实例属性遮盖类的数据属性
obj = Class()
print(obj.__dict__) #{} 这两个效果一样,都是获取实例属性
print(vars(obj)) #{} 表明没有实例属性
print(obj.data) #the class data attr
obj.data = 'bar'
print(vars(obj)) #{'data': 'bar'}
print(obj.data) #bar
print(Class.data) #the class data attr
#实例属性不会遮盖类特性
print(Class.prop) #<property object at 0x000000000212B688> 直接从 Class 中读取 prop 特性,获取的是特性对象本身,不会运行特性的读值方法。
print(obj.prop) #the prop value 读取 obj.prop 会执行特性的读值方法
#obj.prop = 'foo' #AttributeError: can't set attribute
obj.__dict__['prop'] = 'foo'
print(vars(obj)) #{'data': 'bar', 'prop': 'foo'}
print(obj.prop) #the prop value 特性没被实例属性遮盖
Class.prop = 'baz' #覆盖 Class.prop 特性,销毁特性对象
print(obj.prop) #foo 特性被实例属性遮盖
#新添的类特性遮盖现有的实例属性
print(obj.data) #bar
print(Class.data) #the class data attr
Class.data = property(lambda self:'the "data" prop value') #使用新特性覆盖Class.data
print(obj.data) #the "data" prop value
del Class.data
print(obj.data) #bar


#19.4 定义一个自定义特性工厂函数
''' weight 特性覆盖了 weight 实
例属性,因此对 self.weight 或 nutmeg.weight 的每个引用都由特性函数处理,只有
直接存取 __dict__ 属性才能跳过特性的处理逻辑
'''
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] = 999
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.__dict__['weight'] = 888
self.price = price
def subtotal(self):
return self.weight*self.price

nutmeg = LineItem('Moluccan nutmeg',8,13.95)
print(nutmeg.weight,nutmeg.price) #888 13.95
【解析】通过特性读取 weight 和 price,这会遮盖同名实例属性
a = vars(nutmeg)   #  【解析】a = {dict} {'description':'Moluccan nutmeg','weight':888,'price':13.95}
b = a.items() # 【解析】 b = {dict_items} dict_items([('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 888)])
c = sorted(b) #【解析】 c = {list} <class 'list'>:[('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 888)]
print(c) #[('description', 'Moluccan nutmeg'), ('price', 999), ('weight', 888)] 【解析】使用 vars 函数审查 nutmeg 实例,查看真正用于存储值的实例属性
nutmeg.weight = 777
nutmeg.price = 666
print (nutmeg.weight, nutmeg.price). # 777 666





#19.5 处理属性删除操作
class BlackKnight:
def __init__(self):
self.members = ['an arm','another arm','a leg','another leg']
self.phrases = ["'Tis but a scratch.","It's just a flesh wound.","I'm invincible!","All right,we'll call it a draw"]
@property
def member(self):
print('next member is:')
return self.members[0]
@member.deleter
def member(self):
text = 'BLACK KNIGHT (loses {})\n--{}'
print(text.format(self.members.pop(0),self.phrases.pop(0)))


knight = BlackKnight()
print(knight.member)
'''
next member is:
an arm
'''
del knight.member
'''
BLACK KNIGHT (loses an arm)
--'Tis but a scratch.
'''
del knight.member
'''BLACK KNIGHT (loses another arm)
--It's just a flesh wound.
'''
del knight.member
'''BLACK KNIGHT (loses a leg)
--I'm invincible!
'''
del knight.member
'''BLACK KNIGHT (loses another leg)
--All right,we'll call it a draw
'''

#【备注】如果不加@property,还可以通过member = property(member_getter, fdel=member_deleter)来实现


"""



与此同级有个叫data的文件夹,下面有osconfeed1.json文件,内容如下






{ "Schedule": {
"conferences": [{"serial": 115 }],
"events": [


{
"serial": 37801,
"name": "Racing Change: Accelerating Innovation Through Radical Transparency",
"event_type": "Keynote",

"time_start": "2014-07-23 09:20:00",
"time_stop": "2014-07-23 09:30:00",
"venue_serial": 1525,
"description": "In February of this year, PayPal announced it had hired Danese Cooper as their first Head of Open Source. PayPal? And Open Source? In fact, Open Source is playing a key role in reinventing PayPal engineering as a place where innovation at scale is easy and fun - especially if you like to work in Open Source.",
"website_url": "https://conferences.oreilly.com/oscon/oscon2014/public/schedule/detail/37801",
"speakers": [76338,179599,179595,179435],
"categories": [

"Keynotes"

]
},

{
"serial": "slot_40072",
"name": "Lunch",
"event_type": "break",

"time_start": "2014-07-21 12:30:00",
"time_stop": "2014-07-21 13:30:00",
"venue_serial": 1468,
"categories": [
"Break"
]
},
{
"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\u0026#39;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\u0026#39;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"

]
}

],
"speakers": [

{

"serial": 3471,
"name": "Faisal Abid",
"photo": "https://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_%40user_149868.jpg",
"url": "https://medium.com/@faisalabid",
"position": "Engineer \u0026amp; Entrepreneur ",
"affiliation": "League, Inc.",
"twitter": "FaisalAbid",
"bio": "\u003cp\u003eI am a software engineer, author, teacher and entrepreneur.\u003c/p\u003e\n\u003cp\u003eFrom the hardcore server-side and database challenges to the the front end issues, I love solving problems and strive to create software that can make a difference.\u003c/p\u003e\n\u003cp\u003eI\u0026#8217;m an author published by Manning and O\u0026#8217;Reilly and have also appeared in leading publications with my articles on ColdFusion and Flex.\u003c/p\u003e\n\u003cp\u003eIn my free time I teach Android or Node.js at workshops around the word, speak at conferences such as \u003cspan class=\"caps\"\u003eOSCON\u003c/span\u003e, CodeMotion, \u003cspan class=\"caps\"\u003eFITC\u003c/span\u003e and AndroidTO.\u003c/p\u003e\n\u003cp\u003eCurrently, I\u0026#8217;m the founder of Dynamatik, a design and development agency in Toronto.\u003c/p\u003e\n\u003cp\u003eDuring the days I am a Software Engineer at Kobo working on OS and app level features for Android tablets.\u003c/p\u003e"
},

{

"serial": 5199,
"name": "DJ Adams",
"photo": "https://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_%40user_104828.jpg",
"url": "http://www.pipetree.com/qmacro",
"position": "Architect, Integrator, Author, Bread-Maker",
"affiliation": "Bluefin Solutions Ltd",
"twitter": "qmacro",
"bio": "\u003cp\u003eDJ Adams is an enterprise architect and open source programmer, author, and bread-maker living in Manchester, working as a Principal Consultant for \u003ca href=\"http://www.bluefinsolutions.com\"\u003eBluefin Solutions\u003c/a\u003e. He has a degree in Latin \u0026amp; Greek (Classics) from the University of London, and despite having been \u003ca href=\"http://macdevcenter.com/pub/a/mac/2002/05/14/oreilly_wwdc_keynote.html?page=2\"\u003ereferred to\u003c/a\u003e as an \u003ca href=\"http://radar.oreilly.com/2005/11/burn-in-7-dj-adams.html\"\u003ealpha geek\u003c/a\u003e, can nevertheless tie his own shoelaces and drink \u003ca href=\"https://untappd.com/user/qmacro/\"\u003ebeer\u003c/a\u003e without spilling it.\u003c/p\u003e\n\u003cp\u003eHe has written two books for O\u0026#8217;Reilly, on \u003ca href=\"http://oreilly.com/catalog/9780596002022/\" title=\"XMPP\"\u003eJabber\u003c/a\u003e and on \u003ca href=\"http://shop.oreilly.com/product/9780596005504.do\"\u003eGoogle\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eHe is married to his theoretical childhood sweetheart \u003ca href=\"http://www.pipetree.com/michelleadams/\"\u003eMichelle\u003c/a\u003e, and has a son, \u003ca href=\"http://jcla1.com/\"\u003eJoseph\u003c/a\u003e, of whom he is very proud.\u003c/p\u003e"
}
],

"venues": [

{
"serial": 1449,
"name": "Portland 251",
"category": "Conference Venues"
},

{
"serial": 1578,
"name": "Jupiter Hotel",
"category": "Conference Venues"
}

]
}}














































































































































































































































































































































































































































































































































































































posted @ 2017-11-03 17:27  素人渔芙2017  阅读(368)  评论(0编辑  收藏  举报