Python: división de la cadena Unicode en los límites de las palabras

Necesito tomar una cuerda y acortarla a 140 caracteres.

Actualmente estoy haciendo:

if len(tweet) > 140: tweet = re.sub(r"\s+", " ", tweet) #normalize space footer = "… " + utils.shorten_urls(post['url']) avail = 140 - len(footer) words = tweet.split() result = "" for word in words: word += " " if len(word) > avail: break result += word avail -= len(word) tweet = (result + footer).strip() assert len(tweet) <= 140 

Así que esto funciona muy bien para el inglés, e inglés como cadenas, pero falla para una cadena china porque tweet.split() solo devuelve una matriz:

 >>> s = u"简讯:新華社報道,美國總統奧巴馬乘坐的「空軍一號」專機晚上10時42分進入上海空域,預計約30分鐘後抵達浦東國際機場,開展他上任後首次訪華之旅。" >>> s u'\u7b80\u8baf\uff1a\u65b0\u83ef\u793e\u5831\u9053\uff0c\u7f8e\u570b\u7e3d\u7d71\u5967\u5df4\u99ac\u4e58\u5750\u7684\u300c\u7a7a\u8ecd\u4e00\u865f\u300d\u5c08\u6a5f\u665a\u4e0a10\u664242\u5206\u9032\u5165\u4e0a\u6d77\u7a7a\u57df\uff0c\u9810\u8a08\u7d0430\u5206\u9418\u5f8c\u62b5\u9054\u6d66\u6771\u570b\u969b\u6a5f\u5834\uff0c\u958b\u5c55\u4ed6\u4e0a\u4efb\u5f8c\u9996\u6b21\u8a2a\u83ef\u4e4b\u65c5\u3002' >>> s.split() [u'\u7b80\u8baf\uff1a\u65b0\u83ef\u793e\u5831\u9053\uff0c\u7f8e\u570b\u7e3d\u7d71\u5967\u5df4\u99ac\u4e58\u5750\u7684\u300c\u7a7a\u8ecd\u4e00\u865f\u300d\u5c08\u6a5f\u665a\u4e0a10\u664242\u5206\u9032\u5165\u4e0a\u6d77\u7a7a\u57df\uff0c\u9810\u8a08\u7d0430\u5206\u9418\u5f8c\u62b5\u9054\u6d66\u6771\u570b\u969b\u6a5f\u5834\uff0c\u958b\u5c55\u4ed6\u4e0a\u4efb\u5f8c\u9996\u6b21\u8a2a\u83ef\u4e4b\u65c5\u3002'] 

¿Cómo debo hacer esto para que maneje I18N? ¿Tiene sentido esto en todos los idiomas?

Estoy en Python 2.5.4 si eso importa.

El chino no suele tener espacios en blanco entre las palabras, y los símbolos pueden tener diferentes significados según el contexto. Tendrá que entender el texto para dividirlo en un límite de palabra. En otras palabras, lo que estás tratando de hacer no es fácil en general.

Para la segmentación de palabras en chino y otras tareas avanzadas en el procesamiento de lenguaje natural, considere NLTK como un buen punto de partida, si no es una solución completa; es un completo conjunto de herramientas basado en Python, particularmente bueno para aprender sobre técnicas de procesamiento de NL (y no rara vez es bueno suficiente para ofrecerle una solución viable a algunos de estos problemas).

la re.U tratará \s acuerdo con la base de datos de propiedades de caracteres Unicode.

La cadena dada, sin embargo, aparentemente no contiene ningún carácter de espacio en blanco según la base de datos Unicode de Python:

 >>> x = u'\u7b80\u8baf\uff1a\u65b0\u83ef\u793e\u5831\u9053\uff0c\u7f8e\u570b\u7e3d\u7d71\u5967\u5df4\u99ac\u4e58\u5750\u7684\u300c\u7a7a\u8ecd\u4e00\u865f\u300d\u5c08\u6a5f\u665a\u4e0a10\u664242\u5206\u9032\u5165\u4e0a\u6d77\u7a7a\u57df\uff0c\u9810\u8a08\u7d0430\u5206\u9418\u5f8c\u62b5\u9054\u6d66\u6771\u570b\u969b\u6a5f\u5834\uff0c\u958b\u5c55\u4ed6\u4e0a\u4efb\u5f8c\u9996\u6b21\u8a2a\u83ef\u4e4b\u65c5\u3002' >>> re.compile(r'\s+', re.U).split(x) [u'\u7b80\u8baf\uff1a\u65b0\u83ef\u793e\u5831\u9053\uff0c\u7f8e\u570b\u7e3d\u7d71\u5967\u5df4\u99ac\u4e58\u5750\u7684\u300c\u7a7a\u8ecd\u4e00\u865f\u300d\u5c08\u6a5f\u665a\u4e0a10\u664242\u5206\u9032\u5165\u4e0a\u6d77\u7a7a\u57df\uff0c\u9810\u8a08\u7d0430\u5206\u9418\u5f8c\u62b5\u9054\u6d66\u6771\u570b\u969b\u6a5f\u5834\uff0c\u958b\u5c55\u4ed6\u4e0a\u4efb\u5f8c\u9996\u6b21\u8a2a\u83ef\u4e4b\u65c5\u3002'] 

Probé la solución con PyAPNS para notificaciones push y solo quería compartir lo que funcionó para mí. El problema que tuve es que truncar a 256 bytes en UTF-8 daría como resultado que se eliminara la notificación. Tenía que asegurarme de que la notificación estuviera codificada como “unicode_escape” para que funcionara. Supongo que esto se debe a que el resultado se envía como JSON y no como UTF-8 sin formato. De todos modos aquí está la función que me funcionó:

 def unicode_truncate(s, length, encoding='unicode_escape'): encoded = s.encode(encoding)[:length] return encoded.decode(encoding, 'ignore') 

Después de hablar con algunos hablantes nativos de cantonés, mandarín y japonés, parece que lo correcto es difícil, pero mi algoritmo actual todavía tiene sentido para ellos en el contexto de las publicaciones en Internet.

Es decir, están acostumbrados a la “división en el espacio y agregar … al final” el tratamiento.

Así que voy a ser perezoso y seguir con eso, hasta que reciba las quejas de las personas que no lo entienden.

El único cambio en mi implementación original sería no forzar un espacio en la última palabra, ya que no es necesario en ningún idioma (y usar el carácter Unicode … lugar de … three dots para guardar 2 caracteres)

Básicamente, en CJK (Excepto en coreano con espacios), necesita búsquedas en el diccionario para segmentar las palabras correctamente. Dependiendo de su definición exacta de “palabra”, el japonés puede ser más difícil que eso, ya que no todas las variantes de una palabra (es decir, “行 行 う” vs. “行 た”) aparecerán en el diccionario. Si vale la pena el esfuerzo depende de su aplicación.

Esto apunta la decisión de romper la palabra al módulo de re, pero puede funcionar lo suficientemente bien para usted.

 import re def shorten(tweet, footer="", limit=140): """Break tweet into two pieces at roughly the last word break before limit. """ lower_break_limit = limit / 2 # limit under which to assume breaking didn't work as expected limit -= len(footer) tweet = re.sub(r"\s+", " ", tweet.strip()) m = re.match(r"^(.{,%d})\b(?:\W|$)" % limit, tweet, re.UNICODE) if not m or m.end(1) < lower_break_limit: # no suitable word break found # cutting at an arbitrary location, # or if len(tweet) < lower_break_limit, this will be true and # returning this still gives the desired result return tweet[:limit] + footer return m.group(1) + footer 

¡Guarda dos caracteres y usa una elipsis ( , 0x2026 ) en lugar de tres puntos!