Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Trailing whitespace strip in <pre> is quadratic on a long whitespace run

Aperta Adatta ai principianti
#286 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
78/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
tooling

Direzione di ricerca

Il passaggio lento è re_pre_rstrip.sub in strip_pre in markdownify/init.py, intorno alla riga 78; la issue propone di sostituirlo con una chiamata a str.rstrip. Leggi le definizioni di re_pre_lstrip e re_pre_rstrip, poi riproduci il caso con un blocco <pre> da 64 KB di spazi finali passato a markdownify(). È completato quando questo caso viene eseguito ben sotto un secondo, la suite esistente di 83 test continua a passare e un nuovo test di tempistica copre le grafie di <pre> elencate nella issue.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

strip_pre() takes quadratic time on a <pre> block whose trailing whitespace run is a run of spaces not ended by a newline. Measured through markdownify() on tag 1.2.3, both trees in one isolated run:

HTML before after
16,000 B 0.6881 s 0.0003 s
32,000 B 2.6570 s 0.0004 s
65,536 B 9.1117 s 0.0007 s
131,072 B 35.5100 s 0.0014 s
16,000 B of ordinary words 0.0011 s 0.0009 s

Each doubling of the block quadruples the time, so the cost belongs to the shape rather than the size - the last row is a document of the same size that converts in a millisecond. The input is an HTML document, so nothing bounds it, and this is the default path: strip_pre defaults to STRIP, while STRIP_ONE uses the separate re_pre_rstrip1 = r'\n *$' and is unaffected.

Cause. re_pre_rstrip = re.compile(r'[ \n]*$') applied with .sub() at __init__.py:78. .sub restarts at every position in the string, and at each one the unbounded [ \n]* run expands and then backtracks looking for the $ that the following character denies.

It is worth saying why the costly shape is spaces and not newlines, because it is not obvious from the pattern alone: re_pre_lstrip = r'^[ \n]*\n' runs on the line before, so a leading newline run is consumed there and re_pre_rstrip is handed a one-character string. With no newline in the block, lstrip does not fire and rstrip receives the whole thing. On the same 65,536-byte input a newline run costs 0.0001 s and a space run 9.3704 s.

Fix. The docstring already describes a strip, so use one:

-    text = re_pre_rstrip.sub('', text)
+    text = text.rstrip(' \n')

Verification.

  • Test suite on both trees at tag 1.2.3: 83 passed, unchanged, with each run asserting which markdownify/__init__.py was loaded and whether it still contains the regex call.
  • Differential: 0 differences from current behaviour across all 29,524 strings of length 0 to 9 over {space, newline, x} - the whole alphabet the two patterns act on.
  • A new test converts a 64 KB <pre> block in under 0.5 s and checks seven <pre> spellings (blank lines, leading and trailing spaces, indentation, empty, whitespace-only). On 1.2.3 the timing test fails and the behaviour test passes.
  • Shapes aimed at the fix rather than the original (pure space run with no terminator, a leading x then a run, many short runs split by x) are all flat: worst 0.0003 s at 65,536 B.

re_pre_rstrip is now unused. I have deliberately left the definition in place to keep the diff to one line, since it is a module-level name that something downstream may import; say the word and I will remove it.

I am happy to open a pull request with the patch if that is easier.

Found with AI assistance (Claude) and verified by hand.

Patch: full patch
--- a/markdownify/__init__.py
+++ b/markdownify/__init__.py
@@ -75,7 +75,7 @@
 def strip_pre(text):
     """Strip all leading and trailing newlines from a <pre> string."""
     text = re_pre_lstrip.sub('', text)
-    text = re_pre_rstrip.sub('', text)
+    text = text.rstrip(' \n')
     return text
Lingua principale
Python
Stelle
2.3k
Fork
205
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di matthewwithanm/python-markdownify

Tutte le issue di matthewwithanm/python-markdownify

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.