我一直试图调试这个太久了,我显然不知道我在做什么,所以希望有人可以提供帮助。我甚至不确定我应该问什么,但在这里:
我正在尝试发送Apple推送通知,并且它们的有效负载大小限制为256字节。所以减去一些开销的东西,我留下了大约100个主要消息内容的英文字符。
因此,如果消息长于最大值,我会截断它:
MAX_PUSH_LENGTH = 100
body = (body[:MAX_PUSH_LENGTH]) if len(body) > MAX_PUSH_LENGTH else body
这样很好,花花公子,无论我有多长时间的消息(英文),推送通知都会成功发送。但是,现在我有一个阿拉伯字符串:
str = "هيك بنكون
عيش بجنون تون تون تون هيك بنكون
عيش بجنون تون تون تون
أوكي أ"
>>> print len(str)
109
那应该截断。但是,我总是得到无效的有效负载大小错误!好奇,我一直在降低MAX_PUSH_LENGTH阈值以查看它成功需要什么,直到我将限制设置为60左右才推出通知成功。
我不确定这是否与英语以外的语言字节大小有关。我的理解是英文字符占用一个字节,阿拉伯字符占用2个字节也是如此?这可能与它有关吗?
此外,字符串在发送之前是JSON编码的,因此最终看起来像这样:\u0647\u064a\u0643 \u0628\u0646\u0643\u0648\u0646 \n\u0639\u064a\u0634 ...
可能是它被解释为原始字符串,而u0647只是5个字节?
我应该在这做什么?有没有明显的错误,或者我没有问正确的问题?
答案 0 :(得分:10)
如果您有一个python unicode值并且想要截断,以下是在Python中执行此操作的非常简短,通用且有效的方法。
def truncate_unicode_to_byte_limit(src, byte_limit, encoding='utf-8'):
'''
truncate a unicode value to fit within byte_limit when encoded in encoding
src: a unicode
byte_limit: a non-negative integer
encoding: a text encoding
returns a unicode prefix of src guaranteed to fit within byte_limit when
encoded as encoding.
'''
return src.encode(encoding)[:byte_limit].decode(encoding, 'ignore')
例如:
s = u"""
هيك بنكون
ascii
عيش بجنون تون تون تون هيك بنكون
عيش بجنون تون تون تون
أوكي أ
"""
b = truncate_unicode_to_byte_limit(s, 73)
print len(b.encode('utf-8')), b
产生输出:
73
هيك بنكون
ascii
عيش بجنون تون تون تو
答案 1 :(得分:4)
对于unicode字符串s
,您需要使用len(s.encode('utf-8'))
之类的内容来获取字节长度。 len(s)
只返回(未编码的)字符数。
<强>更新强> 经过进一步研究后,我发现Python支持增量编码,这使得编写一个相当快的函数来修剪多余的字符成为可能,同时避免字符串中任何多字节编码序列的损坏。以下是使用它执行此任务的示例代码:
# -*- coding: utf-8 -*-
import encodings
_incr_encoder = encodings.search_function('utf8').incrementalencoder()
def utf8_byte_truncate(text, max_bytes):
""" truncate utf-8 text string to no more than max_bytes long """
byte_len = 0
_incr_encoder.reset()
for index,ch in enumerate(text):
byte_len += len(_incr_encoder.encode(ch))
if byte_len > max_bytes:
break
else:
return text
return text[:index]
s = u"""
هيك بنكون
ascii
عيش بجنون تون تون تون هيك بنكون
عيش بجنون تون تون تون
أوكي أ
"""
print 'initial string:'
print s.encode('utf-8')
print "{} chars, {} bytes".format(len(s), len(s.encode('utf-8')))
print
s2 = utf8_byte_truncate(s, 74) # trim string
print 'after truncation to no more than 74 bytes:'
# following will raise encoding error exception on any improper truncations
print s2.encode('utf-8')
print "{} chars, {} bytes".format(len(s2), len(s2.encode('utf-8')))
输出:
initial string:
هيك بنكون
ascii
عيش بجنون تون تون تون هيك بنكون
عيش بجنون تون تون تون
أوكي أ
98 chars, 153 bytes
after truncation to no more than 74 bytes:
هيك بنكون
ascii
عيش بجنون تون تون تو
49 chars, 73 bytes
答案 2 :(得分:1)
您需要切换到字节长度,因此首先需要.encode('utf-8')
字符串,然后在代码点边界处剪切它。
在UTF-8中,ASCII(<= 127
)是1字节。 Bytes with two or more most significant bits set(>= 192
)是字符起始字节;后面的字节数由设置的最高有效位数决定。其他任何东西都是延续字节。
如果在中间剪切多字节序列,可能会出现问题;如果一个字符不适合,则应该完全剪切,直到起始字节。
这是一些有效的代码:
LENGTH_BY_PREFIX = [
(0xC0, 2), # first byte mask, total codepoint length
(0xE0, 3),
(0xF0, 4),
(0xF8, 5),
(0xFC, 6),
]
def codepoint_length(first_byte):
if first_byte < 128:
return 1 # ASCII
for mask, length in LENGTH_BY_PREFIX:
if first_byte & mask == mask:
return length
assert False, 'Invalid byte %r' % first_byte
def cut_to_bytes_length(unicode_text, byte_limit):
utf8_bytes = unicode_text.encode('UTF-8')
cut_index = 0
while cut_index < len(utf8_bytes):
step = codepoint_length(ord(utf8_bytes[cut_index]))
if cut_index + step > byte_limit:
# can't go a whole codepoint further, time to cut
return utf8_bytes[:cut_index]
else:
cut_index += step
# length limit is longer than our bytes strung, so no cutting
return utf8_bytes
现在测试一下。如果.decode()
成功,我们就做了正确的切割。
unicode_text = u"هيك بنكون" # note that the literal here is Unicode
print cut_to_bytes_length(unicode_text, 100).decode('UTF-8')
print cut_to_bytes_length(unicode_text, 10).decode('UTF-8')
print cut_to_bytes_length(unicode_text, 5).decode('UTF-8')
print cut_to_bytes_length(unicode_text, 4).decode('UTF-8')
print cut_to_bytes_length(unicode_text, 3).decode('UTF-8')
print cut_to_bytes_length(unicode_text, 2).decode('UTF-8')
# This returns empty strings, because an Arabic letter
# requires at least 2 bytes to represent in UTF-8.
print cut_to_bytes_length(unicode_text, 1).decode('UTF-8')
您也可以测试代码是否也适用于ASCII。
答案 3 :(得分:1)
使用我在algorithm上发布的other question,这将编码UTF-8的Unicode字符串,并仅截断整个UTF-8序列,以达到小于或等于a的编码长度最大长度:
s = u"""
هيك بنكون
ascii
عيش بجنون تون تون تون هيك بنكون
عيش بجنون تون تون تون
أوكي أ
"""
def utf8_lead_byte(b):
'''A UTF-8 intermediate byte starts with the bits 10xxxxxx.'''
return (ord(b) & 0xC0) != 0x80
def utf8_byte_truncate(text,max_bytes):
'''If text[max_bytes] is not a lead byte, back up until a lead byte is
found and truncate before that character.'''
utf8 = text.encode('utf8')
if len(utf8) <= max_bytes:
return utf8
i = max_bytes
while i > 0 and not utf8_lead_byte(utf8[i]):
i -= 1
return utf8[:i]
b = utf8_byte_truncate(s,74)
print len(b),b.decode('utf8')
73
هيك بنكون
ascii
عيش بجنون تون تون تو