У контекстно-свободной грамматики обычно есть древовидная структура, а у функциональных программ - древовидная структура. Я не утверждаю, что следующее решит все ваши проблемы, но это хороший шаг в этом направлении, если вы уверены, что не хотите использовать что-то вроде SQLite3
.
from functools import partial
def select_keys(keys, from_):
return ({k : fun(v, row) for k, (v, fun) in keys.items()}
for row in from_)
def select_where(from_, where):
return (row for row in from_
if where(row))
def default_keys_transform(keys, transform=lambda v, row: row[v]):
return {k : (k, transform) for k in keys}
def select(keys=None, from_=None, where=None):
"""
SELECT v1 AS k1, 2*v2 AS k2 FROM table WHERE v1 = a AND v2 >= b OR v3 = c
translates to
select(dict(k1=(v1, lambda v1, r: r[v1]), k2=(v2, lambda v2, r: 2*r[v2])
, from_=table
, where= lambda r : r[v1] = a and r[v2] >= b or r[v3] = c)
"""
assert from_ is not None
idfunc = lambda k, t : t
select_k = idfunc if keys is None else select_keys
if isinstance(keys, list):
keys = default_keys_transform(keys)
idfunc = lambda t, w : t
select_w = idfunc if where is None else select_where
return select_k(keys, select_w(from_, where))
Как убедиться, что вы не даете пользователям возможность выполнять произвольный код. Эта структура допускает все возможные функции. Что ж, вы можете настроить оболочку для безопасности, которая предоставляет фиксированный список приемлемых объектов функций.
ALLOWED_FUNCS = [ operator.mul, operator.add, ...] # List of allowed funcs
def select_secure(keys=None, from_=None, where=None):
if keys is not None and isinstance(keys, dict):
for v, fun keys.values:
assert fun in ALLOWED_FUNCS
if where is not None:
assert_composition_of_allowed_funcs(where, ALLOWED_FUNCS)
return select(keys=keys, from_=from_, where=where)
Как написать assert_composition_of_allowed_funcs
. Это очень трудно сделать это в python, но легко в lisp. Давайте предположим, что где - список функций, которые должны оцениваться в виде губ, например, в формате where=(operator.add, (operator.getitem, row, v1), 2)
или where=(operator.mul, (operator.add, (opreator.getitem, row, v2), 2), 3)
.
Это позволяет написать функцию apply_lisp
, которая гарантирует, что функция where состоит только из ALLOWED_FUNCS или констант типа float, int, str.
def apply_lisp(where, rowsym, rowval, ALLOWED_FUNCS):
assert where[0] in ALLOWED_FUNCS
return apply(where[0],
[ (apply_lisp(w, rowsym, rowval, ALLOWED_FUNCS)
if isinstance(w, tuple)
else rowval if w is rowsym
else w if isinstance(w, (float, int, str))
else None ) for w in where[1:] ])
Кроме того, вам также потребуется проверить точные типы, поскольку вы не хотите, чтобы ваши типы были переопределены. Так что не используйте isinstance
, используйте type in (float, int, str)
. О, мальчик, с которым мы столкнулись:
Десятое правило программирования Гринспуна: любое достаточно сложное
Программа на C или Fortran содержит специальное неофициально указанное
медленная реализация половины Common Lisp.