我正在从Bloomberg的Open Symbology下载主数据表。 csv有我不感兴趣的列。
问题
是否有高效/ Pythonic方法从csv文件中找到的列子集生成namedtuple实例?
我尝试了什么
我目前的流程(下面的Python 3.3代码)如下:
这闻起来效率很低。
from csv import reader
from collections import namedtuple
from datetime import date
from io import BytesIO
from urllib.request import urlopen
from urllib.error import HTTPError
from zipfile import ZipFile
def bsym_records(sector, security_type, file_date):
"""Yield BSYMRecord for given sector and security type."""
template = 'http://bdn-ak.bloomberg.com/precanned/{s}_{t}_{d}.txt.zip'
url = template.format(s=sector, t=security_type, d=file_date)
response = urlopen(url)
zipfile = ZipFile(BytesIO(response.read()))
for filename in zipfile.namelist():
with zipfile.open(filename) as f:
line = f.readline().decode('utf-8')
headers = line.strip().replace(' ', '_').split('|')
TempRecord = namedtuple('BSYMRecord', headers)
while True:
line = f.readline().decode('utf-8')
if line[0] == '#':
break
t = TempRecord._make(line.strip().split('|'))
yield reduce_bsym_record(t)
BSYMRecord = namedtuple('BSYMRecord', ['name',
'ticker',
'pricing_source',
'security_type',
'market_sector',
'BBGID',
'BBGID_composite',
'BSID',
'unique_id'])
def reduce_bsym_record(record):
"""Eliminate non-essential fields."""
return BSYMRecord._make((record.NAME,
record.ID_BB_SEC_NUM_DES,
record.FEED_SOURCE,
record.SECURITY_TYP,
record.MARKET_SECTOR_DES,
record.ID_BB_GLOBAL,
record.COMPOSITE_ID_BB_GLOBAL,
record.ID_BB_SEC_NUM_SRC,
record.ID_BB_UNIQUE))
答案 0 :(得分:3)
您当前正在导入csv
模块但未使用它。如果 使用它,则可以使用csv.DictReader
类为文件中的每一行创建字典而不是列表。您可以使用关键字参数构造namedtuple
,但它不会忽略虚假参数。所以你仍然需要手动过滤它们 - 但是你现在可以用dict理解来做,而不是一个不同的命名元组:
for line in csvfile:
yield BSYMRecord(**{k:v for k,v in line if k in BSYMRecord._fieldnames})
诀窍是首先设置DictReader。它需要一个类似文件的对象来产生字符串; ZipFile.open
提供了一个类似文件的对象,它产生 bytes ,并且无法进行编码。 codecs模块在这里得到了解决 - 您可以获得一个StreamReader,它可以透明地将utf8字节解码为字符串,如下所示:
import codecs
utf8 = codecs.lookup('utf8').streamreader
并像这样使用它:
for filename in zipfile.namelist():
with zipfile.open(filename) as f:
csvfile = csv.DictReader(utf8(f))
for line in csvfile:
yield BSYMRecord(**{k:v for k,v in line if k in BSYMRecord._fieldnames})
答案 1 :(得分:1)
您可以使用index
根据标题从每行中选择所需的值:
fields = ["NAME", "ID_BB_SEC_NUM_DES", ...]
# ...
headers = line.strip().replace(' ', '_').split('|')
indices = [headers.index(field) for field in fields)
while True:
# ...
line = line.strip().split('|')
yield BSYMRecord._make((line[i] for i in indices))
这可以保护您当前的保护,使其不受更改字段和单个字段定义的影响,但这意味着您无需为每行创建TempRecord
。