动态创建一个具有位置参数或关键字参数的Python函数,而不是从字符串生成
在这个问题中,有一些很酷的技巧可以在Python中动态生成函数。
不过在我的情况下,我需要确保生成的函数有一个特定的名字,并且参数的名字也要特别指定。我来举个例子。
假设我想解析一个格式如下的Yaml文件:
Root:
my_idea:
type: conditional
conditionals:
- foo
- bar
query: >
SELECT * FROM some_table
这需要被理解为:为我创建一个名为“my_idea”的函数,参数分别叫“foo”和“bar”(这个顺序要保持)。
当这个函数被调用时,要使用一些现有的工具连接到数据库,并从Yaml文件中准备好query
元素。然后根据传入函数的参数值,添加一个WHERE
条件。
所以,经过这些操作后,用户应该能够调用:
my_idea(10, 20)
这将等同于执行这个查询:
SELECT * FROM some_table WHERE foo = 10 AND bar = 20
如果我用def
来定义这个函数,它可能会像这样:
def my_idea(arg1, arg2):
query = (query_retrieved_from_file +
" WHERE {}={} AND {}={}".format(arg1_name_from_file,
arg1,
arg2_name_from_file,
arg2))
connection = ExistingLibraryConnectionMaker()
return connection.execute(query).fetchall()
这是一个非常简化的例子——我并不是在推荐这个小函数的具体实现,只是想说明这个想法。
问题是:如何动态创建这个函数,并且函数的名字和位置参数的名字是从一个文本文件中提取出来的?
在另一个问题中,有一些示例代码:
import types
def create_function(name, args):
def y(): pass
y_code = types.CodeType(args,
y.func_code.co_nlocals,
y.func_code.co_stacksize,
y.func_code.co_flags,
y.func_code.co_code,
y.func_code.co_consts,
y.func_code.co_names,
y.func_code.co_varnames,
y.func_code.co_filename,
name,
y.func_code.co_firstlineno,
y.func_code.co_lnotab)
return types.FunctionType(y_code, y.func_globals, name)
但不太清楚如何让位置参数反映我想要的语义。
我找到的另一个解决方案是这样的:
import types
import sys,imp
code = """def f(a,b,c):
print a+b+c, "really WoW"
"""
module = imp.new_module('myfunctions')
exec code in module.__dict__
module.f('W', 'o', 'W')
Output:
WoW really WoW
这个方案更接近,但需要把所有代码都嵌入到字符串格式中。我想要程序化地构建一个函数,并且选项数量相对较多,所以把它们都深埋在字符串里是不可行的。
1 个回答
1
这是一个为了好玩而制作的LL解析器示例。
它可以生成一些代码。
生成的代码:
def my_idea(arg0,arg1,atasdasd):
query = "SELECT * FROM some_table WHERE foo==arg0 AND bar>arg1"
connection = ExistingLibraryConnectionMaker()
return connection.execute(query).fetchall()
def lool(hihi,ahah):
query = "SELECT * FROM some_table WHERE foo<=hihi AND bar<ahah"
connection = ExistingLibraryConnectionMaker()
return connection.execute(query).fetchall()
###end
来自
Root:
my_idea:
args :
-arg0
-arg1
-atasdasd
type: conditional
conditional:
-foo == arg0
-bar > arg1
query:
SELECT * FROM some_table
lool:
args :
-hihi
-ahah
type: conditional
conditional:
- foo <= hihi
- bar < ahah
query:
SELECT * FROM some_table
它可以处理任意数量的函数。
代码:
from __future__ import print_function
import re
import traceback
import sys
glIndex = 0
code = ""
class Token(object):
def __init__(self, pattern, name, value=None, transform=None):
self.pattern = pattern
self.name = name
self.value = value
tokens = {
Token(r"(\()","lpar"),
Token(r"(\))","rpar"),
Token(r"(\d(?:\.\d*)?)","number"),
Token(r"(\+)", "plus"),
Token(r"(?!\-\-)(\-)","minus"),
Token(r"(?!\=\=)(\=)","egal"),
Token(r"(;)","colon"),
Token(r"([a-zA-Z][a-zA-Z0-9_]*)(?=[\s\:])","unique"),
Token(r"(\=)\=","=="),
Token(r"(\>\=)",">="),
Token(r"(\<\=)","<="),
Token(r"(?!\>\=)(\>)",">"),
Token(r"(?!\<\=)(\<)","<"),
Token(r"\:",":"),
Token(r"\*","*")}
def peekComp(l):
symbol = None
if peekMatch(l,">=") :
symbol = ">="
elif peekMatch(l,"<=") :
symbol = "<="
elif peekMatch(l,">") :
symbol = ">"
elif peekMatch(l,"<") :
symbol = "<"
elif peekMatch(l,"==") :
symbol = "=="
return symbol
def parseListItem(l):
match(l,"minus")
u = match(l,"unique")
return u
def parseTitle(l):
val = match(l,"unique")
match(l,":")
return val
def parseComp(l):
match(l,"minus")
lvar = match(l,"unique")
symbol = peekComp(l)
if symbol == None:
print("Homemaid SyntaxError: needed a comp symbol")
exit(1)
symbolS = match(l,symbol)
rvar = match(l,"unique")
return (lvar,symbolS,rvar)
def tokenize(s):
l=[]
i=0
while i < s.__len__():
if re.match(r"\s",s[i]):
i+=1
continue
foundAMatch = False
for t in tokens:
pat = "^(" + t.pattern + ").*"
#print("trying with pat :'"+pat+"';")
res = re.match(pat,s[i:])
if res != None:
print("Match: -what : '" + res.group(1) + "' -to-token-named :'" + t.name + "'; \t\tTotal text : '" + res.group(0) + "';" )
i += res.group(1).__len__()
foundAMatch = True
l.append(Token(t.pattern,t.name,res.group(1)))
break
if not foundAMatch:
print("Homemaid SyntaxError: No match for '" + s[i:] + "';")
quit()
return l
def syntaxError(l,fname):
global glIndex
print("Homemaid SyntaxError: '"+l[glIndex].name+"'")
print(fname)
quit()
def match(tokens, wanted):
global glIndex
if tokens[glIndex].name == wanted:
glIndex+=1
print("Matched '" + tokens[glIndex-1].value + "' as '" + wanted + "';")
return tokens[glIndex-1].value
else:
print("Homemaid Syntax Error : Match failed on token '" + tokens[glIndex].name + "' with wanted token '" + wanted + "' and text '" + tokens[glIndex].value + "';")
exit(1)
def peekMatch(token, wanted):
global glIndex
if glIndex < token.__len__() and token[glIndex].name == wanted:
print("Matched "+wanted)
return True
else:
return False
def parse(l):
#root
localCode = ""
rootName = parseTitle(l)
print("Root : " + rootName)
#parse funcitons
while peekMatch(l,"unique"):
localCode += parseFunction(l)
print("Done with the parsing.")
return localCode
def parseFunction(l):
print("CAME IN PARSE FUNCITON")
#function name
localCode = "\n\ndef " + parseTitle(l) +"(";
#args
args = set()
title = parseTitle(l)
if title!="args":
print("Homemaid Syntax Error : title should be 'args', was instead '" + title + "';")
exit(1)
while(peekMatch(l,"minus")):
lastArg = parseListItem(l)
args.add(lastArg)
localCode += lastArg
if peekMatch(l,"minus") :
localCode += ","
localCode += "):\n"
#type
if parseTitle(l)!="type":
print("Homemaid Syntax Error : title should be 'type'")
exit(1)
#query
##query name
queryTypeName = match(l, "unique")
##query args
queryTypeArgs = []
if parseTitle(l)!=queryTypeName:
print("Homemaid Syntax Error : title should be the same as the name of the query.")
exit(1)
while(peekMatch(l,"minus")):
queryTypeArgs.append(parseComp(l))
##query sql code
if parseTitle(l) != "query":
print("Homemaid Syntax Error : title should be 'query'.")
exit(1)
initialQuery = parseBasicSqlQuery(l)
if queryTypeName == "conditional" and queryTypeArgs.__len__() <= 0 :
print("Homemaid Syntax error : Conditional query needs at least one arg.")
exit(1)
##query codegen
localCode += "\tquery = \"" + initialQuery + " WHERE "
first = True
if queryTypeName == "conditional":
for lArg, cmpSign, rArg in queryTypeArgs:
if not first:
localCode += " AND "
if rArg in args:
first = False
localCode += lArg + cmpSign + rArg
else:
print("queryTypeArgs : " + str(queryTypeArgs))
print("Homemaid Logic Error: Query arg '" + rArg + "' is not in the funciton args " + str(args) + ".")
quit(1)
localCode += "\"\n\tconnection = ExistingLibraryConnectionMaker()\n\treturn connection.execute(query).fetchall()"
return localCode
def parseBasicSqlQuery(l):
selectS = match(l,"unique")
whatS = match(l,"*")
fromS = match(l,"unique")
tableNameS = match(l,"unique")
if selectS.upper() != "SELECT" or fromS.upper() != "FROM":
print("Homemaid Syntax error: bad basic sql.")
exit(0)
return selectS + " " + whatS + " " + fromS + " " + tableNameS
def parseVal(l):
if match(l, "lpar"):
parseVal(l)
match(l, "rpar")
elif peekMatch(l, "number") and (peekMatch(l, "plus") or peekMatch(l, "minus") or peekMatch(l, "equal")):
glIndex+=1
print("peekMatched!")
parseOp(l)
parseVal(l)
elif match(l, "number"):
pass
else:
syntaxError(l, "parseVal")
print("** Parsed val.")
def parseOp(l):
if match(l, "plus"):
pass
elif match(l, "minus"):
pass
elif match(l, "egal"):
pass
else:
syntaxError(l, "parseVal")
print("** Parsed op.")
if __name__ == "__main__":
with open("sqlGenTest.SQLGEN", "rw") as file:
print("File:\n'")
text = file.read()
print(text + "'\n")
tokens = tokenize(text)
names = map(lambda x: str("'" + x.name + "'" + " : " + "'" + x.value + "'"), tokens)
map(print,names)
code = parse(tokens)
print("")
print("###Generated Code:\n" + code)
print("###end")
print()