str Vários caracteres substituem por Python




replace string python 2 (8)

Eu preciso substituir alguns caracteres da seguinte forma: & -> \& , # -> \# , ...

Eu codifiquei como segue, mas acho que deveria haver um jeito melhor. Alguma dica?

strs = strs.replace('&', '\&')
strs = strs.replace('#', '\#')
...

FYI, isso é de pouca ou nenhuma utilidade para o OP, mas pode ser útil para outros leitores (por favor, não downvote, estou ciente disso).

Como um exercício um pouco ridículo, mas interessante, queria ver se eu poderia usar a programação funcional em Python para substituir vários caracteres. Tenho certeza que isso não bate apenas chamando replace () duas vezes. E se o desempenho foi um problema, você poderia facilmente bater isso em ferrugem, C, julia, perl, java, javascript e talvez até mesmo awk. Ele usa um pacote externo de 'ajudantes' chamado pytoolz , acelerado via cython ( cytoolz, é um pacote pypi ).

from cytoolz.functoolz import compose
from cytoolz.itertoolz import chain,sliding_window
from itertools import starmap,imap,ifilter
from operator import itemgetter,contains
text='&hello#hi&yo&'
char_index_iter=compose(partial(imap, itemgetter(0)), partial(ifilter, compose(partial(contains, '#&'), itemgetter(1))), enumerate)
print '\\'.join(imap(text.__getitem__, starmap(slice, sliding_window(2, chain((0,), char_index_iter(text), (len(text),))))))

Eu nem vou explicar isso porque ninguém se incomodaria em usar isso para realizar várias substituições. No entanto, eu me senti um pouco realizado em fazer isso e pensei que poderia inspirar outros leitores ou ganhar um concurso de ofuscação de código.


>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi

Basta encadear as funções de replace como esta

strs = "abc&def#ghi"
print strs.replace('&', '\&').replace('#', '\#')
# abc\&def\#ghi

Se as substituições forem mais numerosas, você pode fazer isso dessa maneira genérica

strs, replacements = "abc&def#ghi", {"&": "\&", "#": "\#"}
print "".join([replacements.get(c, c) for c in strs])
# abc\&def\#ghi

Você pode considerar escrever uma função genérica de escape:

def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])

>>> esc = mk_esc('&#')
>>> print esc('Learn & be #1')
Learn \& be \#1

Desta forma você pode tornar sua função configurável com uma lista de caracteres que devem ser escapados.



Usando reduce, que está disponível em python2.7 e python3. *, Você pode facilmente substituir substrings mutiple de uma maneira limpa e pythonic.

# Lets define a helper method to make it easy to use
def replacer(text, replacements):
    return reduce(
        lambda text, ptuple: text.replace(ptuple[0], ptuple[1]), 
        replacements, text
    )

if __name__ == '__main__':
    uncleaned_str = "abc&def#ghi"
    cleaned_str = replacer(uncleaned_str, [("&","\&"),("#","\#")])
    print(cleaned_str) # "abc\&def\#ghi"

Em python2.7 você não precisa importar reduce, mas em python3. * Você precisa importá-lo do módulo functools.


Substituindo dois caracteres

Eu cronometrei todos os métodos nas respostas atuais junto com um extra.

Com uma string de entrada de abc&def#ghi e substituindo & -> \ & e # -> #, o mais rápido era encadear as substituições como: text.replace('&', '\&').replace('#', '\#') .

Horários para cada função:

  • a) 1000000 loops, o melhor de 3: 1,47 μs por loop
  • b) 1000000 loops, o melhor de 3: 1,51 μs por loop
  • c) 100.000 voltas, melhor de 3: 12.3 μs por loop
  • d) 100.000 voltas, melhor de 3: 12 μs por loop
  • e) 100.000 voltas, o melhor de 3: 3.27 μs por loop
  • f) 1000000 loops, o melhor de 3: 0,817 μs por loop
  • g) 100.000 voltas, melhor de 3: 3.64 μs por loop
  • h) 1000000 loops, o melhor de 3: 0,927 μs por loop
  • i) 1000000 loops, o melhor de 3: 0,814 μs por loop

Aqui estão as funções:

def a(text):
    chars = "&#"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['&','#']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([&#])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('&#')
def e(text):
    esc(text)


def f(text):
    text = text.replace('&', '\&').replace('#', '\#')


def g(text):
    replacements = {"&": "\&", "#": "\#"}
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('&', r'\&')
    text = text.replace('#', r'\#')


def i(text):
    text = text.replace('&', r'\&').replace('#', r'\#')

Cronometrado assim:

python -mtimeit -s"import time_functions" "time_functions.a('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.b('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.c('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.d('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.e('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.f('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.g('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.h('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.i('abc&def#ghi')"

Substituindo 17 caracteres

Aqui está um código similar para fazer o mesmo, mas com mais caracteres para escapar (\ `* _ {}> # + -.! $):

def a(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([\\`*_{}[]()>#+-.!$])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('\\`*_{}[]()>#+-.!$')
def e(text):
    esc(text)


def f(text):
    text = text.replace('\\', '\\\\').replace('`', '\`').replace('*', '\*').replace('_', '\_').replace('{', '\{').replace('}', '\}').replace('[', '\[').replace(']', '\]').replace('(', '\(').replace(')', '\)').replace('>', '\>').replace('#', '\#').replace('+', '\+').replace('-', '\-').replace('.', '\.').replace('!', '\!').replace('$', '\$')


def g(text):
    replacements = {
        "\\": "\\\\",
        "`": "\`",
        "*": "\*",
        "_": "\_",
        "{": "\{",
        "}": "\}",
        "[": "\[",
        "]": "\]",
        "(": "\(",
        ")": "\)",
        ">": "\>",
        "#": "\#",
        "+": "\+",
        "-": "\-",
        ".": "\.",
        "!": "\!",
        "$": "\$",
    }
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('\\', r'\\')
    text = text.replace('`', r'\`')
    text = text.replace('*', r'\*')
    text = text.replace('_', r'\_')
    text = text.replace('{', r'\{')
    text = text.replace('}', r'\}')
    text = text.replace('[', r'\[')
    text = text.replace(']', r'\]')
    text = text.replace('(', r'\(')
    text = text.replace(')', r'\)')
    text = text.replace('>', r'\>')
    text = text.replace('#', r'\#')
    text = text.replace('+', r'\+')
    text = text.replace('-', r'\-')
    text = text.replace('.', r'\.')
    text = text.replace('!', r'\!')
    text = text.replace('$', r'\$')


def i(text):
    text = text.replace('\\', r'\\').replace('`', r'\`').replace('*', r'\*').replace('_', r'\_').replace('{', r'\{').replace('}', r'\}').replace('[', r'\[').replace(']', r'\]').replace('(', r'\(').replace(')', r'\)').replace('>', r'\>').replace('#', r'\#').replace('+', r'\+').replace('-', r'\-').replace('.', r'\.').replace('!', r'\!').replace('$', r'\$')

Aqui estão os resultados para a mesma string de entrada abc&def#ghi :

  • a) 100.000 voltas, melhor de 3: 6.72 μs por loop
  • b) 100.000 voltas, melhor de 3: 2,64 μs por loop
  • c) 100.000 voltas, melhor de 3: 11.9 μs por loop
  • d) 100.000 voltas, melhor de 3: 4.92 μs por loop
  • e) 100.000 voltas, o melhor de 3: 2.96 μs por loop
  • f) 100.000 voltas, melhor de 3: 4.29 μs por loop
  • g) 100.000 voltas, melhor de 3: 4.68 μs por loop
  • h) 100.000 voltas, melhor de 3: 4.73 μs por loop
  • i) 100.000 voltas, melhor de 3: 4.24 μs por loop

E com uma string de entrada mais longa ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$ ):

  • a) 100.000 voltas, melhor de 3: 7,59 μs por loop
  • b) 100.000 voltas, melhor de 3: 6,54 μs por loop
  • c) 100.000 voltas, o melhor de 3: 16.9 μs por loop
  • d) 100.000 voltas, melhor de 3: 7.29 μs por loop
  • e) 100.000 voltas, melhor de 3: 12.2 μs por loop
  • f) 100.000 voltas, melhor de 3: 5,38 μs por loop
  • g) 10000 voltas, melhor de 3: 21,7 μs por loop
  • h) 100.000 loops, melhor de 3: 5.7 μs por loop
  • i) 100.000 voltas, melhor de 3: 5,13 μs por loop

Adicionando algumas variantes:

def ab(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        text = text.replace(ch,"\\"+ch)


def ba(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        if c in text:
            text = text.replace(c, "\\" + c)

Com a entrada mais curta:

  • ab) 100.000 voltas, melhor de 3: 7.05 μs por loop
  • ba) 100.000 voltas, melhor de 3: 2.4 μs por loop

Com a entrada mais longa:

  • ab) 100.000 voltas, melhor de 3: 7.71 μs por loop
  • ba) 100.000 voltas, melhor de 3: 6.08 μs por loop

Então eu vou usar ba para legibilidade e velocidade.

Termo aditivo

Provocada por hacks nos comentários, uma diferença entre ab e ba é o if c in text: check. Vamos testá-los contra mais duas variantes:

def ab_with_check(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)

def ba_without_check(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)

Tempos em μs por loop no Python 2.7.14 e 3.6.3 e em uma máquina diferente do conjunto anterior, portanto, não podem ser comparados diretamente.

╭────────────╥──────┬───────────────┬──────┬──────────────────╮
 Py, input    ab   ab_with_check   ba   ba_without_check 
╞════════════╬══════╪═══════════════╪══════╪══════════════════╡
 Py2, short  8.81     4.22        3.45     8.01          
 Py3, short  5.54     1.34        1.46     5.34          
├────────────╫──────┼───────────────┼──────┼──────────────────┤
 Py2, long   9.3      7.15        6.85     8.55          
 Py3, long   7.43     4.38        4.41     7.02          
└────────────╨──────┴───────────────┴──────┴──────────────────┘

Nos podemos concluir que:

  • Aqueles com o cheque são até 4x mais rápidos do que aqueles sem o cheque

  • ab_with_check está um pouco à frente no Python 3, mas ba (com check) tem uma vantagem maior no Python 2

  • No entanto, a maior lição aqui é que o Python 3 é até 3x mais rápido que o Python 2 ! Não há uma diferença enorme entre o mais lento no Python 3 e o mais rápido no Python 2!


Você sempre vai preceder uma barra invertida? Se sim, tente

import re
rx = re.compile('([&#])')
#                  ^^ fill in the characters here.
strs = rx.sub('\\\\\\1', strs)

Pode não ser o método mais eficiente, mas acho que é o mais fácil.





replace