2012-04-10 20 views
9

Se si utilizza string.split() su una stringa Python, viene restituito un elenco di stringhe. Queste sottostringhe che sono state suddivise sono copie della loro parte della stringa padre.slice a stringhe immutabili per riferimento e non copia

È possibile ottenere invece un oggetto fetta meno costoso che contiene solo un riferimento, l'offset e la lunghezza dei bit divisi?

E è possibile avere una "vista stringa" per estrarre e trattare queste sottostringhe come se fossero stringhe senza fare copia dei loro byte?

(mi chiedo come ho molto grandi stringhe voglio affettare e sono a corto di memoria, di tanto in tanto;. Rimuovendo le copie sarebbe un buon win profilo-guida)

+0

Le risposte seguenti che utilizzano il buffer() si applicano solo a 2.7. memoryview() non può essere usato con stringhe unicode, che sono normali stringhe in 3.x. –

risposta

17

buffer vi darà una sola lettura vista su una corda.

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

Wow, e qui stavo pensando di conoscere tutti i builtins. TIL. –

+0

Espandendosi su questo, ci sono ricette/moduli standard per split() in questi buffer? – Will

+0

No, ma probabilmente è possibile adattare [uno di questi] (http://stackoverflow.com/questions/3862010/is-there-a-generator-version-of-string-split-in-python). –

1

Ecco il buffer wrapper rapido simile a una stringa che ho trovato; Sono stato in grado di utilizzare questo al posto delle stringhe classiche senza modificare il codice che prevedeva di consumare stringhe.

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

Si dovrebbe considerare di scrivere la stanza principale come doctest nella cosa reale. –

+0

Su un sistema a 32 bit, ogni singola istanza di questa classe utilizzerà 232 byte di memoria, su un sistema a 64 bit lo sarà ancora di più, quindi questo vale solo per sottostringhe piuttosto lunghe. Dovresti almeno usare '__slots__' per ridurre la memoria che ogni istanza consuma a circa la metà di tale importo. –

+0

Per risparmiare ancora più memoria, puoi eliminare l'oggetto buffer o eliminare 's',' start' e 'stop'. In ogni caso, sbarazzarsi di 'taglia'. –

1

Gli oggetti stringa puntano sempre su un buffer con terminazione NUL in Python, quindi le sottostringhe devono essere copiate. Come ha sottolineato Ignacio, è possibile utilizzare buffer() per ottenere una vista di sola lettura sulla memoria delle stringhe. La funzione integrata buffer() è stata sostituita dagli oggetti più versatili memoryview, che sono disponibili in Python 2.7 e 3.x (buffer() non è presente in Python 3.x).

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

Questo codice stampe

cdabcdabcd 

Non appena si chiama tobytes(), verrà creata una copia della stringa, ma lo stesso accade quando affettare le vecchie buffer oggetti come nella risposta di Ignacio.

+0

sì, è la copia che sono super-appassionato di evitare; pensieri su come ottenere qualcosa che rimane sempre una vista funziona ancora come una stringa? – Will

+0

@Will: Entrambi, la soluzione di Ignacio e questa, evitare la copia se si mantiene solo il buffer/memoryview. Se vuoi usarlo come una stringa, devi trasformarlo temporaneamente in una stringa e lavorarci sopra.E come ho detto prima, i buffer di stringa Python sono terminati NUL, quindi è impossibile usare solo una parte di un'altra stringa come buffer di stringa. –

+0

Intendevo più ciarlatano come un'anatra; Ho aggiunto "in" e l'iterazione al mio StringView e funziona bene. Peccato solo che non sia un built-in davvero. – Will

Problemi correlati