2016-06-21 17 views
0

Ich möchte operator.attrgetter und itertool.groupby verwenden, um eine Textzeichenfolge durch Leerzeichen aufzuteilen, wobei die Anzahl der Leerzeichen zwischen dem Text beibehalten wird. Das ist das erwartete Verhalten:Negieren `operator.attrgetter`

result = process('Am     G   C') 
assert result == [(2, 'Am'), (20, ' '), (1, 'G'), (10, ' '), (1, 'C')] 

Als ich sagte, ich attrgetter verwenden mag, weil ich es mehr pythonic finden als eine Lambda verwenden. Ich kann tun:

text = '''Am     G   C''' 
processed=((k, list(l))for k, l in groupby(text, attrgetter("isspace"))) 
result = [(len(l), "".join(l)) if k else (len(l), " ") for k, l in processed] 

Das wird aber zurück:

[(1, 'A'), (1, 'm'), (20, '     '), (1, 'G'), (10, '   '), (1, 'C')] 

Was ich aber brauchen, ist genau das Gegenteil, und ich versuchte:

from operator import neg, attrgetter 
text = '''Am     G   C''' 
processed = ((k,list(l)) for k,l in groupby(text, neg(attrgetter("isspace")))) 
result = [(len(l), "".join(l)) if k else (len(l), " ") for k, l in processed] 

Dies wirft:

TypeError: bad operand type for unary -: 'operator.attrgetter'

Wie kann ich den Wert von 0 negierenfür Schlüsselfunktionszwecke?

+2

Schreiben Sie einfach Ihre eigene Funktion, um es umzukehren und zu verwenden. – jonrsharpe

+1

Ich denke, die nächste, die Sie erhalten, ohne auf ein Lambda zurückgreifen, ist "" .__ ne__'. –

+0

In dieser speziellen Frage müssen Sie die Funktion hier nicht wirklich negieren, sondern negieren Sie einfach die Bedingung im If/Else-Ausdruck. Also wird 'x wenn y else z' zu 'x wenn nicht y sonst z'. z.B. 'result = [(len (l)," ".join (l)) wenn nicht k else (len (l)," ") für k, l in processed]'. Sie können auch 'str.ispace' über' attrgetter ("isspace") 'verwenden. – Dunes

Antwort

4

operator.attrgetter wird hier nicht direkt als Schlüsselfunktion arbeiten. attrgetter("isspace")(x) wird holen nur das "isspace" Attribut x, während Sie es auch anrufen müssen:

>>> attrgetter("isspace")(" ") 
<built-in method isspace of str object at 0x7f30c4301ab0> 
>>> attrgetter("isspace")(" ")() 
True 

Sie str.isspace stattdessen verwenden:

>>> processed = ((k, list(l)) for k, l in groupby(text, str.isspace)) 
>>> result = [(len(l), " ") if k else (len(l), "".join(l)) for k, l in processed] 
>>> result 
[(2, 'Am'), (20, ' '), (1, 'G'), (10, ' '), (1, 'C')] 
2

Nein, man kann nicht eine umgekehrte Funktion der der attrgetter machen oder eine andere Funktion mit dem Operator neg. Zunächst steht neg für Negation; z.B. neg(x) < =>-x. Und dies galt für False Ergebnisse in 0; True =>-1. Die boolsche Negation ist operator.not_. Aber selbst dann würde dies wenig dazu beitragen, den Rückgabewert zu negieren - und tatsächlich würde sogar das Negieren Ihnen nicht helfen.

Stattdessen sollten Sie einfach str.isspace an groupby übergeben; str.ispace ist eine ungebundene Methode, die ein Argument vom Typ str akzeptiert.

Was Sie hier sehen, ist, dass der Rückgabewert von attrgetter('isspace') mit jedem einzelnen Zeichen als Wert aufgerufen wird. Jeder Aufruf gibt eine gebunden Instanzmethode:

>>> attrgetter('isspace')('a') 
<built-in method isspace of str object at 0x7fb204de5110> 

Da jedes dieser gebundenen Methoden an eine andere Instanz gebunden sind, sind sie notwendigerweise voneinander verschieden, und nicht zu vergleichen, einander gleich, weshalb Ihr Code tut nicht das, was Sie erwarten würden. Um den tatsächlichen Wert zu erhalten, würden Sie zu Anruf haben diese Methode noch einmal:

>>> attrgetter('isspace')('a') 
False 

Für diesen Fall würde ich die itertools völlig Graben. Dies ist eine Nur-Text-Aufgabe, daher sind reguläre Ausdrücke und das re Modul der richtige Weg.

Es gibt keine Notwendigkeit, auch dieses Zeichen-für-Zeichen handhaben, wenn re.split wird sich bereits 90% erreichen, was Sie wollen:

>>> import re 
>>> s = 'Am     G   C' 
>>> parts = re.split('(\s+)', s) 
>>> parts 
['Am', '     ', 'G', '   ', 'C'] 

Dann nur eine Liste Verständnis verwenden diese in Länge zu machen Tupel, string:

>>> [(len(i), i) for i in parts] 
[(2, 'Am'), (20, '     '), (1, 'G'), (10, '   '), (1, 'C')] 

Das heißt, re.split Splits durch einen gegebenen regulären Ausdruck ist. \s+ entspricht einem oder mehreren Leerzeichen. Normalerweise wird das Trennzeichen verworfen. Enthält der reguläre Ausdruck jedoch eine Untergruppe (...), wird der Inhalt dieser Untergruppe ebenfalls in der Übereinstimmung beibehalten.

+0

Dies ist eigentlich eine viel besser lesbare Lösung. Vielen Dank. –