2012-04-10 12 views
9

Si usa string.split() en una cadena de Python, devuelve una lista de cadenas. Estas subcadenas que se han dividido son copias de su parte de la cadena primaria.corta en cadenas inmutables por referencia y no copia

¿Es posible obtener en cambio un objeto de corte más económico que contenga solo una referencia, desplazamiento y longitud para dividir los bits?

¿Y es posible tener alguna 'vista de cadena' para extraer y tratar estas subcadenas como si todavía fueran cadenas sin hacer una copia de sus bytes?

(I pedir ya que tengo muy grandes cadenas Quiero cortar y estoy quedando sin memoria de vez en cuando;. Retirar las copias sería una victoria barata guiada por perfiles)

+0

Las respuestas a continuación que usan buffer() solo se aplican a 2.7. memoryview() no se puede usar con cadenas unicode, que son cadenas normales en 3.x. –

Respuesta

17

buffer le dará un solo lectura ver en una cadena.

>>> s = 'abcdefghijklmnopqrstuvwxyz' 
>>> b = buffer(s, 2, 10) 
>>> b 
<read-only buffer for 0x7f935ee75d70, size 10, offset 2 at 0x7f935ee5a8f0> 
>>> b[:] 
'cdefghijkl' 
+0

Guau, y aquí estaba pensando que sabía todos los builtins. TIL. –

+0

Ampliando esto, ¿hay recetas/módulos estándar para dividir() en estos búferes? – Will

+0

No, pero probablemente pueda adaptar [uno de estos] (http://stackoverflow.com/questions/3862010/is-there-a-generator-version-of-string-split-in-python). –

1

Aquí está la envoltura rápida de tampón que se me ocurrió; Pude usar esto en lugar de cadenas clásicas sin cambiar el código que se esperaba consumir cadenas.

class StringView: 
    def __init__(self,s,start=0,size=sys.maxint): 
     self.s, self.start, self.stop = s, start, min(start+size,len(s)) 
     self.size = self.stop - self.start 
     self._buf = buffer(s,start,self.size) 
    def find(self,sub,start=0,stop=None): 
     assert start >= 0, start 
     assert (stop is None) or (stop <= self.size), stop 
     ofs = self.s.find(sub,self.start+start,self.stop if (stop is None) else (self.start+stop)) 
     if ofs != -1: ofs -= self.start 
     return ofs 
    def split(self,sep=None,maxsplit=sys.maxint): 
     assert maxsplit > 0, maxsplit 
     ret = [] 
     if sep is None: #whitespace logic 
      pos = [self.start,self.start] # start and stop 
      def eat(whitespace=False): 
       while (pos[1] < self.stop) and (whitespace == (ord(self.s[pos[1]])<=32)): 
        pos[1] += 1 
      def eat_whitespace(): 
       eat(True) 
       pos[0] = pos[1] 
      eat_whitespace() 
      while pos[1] < self.stop: 
       eat() 
       ret.append(self.__class__(self.s,pos[0],pos[1]-pos[0])) 
       eat_whitespace() 
       if len(ret) == maxsplit: 
        ret.append(self.__class__(self.s,pos[1])) 
        break 
     else: 
      start = stop = 0 
      while len(ret) < maxsplit: 
       stop = self.find(sep,start) 
       if -1 == stop: 
        break 
       ret.append(self.__class__(self.s,self.start+start,stop-start)) 
       start = stop + len(sep) 
      ret.append(self.__class__(self.s,self.start+start,self.size-start)) 
     return ret 
    def split_str(self,sep=None,maxsplit=sys.maxint): 
     "if you really want strings and not views" 
     return [str(sub) for sub in self.split(sep,maxsplit)] 
    def __cmp__(self,s): 
     if isinstance(s,self.__class__): 
      return cmp(self._buf,s._buf) 
     assert isinstance(s,str), type(s) 
     return cmp(self._buf,s) 
    def __len__(self): 
     return self.size 
    def __str__(self): 
     return str(self._buf) 
    def __repr__(self): 
     return "'%s'"%self._buf 

if __name__=="__main__": 
    test_str = " this: is: a: te:st str:ing :" 
    test = Envelope.StringView(test_str) 
    print "find('is')" 
    print "\t",test_str.find("is") 
    print "\t",test.find("is") 
    print "find('is',4):" 
    print "\t",test_str.find("is",4) 
    print "\t",test.find("is",4) 
    print "find('is',4,7):" 
    print "\t",test_str.find("is",4,7) 
    print "\t",test.find("is",4,7) 
    print "split():" 
    print "\t",test_str.split() 
    print "\t",test.split() 
    print "split(None,2):" 
    print "\t",test_str.split(None,2) 
    print "\t",test.split(None,2) 
    print "split(':'):" 
    print "\t",test_str.split(":") 
    print "\t",test.split(":") 
    print "split('x'):" 
    print "\t",test_str.split("x") 
    print "\t",test.split("x") 
    print "''.split('x'):" 
    print "\t","".split("x") 
    print "\t",Envelope.StringView("").split("x") 
+0

Debería considerar escribir la estrofa principal como un doctest en el caso real. –

+0

En un sistema de 32 bits, cada instancia de esta clase usará 232 bytes de memoria, en un sistema de 64 bits será aún más, por lo que solo valdrá la pena para subcadenas bastante largas. Al menos debe usar '__slots__' para reducir la memoria que consume cada instancia a aproximadamente la mitad de esa cantidad. –

+0

Para ahorrar aún más memoria, deshazte del objeto del búfer o deshazte de 's',' start' y 'stop'. En cualquier caso, deshazte de 'size'. –

1

Los objetos de cadena siempre apuntan a un búfer terminado en NUL en Python, por lo que las subcadenas deben copiarse. Como señaló Ignacio, puede usar buffer() para obtener una vista de solo lectura en la memoria de cadena. La función incorporada buffer() ha sido reemplazada por los objetos más versátiles memoryview, que están disponibles en Python 2.7 y 3.x (buffer() se ha ido en Python 3.x).

s = "abcd" * 50 
view = memoryview(s) 
subview = view[10:20] 
print subview.tobytes() 

Este código imprime

cdabcdabcd 

Tan pronto como se llama a tobytes(), se creará una copia de la cadena, pero lo mismo sucede al rebanar los viejos buffer objetos como en la respuesta de Ignacio.

+0

sí, es la copia que estoy muy interesado en evitar; pensamientos sobre cómo obtener algo que siempre sigue siendo una vista pero funciona como una cadena? – Will

+0

@Will: Ambas, la solución de Ignacio y esta, evite la copia si solo mantiene alrededor de la vista buffer/memory. Si desea usarlo como una cadena, debe convertirlo temporalmente en una cadena y trabajar en él.Y como dije antes, los almacenamientos intermedios de cadenas de Python tienen terminación NUL, por lo que es imposible usar una porción de otra cadena como un buffer de cadenas. –

+0

Quise decir más graznido como un pato; He agregado 'in' e iteración a mi StringView y funciona muy bien. Es una pena que no esté incorporado realmente. – Will

Cuestiones relacionadas