33

我正在动态创建 python 类,我知道并非所有字符在这种情况下都是有效的。

类库中是否有一种方法可以用来清理随机文本字符串,以便可以将其用作类名?那个或允许的字符列表将是一个很好的帮助。


关于与标识符名称冲突的补充:就像@Ignacio 在下面的答案中指出的那样,任何作为标识符有效的字符都是类名中的有效字符。您甚至可以毫无问题地使用保留字作为类名。但有一个问题。如果您确实使用了保留字,您将无法像其他(非动态创建的)类一样使该类可访问(例如,通过做globals()[my_class.__name__] = my_class)。在这种情况下,保留字将始终优先。

4

4 回答 4

36

蟒蛇 3

Python 语言参考,第 2.3 节,“标识符和关键字”

Python 中标识符的语法基于 Unicode 标准附件 UAX-31,详细说明和更改如下定义;另请参阅 PEP 3131 了解更多详情。

在 ASCII 范围内 (U+0001..U+007F),标识符的有效字符与 Python 2.x 中的相同:大写和小写字母 A 到 Z、下划线 _ 以及除第一个字符外,数字 0 到 9。

Python 3.0 引入了 ASCII 范围之外的其他字符(参见 PEP 3131)。对于这些字符,分类使用包含在 unicodedata 模块中的 Unicode 字符数据库的版本。

标识符的长度不受限制。案例意义重大。

identifier   ::=  xid_start xid_continue*
id_start     ::=  <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
id_continue  ::=  <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
xid_start    ::=  <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">
xid_continue ::=  <all characters in id_continue whose NFKC normalization is in "id_continue*">

上面提到的 Unicode 类别代码代表:

  • 卢——大写字母
  • Ll - 小写字母
  • Lt - 大写字母
  • Lm - 修饰字母
  • Lo - 其他字母
  • Nl - 字母数字
  • Mn - 非间距标记
  • Mc - 间距组合标记
  • Nd——十进制数
  • Pc - 连接器标点符号
  • Other_ID_Start - PropList.txt 中的显式字符列表以支持向后兼容性
  • Other_ID_Continue - 同样

所有的标识符在解析的时候都被转换成正常形式的NFKC;标识符的比较基于 NFKC。

可在https://www.dcl.hpi.uni-potsdam.de/home/loewis/table-3131.html找到列出 Unicode 4.1 的所有有效标识符字符的非规范 HTML 文件。

蟒蛇2

Python 语言参考,第 2.3 节,“标识符和关键字”

标识符(也称为名称)由以下词汇定义描述:

identifier ::=  (letter|"_") (letter | digit | "_")*
letter     ::=  lowercase | uppercase
lowercase  ::=  "a"..."z"
uppercase  ::=  "A"..."Z"
digit      ::=  "0"..."9"

标识符的长度不受限制。案例意义重大。

于 2012-04-12T08:52:14.927 回答
9

根据Python 语言参考,第 2.3 节,“标识符和关键字”,有效的 Python 标识符定义为:

(letter|"_") (letter | digit | "_")*

或者,在正则表达式中:

[a-zA-Z_][a-zA-Z0-9_]*
于 2014-07-02T21:37:05.193 回答
7

有趣的是标识符的第一个字符是特殊的。在第一个字符之后,数字 '0' 到 '9' 对标识符有效,但它们不能是第一个字符。

这是一个函数,它将返回一个给定任意随机字符串的有效标识符。以下是它的工作原理:

首先,我们用于itr = iter(seq)获取输入的显式迭代器。然后是第一个循环,它使用迭代器itr查看字符,直到找到标识符的有效第一个字符。然后它跳出那个循环并运行第二个循环,对第二个循环使用相同的迭代器(我们命名为itr)。迭代器itr为我们保留了位置;当第二个循环运行时,从迭代器中拉出的第一个循环的字符仍然消失。

def gen_valid_identifier(seq):
    # get an iterator
    itr = iter(seq)
    # pull characters until we get a legal one for first in identifer
    for ch in itr:
        if ch == '_' or ch.isalpha():
            yield ch
            break
    # pull remaining characters and yield legal ones for identifier
    for ch in itr:
        if ch == '_' or ch.isalpha() or ch.isdigit():
            yield ch

def sanitize_identifier(name):
    return ''.join(gen_valid_identifier(name))

这是一种以两种不同方式处理序列的干净且 Pythonic 的方式。对于这么简单的问题,我们可以只使用一个布尔变量来指示我们是否已经看到了第一个字符:

def gen_valid_identifier(seq):
    saw_first_char = False
    for ch in seq:
        if not saw_first_char and (ch == '_' or ch.isalpha()):
            saw_first_char = True 
            yield ch
        elif saw_first_char and (ch == '_' or ch.isalpha() or ch.isdigit()):
            yield ch

我不像第一个版本那样喜欢这个版本。对一个字符的特殊处理现在纠缠在整个控制流程中,这将比第一个版本慢,因为它必须不断检查 的值saw_first_char。但这是您在大多数语言中处理控制流的方式!Python 的显式迭代器是一个很好的特性,我认为它使这段代码变得更好。

在显式迭代器上循环与让 Python 为您隐式获取迭代器一样快,并且显式迭代器允许我们拆分处理标识符不同部分的不同规则的循环。因此,显式迭代器为我们提供了运行速度更快的更简洁的代码。双赢。

于 2012-04-12T09:39:38.643 回答
3

到目前为止,这是一个老问题,但我想添加一个关于如何在 Python 3 中执行此操作的答案,因为我已经做了一个实现。

此处记录了允许的字符:https ://docs.python.org/3/reference/lexical_analysis.html#identifiers 。它们包括很多特殊字符,包括标点符号、下划线和大量外来字符。幸运的是,该unicodedata模块可以提供帮助。这是我的实现直接实现 Python 文档所说的内容:

import unicodedata

def is_valid_name(name):
    if not _is_id_start(name[0]):
        return False
    for character in name[1:]:
        if not _is_id_continue(character):
            return False
    return True #All characters are allowed.

_allowed_id_continue_categories = {"Ll", "Lm", "Lo", "Lt", "Lu", "Mc", "Mn", "Nd", "Nl", "Pc"}
_allowed_id_continue_characters = {"_", "\u00B7", "\u0387", "\u1369", "\u136A", "\u136B", "\u136C", "\u136D", "\u136E", "\u136F", "\u1370", "\u1371", "\u19DA", "\u2118", "\u212E", "\u309B", "\u309C"}
_allowed_id_start_categories = {"Ll", "Lm", "Lo", "Lt", "Lu", "Nl"}
_allowed_id_start_characters = {"_", "\u2118", "\u212E", "\u309B", "\u309C"}

def _is_id_start(character):
    return unicodedata.category(character) in _allowed_id_start_categories or character in _allowed_id_start_categories or unicodedata.category(unicodedata.normalize("NFKC", character)) in _allowed_id_start_categories or unicodedata.normalize("NFKC", character) in _allowed_id_start_characters

def _is_id_continue(character):
    return unicodedata.category(character) in _allowed_id_continue_categories or character in _allowed_id_continue_characters or unicodedata.category(unicodedata.normalize("NFKC", character)) in _allowed_id_continue_categories or unicodedata.normalize("NFKC", character) in _allowed_id_continue_characters

此代码改编自 CC0 下的此处:https ://github.com/Ghostkeeper/Luna/blob/d69624cd0dd5648aec2139054fae4d45b634da7e/plugins/data/enumerated/enumerated_type.py#L91 。它已经过很好的测试。

于 2017-01-10T03:34:32.573 回答