tldr;看到最后一行;其余的只是序言。
我正在开发一个测试工具,它解析用户脚本并生成一个随后运行的Python脚本。这个想法是让非技术人员能够编写高级测试脚本。
我已经介绍了变量的概念,因此用户可以在他的脚本中使用LET
关键字。例如。 LET X = 42
,我只是扩展到X = 42
。然后,他们可以在脚本中使用X - RELEASE CONNECTION X
但如果有人写LET 2 = 3
怎么办?那会产生无效的Python。
如果我在变量X
中有variableName
,那么如何检查variableName
是否是有效的Python变量?
答案 0 :(得分:31)
在Python 3中,您可以使用str.isidentifier()
来测试给定字符串是否是有效的Python标识符/名称。
>>> 'X'.isidentifier()
True
>>> 'X123'.isidentifier()
True
>>> '2'.isidentifier()
False
>>> 'while'.isidentifier()
True
最后一个示例显示您还应检查变量名称是否与Python关键字冲突:
>>> from keyword import iskeyword
>>> iskeyword('X')
False
>>> iskeyword('while')
True
所以你可以把它放在一个函数中:
from keyword import iskeyword
def is_valid_variable_name(name):
return name.isidentifier() and not iskeyword(name)
另一个在Python 2和3中有效的选项是使用ast
模块:
from ast import parse
def is_valid_variable_name(name):
try:
parse('{} = None'.format(name))
return True
except SyntaxError, ValueError, TypeError:
return False
>>> is_valid_variable_name('X')
True
>>> is_valid_variable_name('123')
False
>>> is_valid_variable_name('for')
False
>>> is_valid_variable_name('')
False
>>> is_valid_variable_name(42)
False
这将解析赋值语句而不实际执行它。它将获取无效标识符以及尝试分配给关键字。在上面的代码中,None
是一个分配给给定名称的任意值 - 它可以是RHS的任何有效表达式。
答案 1 :(得分:2)
您可以使用异常处理并实际捕获NameError
和SyntaxError
。在try/except
块内测试它,并告知用户是否存在无效输入。
答案 2 :(得分:1)
您可以尝试测试作业并查看它是否会引发SyntaxError
:
>>> 2fg = 5
File "<stdin>", line 1
2fg = 5
^
SyntaxError: invalid syntax
答案 3 :(得分:1)
在Python 3中,如上所述,您只需使用str.isidentifier
即可。但是在Python 2中,这不存在。
tokenize
模块具有名称(标识符)的正则表达式:tokenize.Name
。但我无法找到任何文档,因此它可能无法在任何地方使用。它只是r'[a-zA-Z_]\w*'
。一个$
后面的re.match
将允许您使用identifier ::= (letter|"_") (letter | digit | "_")*
letter ::= lowercase | uppercase
lowercase ::= "a"..."z"
uppercase ::= "A"..."Z"
digit ::= "0"..."9"
测试字符串。
docs表示该语法定义了一个标识符:
tokenize.Name
这相当于上面的正则表达式。但是,如果这种情况发生变化,我们仍应导入pass
。 (这是非常不可能的,但可能在旧版本的Python中有所不同?)
要过滤掉def
,return
和keyword.iskeyword
等关键字,请使用None
。有一点需要注意:keyword.iskeyword('None')
不是Python 2中的关键字,但仍然无法分配。 (Python 2中的False
是import keyword
if hasattr(str, 'isidentifier'):
_isidentifier = str.isidentifier
else:
import re
_fallback_pattern = '[a-zA-Z_][a-zA-Z0-9_]*'
try:
import tokenize
except ImportError:
_isidentifier = re.compile(_fallback_pattern + '$').match
else:
_isidentifier = re.compile(
getattr(tokenize, 'Name', _fallback_pattern) + '$'
).match
del _fallback_pattern
def isname(s):
return bool(_isidentifier(s)) and not keyword.iskeyword(s) and s != 'None'
)。
所以:
df['G']=df.groupby(level='Date').cumcount()
df
Out[125]:
string number G
Date
2017-01-16 stringvalue 90 0
2017-01-16 stringvalue 912 1
2017-01-16 stringvalue 29 2
2017-01-17 stringvalue 883 0
2017-01-17 stringvalue 223 1
2017-01-17 stringvalue 211 2
2015-04-30 stringvalue 908 0
2017-03-30 stringvalue 348 0
df.sort_values('G').sort_index().drop('G',1)
Out[124]:
string number
Date
2015-04-30 stringvalue 908
2017-01-16 stringvalue 90
2017-01-16 stringvalue 912
2017-01-16 stringvalue 29
2017-01-17 stringvalue 883
2017-01-17 stringvalue 223
2017-01-17 stringvalue 211
2017-03-30 stringvalue 348
答案 4 :(得分:1)
你可以让Python(在我所知的今天使用的任何版本上工作)按照它们通常在内部的方式进行检查,并捕获异常:
def _dummy_function_taking_kwargs(**_):
pass
try:
_dummy_function_taking_kwargs(**{my_variable: None})
# if the above line didn't raise and we get here,
# the keyword/variable name was valid.
# You could also replace the external dummy function
# with an inline lambda function.
except TypeError:
# If we get here, it wasn't.
值得注意的是,每当TypeError
经历关键字参数扩展并且其密钥不是有效函数参数时,dict
始终会被提升,和只要{{1}使用无效键构造文字。
优于已接受的答案的是,它在<3>和<3> 2 和之间 兼容,而不像dict
/ {{那样脆弱1}}方法(将ast.parse
之类的字符串视为有效)。
我还没有彻底审核这个解决方案,或者写了假设测试来模糊它,所以可能会有一些极端情况,但它似乎通常适用于Python 3.7,3.6,2.7和2.5(不是任何人< em>应该现在使用2.5,但它仍然是野外的,你可能是为数不多的可怜的草皮之一,不得不编写与2.6 / 2.5一起工作的代码。
答案 5 :(得分:0)
我认为您不需要与python本身完全相同的命名语法。 宁愿选择一个简单的正则表达式:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Controls:ItemView />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<StackPanel>
<ItemsPresenter />
<Button Content="Add Item" Click="AddItem_Click"/>
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
确保它是字母数字,然后添加一个前缀以远离python自己的语法。所以非技术用户的声明:
\w+
应该在解析之后成为:
LET return = 12