Подтвердить что ты не робот

Python ast.literal_eval и datetime

У меня есть строка "{'datetime': datetime.datetime(2010, 11, 21, 0, 56, 58)}", и я хочу преобразовать ее в объект, который он представляет. Используя ast.literal_eval() дает

ValueError: malformed string; 

поскольку он не позволяет создавать объекты (т.е. вызов datetime). Есть ли способ либо получить ast, чтобы обработать это правильно, либо защитить eval, чтобы предотвратить ввод кода?

4b9b3361

Ответ 1

Следуя идее Игнасио Васкес-Абрамса:

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]

Ответ 2

Вы можете извлечь символы (2010, 11, 21, 0, 56, 58) из строки с помощью regex, передать это ast.literal_eval(), чтобы получить кортеж, а затем передать этот кортеж в datetime.datetime(*a_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:  # any matches?
    args = ast.literal_eval(m.group(1))
    print datetime.datetime(*args)
    # 2010-11-21 00:56:58

Здесь выполняется поиск шаблона "datetime(<comma separated list of integers>)" в строке и передается только список значений целочисленного значения буква в ast.literal_eval() для преобразования в кортеж, который всегда должен быть успешным и устойчив к коду. Я считаю, что это называется "Контекстно-чувствительная оценка строк" ​​или CSSE.

Ответ 3

Вместо того, чтобы писать много кода, не используйте ast, когда вам нужно разбирать datetime objs. Вы можете запустить eval(). BTW обратите внимание, что у вас могут быть проблемы с безопасностью, используя эту функцию, если строка может содержать изворотливые команды 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

Ответ 4

Используйте языковые службы, чтобы скомпилировать его в AST, пройдите AST, убедившись, что он содержит только белые списки node, затем выполните он.

Ответ 5

Я столкнулся с этой проблемой и решил ее, заменив объект datetime такой строкой:

            if mystrobj.__contains__('datetime.datetime('):
                mystrobj = re.sub(r"datetime.datetime([(),0-9 ]*),", r"'\1',", mystrobj)

Ответ 6

Используйте функцию repr (datetime.datetime.utcnow()) для сохранения в вашем dict или вашем файле. Смотри,

>>> import datetime
>>> oarg = datetime.datetime.utcnow()
>>> oarg
datetime.datetime(2013, 2, 6, 12, 39, 51, 709024)
>>> butiwantthis = repr(oarg)
>>> butiwantthis
'datetime.datetime(2013, 2, 6, 12, 39, 51, 709024)'
>>> eval(oarg)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: eval() arg 1 must be a string, bytes or code object
>>> eval(butiwantthis)
datetime.datetime(2013, 2, 6, 12, 39, 51, 709024)

Круто! eval() работает!

Позаботьтесь о импортировании

>>> from datetime import datetime, date, time
>>> oarg = datetime.datetime.utcnow()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'datetime.datetime' has no attribute 'datetime'
>>> oarg = datetime.utcnow()
>>> oarg
datetime.datetime(2013, 2, 6, 12, 41, 51, 870458)
>>> butiwantthis = repr(oarg)
>>> butiwantthis
'datetime.datetime(2013, 2, 6, 12, 41, 51, 870458)'
>>> eval(butiwantthis)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
AttributeError: type object 'datetime.datetime' has no attribute 'datetime'
>>> # wrong import

Perfect!

Ps: Python3.3