如何在Python中检查一个字符串是否为ASCII编码?

261

我想要检查一个字符串是否为ASCII编码。

我知道使用ord()方法可以实现,但是当我尝试使用ord('é')时,会出现TypeError: ord() expected a character, but string of length 2 found的错误。根据ord()方法的文档中的解释,这是由于我所使用的Python版本构建方式引起的。

是否有其他的方法可以进行检查?


2
Python 2和Python 3之间的字符串编码差别相当大,因此最好知道你的目标版本是哪个。 - florisla
根据ord('é')的错误,@florisla表示OP正在使用Python 2。 - wjandrea
16个回答

286

我认为你没有问对问题—

在Python中,字符串没有与'ascii'、utf-8或任何其他编码相对应的属性。您的字符串的来源(无论是从文件中读取还是从键盘输入等)可能已经将Unicode字符串编码为ASCII以生成您的字符串,但这就是您需要去寻找答案的地方。

也许你可以问的问题是:“这个字符串是通过将Unicode字符串编码为ASCII而得到的吗?”——你可以通过尝试回答这个问题:

try:
    mystring.decode('ascii')
except UnicodeDecodeError:
    print "it was not a ascii-encoded unicode string"
else:
    print "It may have been an ascii-encoded unicode string"

38
使用编码更好,因为在Python 3中字符串没有解码方法,参见encode / decode有什么区别?(Python 2.x) - Jet Guo
1
@Sri:这是因为你在未编码的字符串上使用它(在Python 2中为str,在Python 3中为bytes)。 - dotancohen
在Python 2中,这个解决方案只适用于_unicode_字符串。任何ISO编码的str都需要先编码为Unicode。答案应该写在这里。 - alexis
@JetGuo:根据输入的类型,你应该使用两个方法: s.decode('ascii') if isinstance(s, bytes) else s.encode('ascii') 在 Python 3 中。OP的输入是一个字节串 'é' (Python 2 语法,当时还没有发布 Python 3),因此 .decode() 是正确的。 - jfs
2
@alexis:错误。在Python 2中,str是一个字节串。使用.decode('ascii')来查找所有字节是否在ASCII范围内是正确的。 - jfs
显示剩余2条评论

232
def is_ascii(s):
    return all(ord(c) < 128 for c in s)

109
无意义的低效。更好的方式是尝试使用s.decode('ascii')并捕获UnicodeDecodeError,正如Vincent Marchetti所建议的那样。 - ddaa
27
不低效。当遇到无效字节时,all()会迅速返回False停止运行。 - John Millikin
11
无论是否高效,更符合Python风格的方法是使用try/except。 - Jeremy Cantrell
46
与 try/except 相比,这种方法效率较低。在这里,循环是在解释器中进行的。而使用 try/except 的形式,循环是在由 str.decode('ascii') 调用的 C 编解码实现中执行的。我同意,try/except 的形式也更符合 Python 风格。 - ddaa
36
@JohnMachin ord(c) < 128c <= "\x7F"更易读和直观。 - Slater Victoroff
显示剩余9条评论

188
在Python 3中,我们可以将字符串编码为UTF-8,然后检查长度是否保持不变。如果是,则原始字符串为ASCII。
def isascii(s):
    """Check if the characters in string s are in ASCII, U+0-U+7F."""
    return len(s) == len(s.encode())

要进行检查,请传递测试字符串:

>>> isascii("♥O◘♦♥O◘♦")
False
>>> isascii("Python")
True

9
这是一个很好的小技巧,用于检测Unicode字符串中的非ASCII字符,在Python3中几乎所有字符串都是Unicode的。由于ASCII字符可以使用只有1个字节来编码,因此任何ASCII字符在编码为字节后大小将保持不变;而其他非ASCII字符将分别编码为2个或3个字节,这将增加它们的大小。 - Devy
迄今为止最好的答案,但是请注意,像…和—这样的一些字符看起来可能像ASCII码,因此,如果您想使用它来检测英文文本,请在检查之前替换这些字符。 - Christophe Roussy
2
但在Python2中,它会抛出UnicodeEncodeError。必须找到适用于Py2和Py3的解决方案。 - alvas
13
这只是纯粹的浪费。它使用UTF-8编码一个字符串,创建了另一个字节串。在Python 3中,更好的方法是:try: s.encode('ascii'); return True except UnicodeEncodeError: return False(与上面相似,但由于在Python 3中字符串是Unicode类型,所以需要编码)。但是,这种方法在Python 3中会在有代理项字符时产生错误(例如 isascii('\uD800') 会引发错误而不是返回False)。 - Artyer
这看起来很漂亮,但我想知道当处理长字符串时,它是否和all一样高效。 - Endle_Zhenbo
显示剩余2条评论

169

Python 3.7的新功能 (bpo32677)

不再需要在字符串上进行繁琐/低效的ASCII检查,新的内置str/bytes/bytearray方法 - .isascii()将检查字符串是否为ASCII。

print("is this ascii?".isascii())
# True

9
"\x03".isascii()也为True。文档说明这只是检查所有字符是否在128(0-127)的码点以下。如果你想避免控制字符,你需要使用:text.isascii() and text.isprintable()。仅使用isprintable也不够,因为它会将像¿这样的字符视为(正确的)可打印字符,但它不在ascii可打印部分内,所以如果你想要两者都检查,你需要同时检查。还有一个问题:空格被认为是可打印的,制表符和换行符则不是。 - Luc
3
@Luc 很好知道,但 ASCII 包括控制字符。避免它们是另一个话题。 - wjandrea
@wjandrea 当然,显然,但是因为0x03适合7位并不意味着大多数人在找到这个页面时想要检查它。 - Luc
2
@Luc 是的,没错。如果有人认为所有ASCII字符都可以安全打印,那么他们是错误的,但这是一个有效的话题,可能值得单独提出问题。 - wjandrea
很遗憾,除了等待点赞之外,没有其他方法可以让这个答案跳到顶部。如果原帖作者再次登录,他们至少可以接受它,但似乎自从发布这个问题以来,他们就没有出现过。 - John Y
我将这个与@far的答案和那个答案上的建议进行了基准测试,这个略微更快一点。 - Wayne Workman

29

Vincent Marchetti的想法是正确的,但在Python 3中已经弃用了str.decode。你可以使用str.encode来进行相同的测试:

try:
    mystring.encode('ascii')
except UnicodeEncodeError:
    pass  # string is not ascii
else:
    pass  # string is ascii

请注意您想要捕获的异常已经从UnicodeDecodeError变成了UnicodeEncodeError


OP的输入是一个字节串(Python 3中的“bytes”类型,没有“.encode()”方法)。在@Vincent Marchetti的答案中,.decode()是正确的 - jfs
1
@J.F.Sebastian,这个OP提问“如何检查Python中的字符串是否为ASCII字符”,但并没有具体说明字节字符串(bytes)还是Unicode字符串。你为什么说他/她的输入是字节序列(bytestring)呢? - drs
1
看一下问题的日期:在那个时候,'é' 是一个字节串。 - jfs
3
@J.F.Sebastian,好的,考虑到这个回答是根据今天的情况回答这个问题的,我认为它仍然有效且有帮助。越来越少的人会像在2008年一样来这里寻找答案。 - drs
2
当我在寻找Python3的解决方案时,我发现了这个问题,并且快速阅读问题并没有让我怀疑这是Python2特定的。但是这个答案真的很有帮助 - 点赞! - josch
显示剩余5条评论

18

最近遇到了类似的问题 -供以后参考

import chardet

encoding = chardet.detect(string)
if encoding['encoding'] == 'ascii':
    print 'string is in ascii'

你可以使用以下方式:

string_ascii = string.decode(encoding['encoding']).encode('ascii')

8
当然,这需要使用chardet库。 - StackExchange saddens dancek
1
是的,虽然在大多数安装中默认提供了chardet。 - Alvin
8
chardet 只是猜测编码,并给出一个确定的概率,如:{'confidence': 0.99, 'encoding': 'EUC-JP'}(但在这种情况下完全错误)。 - Suzana

18

你的问题是不正确的,你看到的错误不是由于你构建Python的方式而引起的,而是因为字节字符串和Unicode字符串之间的混淆。

字节字符串(例如,在Python语法中,“foo”或'bar')是八位组序列;数字从0-255。Unicode字符串(例如u“foo”或u'bar')是Unicode代码点的序列;数字从0-1112064。但是你似乎对字符é感兴趣,在你的终端中它是表示一个单一字符的多字节序列。

请尝试这个方法替代ord(u'é')

>>> [ord(x) for x in u'é']

这告诉你“é”表示哪个码点序列。它可能给你[233],也可能给你[101, 770]。

与使用chr()相反,可以使用unichr()来进行反向操作:

>>> unichr(233)
u'\xe9'

这个字符实际上可以被表示为单个或多个Unicode "code points",它们本身表示字形或字符。 它可以是“带有重音符号的e(即代码点233)”或“e”(代码点101),后面跟随“前一个字符上的重音符号”(代码点770)。因此,这个完全相同的字符可以用Python数据结构u'e\u0301'u'\u00e9'来表示。

大多数情况下,您不必关心此问题,但如果您正在迭代unicode字符串,则可能会成为一个问题,因为迭代是按照代码点而不是可分解字符进行工作的。换句话说,len(u'e\u0301') == 2len(u'\u00e9') == 1。如果这对你很重要,你可以使用unicodedata.normalize在组合和分解形式之间进行转换。

Unicode词汇表可以帮助理解其中一些问题,指出每个特定术语如何引用文本表示的不同部分,这比许多程序员意识到的要复杂得多。


3
“é” 不一定代表一个单独的码点。它可能是两个码点(U+0065 + U+0301)。 - jfs
2
每个抽象字符始终由单个代码点表示。但是,根据编码方案,代码点可能被编码为多个字节。例如,'é' 在 UTF-8 和 UTF-16 中是两个字节,在 UTF-32 中是四个字节,但在每种情况下仍然是单个代码点 - U+00E9。 - Ben Blank
5
@Ben Blank: U+0065和U+0301是码点,它们代表的是“é”,同样的字符也可以用U+00E9表示。请搜索“combining acute accent”。 - jfs
J.F. 关于将 U+0065 和 U+0301 结合成 'é' 是正确的,但这不是一个可逆的函数。你会得到 U+00E9。根据 维基百科 的说法,这些组合码点对于向后兼容性非常有用。 - Martin Konecny
1
@teehoo - 它是一种可逆函数,因为您可以将表示组合字符的代码点重新规范化为表示相同组合字符的代码点序列。在Python中,您可以这样做:unicodedata.normalize('NFD',u'\xe9')。 - Glyph
我已经更新了答案,试图解决一些反馈以及问题所做的更改。 - Glyph

9
这个怎么样呢?
import string

def isAscii(s):
    for c in s:
        if c not in string.ascii_letters:
            return False
    return True

8
如果字符串包含非字母的ASCII字符,则此方法会失败。对于您的代码示例,这包括换行符、空格、句点、逗号、下划线和括号。 - florisla

9

我在尝试确定如何使用/编码/解码一串编码格式未知的字符串(以及如何转义/转换其中的特殊字符)时发现了这个问题。

我的第一步应该是检查字符串的类型-我没有意识到从类型(s)可以获得关于其格式的良好数据。 这个答案非常有帮助,找到了我的实际问题所在。

如果你在进行编码时遇到

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 263: ordinal not in range(128)

特别是当你在ENCODING时,确保你不要尝试unicode()一个已经是unicode的字符串-出于某种可怕的原因,你会遇到ascii编解码器错误。(另请参阅Python Kitchen recipePython docs教程,以更好地理解这种情况下的恶性循环.)

最终我确定我的目的是这样的:

escaped_string = unicode(original_string.encode('ascii','xmlcharrefreplace'))

在调试过程中有用的一点是将我的文件默认编码设置为utf-8(将此放在python文件的开头):

# -*- coding: utf-8 -*-

这使您能够测试特殊字符('àéç'),而无需使用它们的Unicode转义符(u'\xe0\xe9\xe7')。

>>> specials='àéç'
>>> specials.decode('latin-1').encode('ascii','xmlcharrefreplace')
'&#224;&#233;&#231;'

4
为了改进Python 2.6中的Alexander解决方案(以及Python 3.x),您可以使用辅助模块curses.ascii并使用curses.ascii.isascii()函数或各种其他函数:https://docs.python.org/2.6/library/curses.ascii.html。请注意,保留HTML标记,请勿添加解释,并确保内容易于理解。
from curses import ascii

def isascii(s):
    return all(ascii.isascii(c) for c in s)

3
它可以工作,但要注意curses.ascii的字符分类函数存在已知问题。 - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接