6

私はこれを非常に長い間デバッグしようとしてきましたが、明らかに自分が何をしているのかわからないので、誰かが助けてくれることを願っています。私は何を尋ねるべきかさえわかりませんが、ここにそれが行きます:

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に設定するまでです。

これが英語以外の言語のバイトサイズと関係があるかどうかは正確にはわかりません。英語の文字は1バイトかかると理解していますが、アラビア語の文字は2バイトかかりますか?これはそれと関係があるのでしょうか?

また、文字列は送信される前にJSONでエンコードされているため、次のように\u0647\u064a\u0643 \u0628\u0646\u0643\u0648\u0646 \n\u0639\u064a\u0634 ...なります。生の文字列として解釈され、u0647だけが5バイトである可能性がありますか?

私はここで何をすべきですか?明らかなエラーはありますか、それとも正しい質問をしていませんか?

4

4 に答える 4

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
于 2012-12-02T00:02:02.367 に答える
1

バイト長にカットする必要があるため、最初に.encode('utf-8')文字列をカットしてから、コード ポイントの境界でカットする必要があります。

UTF-8 では、ASCII ( <= 127) は 1 バイトです。2 つ以上の最上位ビットが設定されているバイト( >= 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 でも機能することをテストできます。

于 2012-12-02T01:42:23.890 に答える
1

あなたの他の質問に投稿したアルゴリズムを使用して、これは Unicode 文字列を UTF-8 でエンコードし、UTF-8 シーケンス全体のみを切り捨てて、最大長以下のエンコード長に到達します。

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
    عيش بجنون تون تون تو
于 2012-12-06T07:10:14.793 に答える