从配置文件中正确获取时间差值的方法

0 投票
2 回答
2613 浏览
提问于 2025-04-17 22:52

我有一个配置文件,里面存储了一些统计数据:迭代次数和时间差对象。我想找到一种既符合Python风格又安全的方法来评估这些值。

这个配置文件的样子是这样的(test_config.cfg):

[Section]

option1 = (6, datetime.timedelta(0, 9, 520000))
option2 = (4, datetime.timedelta(0, 8, 510000))

当我使用 RawConfigParser.set('Section', 'option', (i, t_delta)) 时,time.timedelta() 这个条目就会出现,其中 t_delta 只是两个操作之间的时间间隔,而 i 是迭代的次数。当我从配置中读取这些值时,它们会以字符串的形式返回。我想把它们评估回它们最初的样子。我尝试过的方法可以工作,但我觉得应该有一种更安全、更符合Python风格的方法:

import ConfigParser
import datetime

config = ConfigParser.RawConfigParser()
config.read('test_config.cfg')

stats = config.get('Section', 'option1')

# The obvious way is with eval() but makes me very uncomfortable using it
iterations = eval(stats)[0] # 6
duration = eval(stats)[1] # 0:00:09.520000

# The ugly way is with stripping and splitting
duration_tuple = tuple(int(i) for i in stats.split('(')[-1].strip(')').split(','))
duration = datetime.timedelta(*duration_tuple)
iterations = int(stats.split(',')[0].lstrip('('))
print iterations # 6
print duration # 0:00:09.520000

那么有没有更好的方法呢?最终有没有办法在设置配置时只使用时间差对象的“元组”?比如 (0, 9, 520000) 而不是 datetime.timedelta(0, 9, 520000)。这样我就可以轻松使用 ast.literal_eval() 了。谢谢!

2 个回答

1

使用

RawConfigParser.set('Section', 'option', (i, [t_delta.days, t_delta.seconds, t_delta.microseconds]))

你可以把你的配置文件整理成这样:

[Section]

option1 = [6, [0, 9, 520000]]
option2 = [4, [0, 8, 510000]]

然后你可以使用 json 来解析 config.get 返回的字符串:

import ConfigParser
import datetime
import json

config = ConfigParser.RawConfigParser()
config.read('test_config.cfg')

stats = config.get('Section', 'option1')
stats = json.loads(stats)
iterations = stats[0] # 6
duration = datetime.timedelta(*stats[1]) # 0:00:09.520000
print iterations # 6
print duration # 0:00:09.520000

这其实和使用 ast.literal_eval 的想法是一样的。


要解析一个 JSON 格式的配置文件:

不过,如果可以选择的话,我觉得直接放弃 ConfigParser,只用 JSON 会更简单。例如,如果配置文件看起来是这样的:

{"option2": [4, [0, 8, 510000]], "option1": [6, [0, 9, 520000]]}

那么要把它读回 Python,你可以使用:

import datetime
import json

with open('test_config.cfg', 'r') as f:
    config = json.load(f)
stats = config['option1']
iterations = stats[0] # 6
duration = datetime.timedelta(*stats[1]) # 0:00:09.520000
print iterations # 6
print duration # 0:00:09.520000

要把配置保存到一个 JSON 格式的文件:

config = {'option': [i, [t_delta.days, t_delta.seconds, t_delta.microseconds]}
with open('test_config.json', 'w') as f:
    json.dump(config, f)
0

你可以把时间差(timedelta)转换成秒数(用浮点数表示,这样可以保留小数部分)。

RawConfigParser.set('Section', 'option1', '%s,%s' % (i, t_delta.total_seconds()))

现在你的代码部分看起来是这样的:

[Section]
option1 = (6, 9.520000)
option2 = (4, 8.510000)

而且你可以不使用eval这个函数:

stats = RawConfigParser.get('Section', 'option1').split(',')
iterations = int(stats[0]) # 6
duration = datetime.timedelta(seconds=float(stats[1])) # 0:00:09.520000

(编辑)

这里有一个示例,展示了如何用ast.literal_eval这个方法和split/cast这两种方式来处理。使用literal_eval有点风险,因为不怀好意的人可能会执行你不想让他们执行的代码,但它仍然是一个合理的选择。而split/cast方法对输入的要求更严格,只接受特定格式的数据:

>>> from ConfigParser import RawConfigParser
>>> import datetime
>>> import ast
>>> 
>>> i = 6
>>> t_delta = datetime.timedelta(0, 9, 520000)
>>> 
>>> config = RawConfigParser()
>>> config.add_section('Section')
>>> config.set('Section', 'option_for_eval', (i, t_delta.total_seconds()))
>>> config.set('Section', 'option_for_cast', '%s,%f' % (i, t_delta.total_seconds()))
>>> config.write(open('/tmp/config.ini', 'w'))
>>> 
>>> config = RawConfigParser()
>>> config.read('/tmp/config.ini')
['/tmp/config.ini']
>>> option_for_eval = config.get('Section', 'option_for_eval')
>>> option_for_eval
'(6, 9.52)'
>>> i, t_delta = ast.literal_eval(option_for_eval)
>>> i, t_delta
(6, 9.52)
>>> 
>>> option_for_cast = config.get('Section', 'option_for_cast')
>>> stat = option_for_cast.split(',')
>>> i = int(stat[0])
>>> t_delta = datetime.timedelta(seconds=float(stat[1]))
>>> i, t_delta
(6, datetime.timedelta(0, 9, 520000))
>>> 

撰写回答