2012-04-10 6 views
9

Wenn Sie string.split() für eine Python-Zeichenfolge verwenden, wird eine Liste der Zeichenfolgen zurückgegeben. Diese Teilstrings, die aufgeteilt wurden, sind Kopien ihres Teils der Elternkette.Slices zu unveränderlichen Zeichenfolgen als Referenz und nicht kopieren

Ist es möglich, stattdessen ein billigeres Slice-Objekt zu erhalten, das nur eine Referenz, einen Versatz und eine Länge für die ausgeblendeten Bits enthält?

Und ist es möglich, eine 'String-Ansicht' zu haben, um diese Sub-Strings zu extrahieren und zu behandeln, als wären sie Strings, ohne eine Kopie ihrer Bytes zu erstellen?

(ich frage, wie ich habe sehr große Strings ich schneiden wollen und gelegentlich aus der Erinnerung leite;. Die Kopien zu entfernen wäre eine billige Profil-geführten Sieg sein)

+0

Die folgenden Antworten, die puffer() verwenden, gelten nur für 2.7. memoryview() kann nicht mit Unicode-Zeichenfolgen verwendet werden, bei denen es sich um normale Zeichenfolgen in 3.x handelt. –

Antwort

17

buffer geben Ihnen eine schreibgeschützte Sicht auf eine Zeichenkette.

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

Wow, und hier dachte ich, ich wüsste alle Builtins. TIL. –

+0

Gibt es dazu noch Standardrezepte/Module für die Aufteilung in diese Puffer? – Will

+0

Nein, aber Sie können wahrscheinlich [eine davon] anpassen (http://stackoverflow.com/questions/3862010/is-there-a-generator-version-of-string-split-in-python). –

1

Hier ist die schnelle Zeichenfolge-wie Puffer-Wrapper ich kam; Ich konnte dies anstelle von klassischen Strings verwenden, ohne den Code zu ändern, der Strings verbrauchen sollte.

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

Sie sollten in Erwägung ziehen, die Hauptzeilen als Doktest in der echten Sache zu schreiben. –

+0

Auf einem 32-Bit-System verwendet jede einzelne Instanz dieser Klasse 232 Byte Speicher, auf einem 64-Bit-System wird es noch mehr sein, so dass es sich nur für ziemlich lange Teilstrings lohnt. Sie sollten mindestens '__slots__' verwenden, um den Speicher zu reduzieren, den jede Instanz auf ungefähr die Hälfte dieser Menge verbraucht. –

+0

Um noch mehr Speicher zu sparen, entfernen Sie entweder das Pufferobjekt oder entfernen Sie 's',' start' und 'stop'. In jedem Fall, loswerden 'Größe'. –

1

String-Objekte zeigen immer auf einen NUL-terminierten Puffer in Python, daher müssen Teilstrings kopiert werden. Wie Ignacio darauf hingewiesen hat, können Sie buffer() verwenden, um eine schreibgeschützte Ansicht des Zeichenfolgenspeichers zu erhalten. Die integrierte Funktion buffer() wurde durch die vielseitigeren Objekte memoryview ersetzt, die in Python 2.7 und 3.x verfügbar sind (buffer() ist in Python 3.x verschwunden).

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

Dieser Code druckt

cdabcdabcd 

Sobald Sie tobytes() aufrufen, wird eine Kopie des Strings erstellt werden, aber das gleiche passiert, wenn die alten buffer Objekte wie in Ignacio Antwort schneiden.

+0

ja, es ist die Kopie, die ich sehr gerne vermeiden möchte; Gedanken darüber, wie man etwas bekommt, das immer eine Perspektive bleibt, funktioniert wie eine Schnur? – Will

+0

@Will: Beide, Ignacios Lösung und diese, vermeiden die Kopie, wenn Sie nur die Puffer-/Speicheransicht behalten. Wenn Sie es als Zeichenfolge verwenden möchten, müssen Sie es vorübergehend in eine Zeichenfolge umwandeln und daran arbeiten.Und wie bereits erwähnt, sind Python-String-Puffer NUL-terminiert, so dass es unmöglich ist, nur einen Teil einer anderen Zeichenfolge als Zeichenfolgenpuffer zu verwenden. –

+0

Ich meinte mehr Quacksalberei wie eine Ente; Ich habe 'In' und Iteration zu meiner StringView hinzugefügt und es funktioniert gut. Schade, es ist nicht wirklich eingebaut. – Will

Verwandte Themen