python类名中的有效字符

时间:2012-04-12 08:50:16

标签: python metaprogramming

我正在动态创建python类,我知道并非所有字符在此上下文中都有效。

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


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

4 个答案:

答案 0 :(得分:34)

Python Language Reference, §2.3, "Identifiers and keywords"

  

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

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

标识符的长度不受限制。案件很重要。

答案 1 :(得分:6)

根据Python Language Reference, §2.3, "Identifiers and keywords",有效的Python标识符定义为:

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

或者,在正则表达式

[a-zA-Z_][a-zA-Z0-9_]*

答案 2 :(得分:5)

使这个有趣的是标识符的第一个字符是特殊的。在第一个字符后,数字“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隐式为您获取迭代器一样快,而显式迭代器允许我们拆分处理标识符不同部分的不同规则的循环。因此,显式迭代器为我们提供了更快的代码。赢/赢。

答案 3 :(得分:1)

这是一个古老的问题,但我想在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。它经过了很好的测试。