如何使用ast.literal_eval()将字符串转换为datetime?

16 投票
6 回答
13649 浏览
提问于 2025-04-16 07:24

我有一个字符串 "{'datetime': datetime.datetime(2010, 11, 21, 0, 56, 58)}",我想把它转换成它所代表的对象。使用 ast.literal_eval() 的结果是:

ValueError: malformed string; 

因为这个方法不允许创建对象(也就是说,它不能处理 datetime 这个调用)。有没有办法让 ast 正确处理这个,或者让 eval 更安全,以防止代码注入的问题?

6 个回答

6

与其写很多代码,当你需要解析日期时间对象时,不要使用ast模块。你可以使用eval()函数。不过要注意,如果字符串里可能包含一些危险的Python命令,使用这个函数可能会有安全问题。

下面是它的工作原理:

>>> x="{'datetime': datetime.datetime(2010, 11, 21, 0, 56, 58)}"
>>> b=eval(x)
>>> b
{'datetime': datetime.datetime(2010, 11, 21, 0, 56, 58)}
>>> b["datetime"].year
2010

祝你玩得开心!:D

10

你可以从字符串中提取出 (2010, 11, 21, 0, 56, 58) 这些字符,方法是使用一种叫做 regex 的工具,然后把提取出来的内容传给 ast.literal_eval(),这样就能得到一个元组,接着再把这个元组传给 datetime.datetime(*that_tuple),就能得到一个日期时间对象。听起来步骤很多,但其实每一步都很简单,而且很安全。

import ast
import datetime
import re

s = "{'datetime': datetime.datetime(2010, 11, 21, 0, 56, 58)}"
m = re.search(r"datetime(\((\d+)(,\s*\d+)*\))", s)
if m:  # Match?
    args = ast.literal_eval(m.group(1))
    print(datetime.datetime(*args))  # -> 2010-11-21 00:56:58

这个方法会在字符串中查找 datetime(<用逗号分隔的整数列表>) 这样的模式,然后只把里面的整数列表传给 ast.literal_eval() 来转换成元组——这个过程总是能成功,并且能防止代码注入攻击。像这样的方法用来防御注入攻击,叫做“上下文敏感字符串评估”或者简称 CSSE


Pietraszek, Tadeusz 和 Chris Vanden Berghe, "通过上下文敏感字符串评估防御注入攻击", 会议论文, 2005

15

接着讨论一下 Ignacio Vazquez-Abrams 的想法:

import ast
import datetime

def parse_datetime_dict(astr,debug=False):
    try: tree=ast.parse(astr)
    except SyntaxError: raise ValueError(astr)
    for node in ast.walk(tree):
        if isinstance(node,(ast.Module,ast.Expr,ast.Dict,ast.Str,
                            ast.Attribute,ast.Num,ast.Name,ast.Load, ast.Tuple)): continue
        if (isinstance(node,ast.Call)
                and isinstance(node.func, ast.Attribute)
                and node.func.attr == 'datetime'): continue
        if debug:
            attrs=[attr for attr in dir(node) if not attr.startswith('__')]
            print(node)
            for attrname in attrs:
                print('    {k} ==> {v}'.format(k=attrname,v=getattr(node,attrname)))
        raise ValueError(astr)
    return eval(astr)

good_strings=["{'the_datetime': datetime.datetime(2010, 11, 21, 0, 56, 58)}"]
bad_strings=["__import__('os'); os.unlink",
             "import os; os.unlink",
             "import(os)", # SyntaxError
             ]

for astr in good_strings:
    result=parse_datetime_dict(astr)    
    print('{s} ... [PASSED]'.format(s=astr))

for astr in bad_strings:
    try:
        result=parse_datetime_dict(astr)
    except ValueError:
        print('{s} ... [REJECTED]'.format(s=astr))
    else:
        sys.exit('ERROR: failed to catch {s!r}'.format(s=astr))

会产生

{'the_datetime': datetime.datetime(2010, 11, 21, 0, 56, 58)} ... [PASSED]
__import__('os'); os.unlink ... [REJECTED]
import os; os.unlink ... [REJECTED]
import(os) ... [REJECTED]

撰写回答