Удаление всего, кроме буквенно-цифровых символов из строки в Python

339

Каков наилучший способ вырезать все не алфавитно-цифровые символы из строки, используя Python?

Решения, представленные в PHP-варианте этого вопроса , вероятно, будут работать с некоторыми незначительными изменениями, но не кажутся мне «питонными».

Для справки, я хочу не просто удалять точки и запятые (и другие знаки пунктуации), но также и кавычки, скобки и т. Д.

Марк ван Лент
источник
8
Вы заботитесь о международных буквенно-цифровых символах, таких как ''øå', 'مرحبا', 'สวัสดี', 'こ ん に ち は'?
Пимин Константин Кефалукос
4
@PiminKonstantinKefaloukos Да, я забочусь о международных символах, поэтому я комментирую принятый ответ на использование re.UNICODE.
Марк ван Лент

Ответы:

337

Я просто рассчитал некоторые функции из любопытства. В этих тестах я удаляю не алфавитно-цифровые символы из строки string.printable(часть встроенного stringмодуля). Использование скомпилировано '[\W_]+'и pattern.sub('', str)признано самым быстрым.

$ python -m timeit -s \
     "import string" \
     "''.join(ch for ch in string.printable if ch.isalnum())" 
10000 loops, best of 3: 57.6 usec per loop

$ python -m timeit -s \
    "import string" \
    "filter(str.isalnum, string.printable)"                 
10000 loops, best of 3: 37.9 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]', '', string.printable)"
10000 loops, best of 3: 27.5 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]+', '', string.printable)"                
100000 loops, best of 3: 15 usec per loop

$ python -m timeit -s \
    "import re, string; pattern = re.compile('[\W_]+')" \
    "pattern.sub('', string.printable)" 
100000 loops, best of 3: 11.2 usec per loop
Отто Альмендингер
источник
2
Очень интересные результаты: я ожидал, что регулярные выражения будут медленнее. Интересно, что я попробовал это с одним другим вариантом ( valid_characters = string.ascii_letters + string.digitsза которым следовало, join(ch for ch in string.printable if ch in valid_characters)и это было на 6 микросекунд быстрее, чем isalnum()опция. Хотя все еще намного медленнее, чем регулярное выражение.
DrAl
+1, время измерения хорошо! (но в предпоследнем случае pattern.sub('', string.printable)вместо этого сделайте глупый вызов re.sub, когда у вас есть объект RE! -).
Алекс Мартелли
46
Для записи: используйте, re.compile('[\W_]+', re.UNICODE)чтобы сделать Unicode безопасным.
Марк ван Лент
3
как вы делаете это, не удаляя пустое пространство?
maudulus
6
сделать это без удаления пробела: re.sub ('[\ W _] +', '', предложение, flags = re.UNICODE)
PALEN
269

Регулярные выражения на помощь:

import re
re.sub(r'\W+', '', your_string)

По определению Python '\W== [^a-zA-Z0-9_], что исключает все numbers, lettersи_

Муравьи Аасма
источник
2
Что делает знак плюс в регулярном выражении? (Я знаю, что это значит, просто любопытно, зачем это нужно для повторного участия.)
Марк ван Лент
7
@Mark: я полагаю, что это ускорит замену, поскольку замена избавит от всех несловарных символов в блоке за один раз, а не удалит их один за другим.
DrAl
2
Да, я проверял это, настраивая некоторый критичный к производительности код некоторое время назад. Если есть значительные промежутки символов для замены, ускорение огромно.
Муравьи Аасма
20
Это может быть не актуально в этом случае, но также \Wбудет подчеркивать.
блик
12
Следуя подсказке @Blixt, если вам нужны только буквы и цифры, вы можете сделать re.sub (r '[^ a-zA-Z0-9]', '', your_string)
Нигини
69

Используйте метод str.translate () .

Предполагая, что вы будете делать это часто:

(1) Однажды создайте строку, содержащую все символы, которые вы хотите удалить:

delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())

(2) Всякий раз, когда вы хотите сократить строку:

scrunched = s.translate(None, delchars)

Стоимость установки, вероятно, выгодно отличается от re.compile; предельные издержки намного ниже:

C:\junk>\python26\python -mtimeit -s"import string;d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s=string.printable" "s.translate(None,d)"
100000 loops, best of 3: 2.04 usec per loop

C:\junk>\python26\python -mtimeit -s"import re,string;s=string.printable;r=re.compile(r'[\W_]+')" "r.sub('',s)"
100000 loops, best of 3: 7.34 usec per loop

Примечание. Использование string.printable в качестве эталонных данных дает шаблону «[\ W _] +» несправедливое преимущество ; все не алфавитно-цифровые символы находятся в одной связке ... в типичных данных можно сделать более одной замены:

C:\junk>\python26\python -c "import string; s = string.printable; print len(s),repr(s)"
100 '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

Вот что произойдет, если вы дадите re.sub немного больше работы:

C:\junk>\python26\python -mtimeit -s"d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s='foo-'*25" "s.translate(None,d)"
1000000 loops, best of 3: 1.97 usec per loop

C:\junk>\python26\python -mtimeit -s"import re;s='foo-'*25;r=re.compile(r'[\W_]+')" "r.sub('',s)"
10000 loops, best of 3: 26.4 usec per loop
Джон Мачин
источник
1
Использование перевода действительно немного быстрее. Даже при добавлении цикла for непосредственно перед выполнением подстановки / перевода (чтобы весы при установке весили меньше) все равно делает перевод примерно в 17 раз быстрее, чем регулярное выражение на моей машине. Хорошо знать.
Марк ван Лент
3
Это определенно самое питонное решение.
Codygman
1
Это почти убедило меня, но я бы предложил использовать string.punctuationвместо''.join(c for c in map(chr, range(256)) if not c.isalnum())
ArnauOrriols
1
Обратите внимание, что это работает для strобъектов, но не для unicodeобъектов.
Явар
@ Джон Мачин Это, по сути, понимание списка, которому передают в качестве аргумента .join()?
АдъюнктПрофессорФалькон
42

Вы можете попробовать:

print ''.join(ch for ch in some_string if ch.isalnum())
АРС
источник
16
>>> import re
>>> string = "Kl13@£$%[};'\""
>>> pattern = re.compile('\W')
>>> string = re.sub(pattern, '', string)
>>> print string
Kl13
DisplacedAussie
источник
1
Мне понравился ваш ответ, но он также удаляет арабские символы, можете ли вы сказать мне, как их сохранить
Чариф Д.З.
14

Как насчет:

def ExtractAlphanumeric(InputString):
    from string import ascii_letters, digits
    return "".join([ch for ch in InputString if ch in (ascii_letters + digits)])

Это работает с использованием списочного понимания, чтобы создать список символов, InputStringесли они присутствуют в комбинации ascii_lettersи digitsстроках. Затем он объединяет список в строку.

Dral
источник
Кажется, что string.ascii_letters содержит только буквы (дух), а не цифры. Мне также нужны цифры ...
Марк ван Лент
Добавление string.digits действительно решит проблему, которую я только что упомянул. :)
Марк ван Лент
Да, я понял это, когда вернулся, чтобы прочитать ваш вопрос. Примечание для себя: научиться читать!
DrAl
5

В дополнение к некоторым другим ответам здесь, я предлагаю действительно простой и гибкий способ определения набора символов, которым вы хотите ограничить содержимое строки. В этом случае я разрешаю буквенно-цифровые символы плюс тире и подчеркивание. Просто добавьте или удалите символы из моего, PERMITTED_CHARSкак вам подходит.

PERMITTED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" 
someString = "".join(c for c in someString if c in PERMITTED_CHARS)
BuvinJ
источник
3
Вместо жесткого кодирования используйте разрешенные символы, которые подвержены незначительным ошибкам string.digits + string.ascii_letters + '_-'.
Reti43
Ваше предложение не является неправильным, но оно также не спасает многих символов «ввода», если это ваша цель. Если вы скопируете мой пост, у вас тоже не будет опечатки! Однако суть моего ответа в том, чтобы позволить явным, открытым и простым способом точно определить, какие символы вы хотите разрешить.
BuvinJ
В качестве среднего уровня вы можете объединить эти предложения в SPECIAL_CHARS = '_-'и затем использоватьstring.digits + string.ascii_letters + SPECIAL_CHARS
BuvinJ
Это было предложение с точки зрения разумного, если только мы не занимаемся гольф-кодом. «Ходьба» вокруг клавиатуры для ввода 52 букв алфавита по порядку занимает значительно больше времени, чем импорт пакета для использования одного или двух объектов. И это не включает время, чтобы перепроверить, что вы все правильно набрали. Это о хорошей практике, вот и все.
Reti43
Я слышу тебя! Моя реальная цель здесь - чрезвычайная гибкость, на случай, если вы хотите получить более конкретную информацию о вашем наборе символов.
BuvinJ
5
sent = "".join(e for e in sent if e.isalpha())
Том Калвин
источник
Я попытаюсь объяснить: он просматривает все строковые символы в e for e in sentи проверяет через if e.isalpha()оператор, является ли текущий символ буквенным символом, если так - присоединяет его к sentпеременной с помощью sent = "".join()и все не алфавитные символы будут заменены на ""(пустая строка), потому что из joinфункции.
Sysanin
поскольку это делает цикл для каждого символа, а не полагается на регулярное выражение C, разве это не слишком медленно?
dcsan
3
for char in my_string:
    if not char.isalnum():
        my_string = my_string.replace(char,"")
Младший огун
источник
2

Синхронизация со случайными строками печатных форм ASCII:

from inspect import getsource
from random import sample
import re
from string import printable
from timeit import timeit

pattern_single = re.compile(r'[\W]')
pattern_repeat = re.compile(r'[\W]+')
translation_tb = str.maketrans('', '', ''.join(c for c in map(chr, range(256)) if not c.isalnum()))


def generate_test_string(length):
    return ''.join(sample(printable, length))


def main():
    for i in range(0, 60, 10):
        for test in [
            lambda: ''.join(c for c in generate_test_string(i) if c.isalnum()),
            lambda: ''.join(filter(str.isalnum, generate_test_string(i))),
            lambda: re.sub(r'[\W]', '', generate_test_string(i)),
            lambda: re.sub(r'[\W]+', '', generate_test_string(i)),
            lambda: pattern_single.sub('', generate_test_string(i)),
            lambda: pattern_repeat.sub('', generate_test_string(i)),
            lambda: generate_test_string(i).translate(translation_tb),

        ]:
            print(timeit(test), i, getsource(test).lstrip('            lambda: ').rstrip(',\n'), sep='\t')


if __name__ == '__main__':
    main()

Результат (Python 3.7):

       Time       Length                           Code                           
6.3716264850008880  00  ''.join(c for c in generate_test_string(i) if c.isalnum())
5.7285426190064750  00  ''.join(filter(str.isalnum, generate_test_string(i)))
8.1875841680011940  00  re.sub(r'[\W]', '', generate_test_string(i))
8.0002205439959650  00  re.sub(r'[\W]+', '', generate_test_string(i))
5.5290945199958510  00  pattern_single.sub('', generate_test_string(i))
5.4417179649972240  00  pattern_repeat.sub('', generate_test_string(i))
4.6772285089973590  00  generate_test_string(i).translate(translation_tb)
23.574712151996210  10  ''.join(c for c in generate_test_string(i) if c.isalnum())
22.829975890002970  10  ''.join(filter(str.isalnum, generate_test_string(i)))
27.210196289997840  10  re.sub(r'[\W]', '', generate_test_string(i))
27.203713296003116  10  re.sub(r'[\W]+', '', generate_test_string(i))
24.008979928999906  10  pattern_single.sub('', generate_test_string(i))
23.945240008994006  10  pattern_repeat.sub('', generate_test_string(i))
21.830899796994345  10  generate_test_string(i).translate(translation_tb)
38.731336012999236  20  ''.join(c for c in generate_test_string(i) if c.isalnum())
37.942474347000825  20  ''.join(filter(str.isalnum, generate_test_string(i)))
42.169366310001350  20  re.sub(r'[\W]', '', generate_test_string(i))
41.933375883003464  20  re.sub(r'[\W]+', '', generate_test_string(i))
38.899814646996674  20  pattern_single.sub('', generate_test_string(i))
38.636144253003295  20  pattern_repeat.sub('', generate_test_string(i))
36.201238164998360  20  generate_test_string(i).translate(translation_tb)
49.377356811004574  30  ''.join(c for c in generate_test_string(i) if c.isalnum())
48.408927293996385  30  ''.join(filter(str.isalnum, generate_test_string(i)))
53.901889764994850  30  re.sub(r'[\W]', '', generate_test_string(i))
52.130339455994545  30  re.sub(r'[\W]+', '', generate_test_string(i))
50.061149017004940  30  pattern_single.sub('', generate_test_string(i))
49.366573111998150  30  pattern_repeat.sub('', generate_test_string(i))
46.649754120997386  30  generate_test_string(i).translate(translation_tb)
63.107938601999194  40  ''.join(c for c in generate_test_string(i) if c.isalnum())
65.116287978999030  40  ''.join(filter(str.isalnum, generate_test_string(i)))
71.477421126997800  40  re.sub(r'[\W]', '', generate_test_string(i))
66.027950693998720  40  re.sub(r'[\W]+', '', generate_test_string(i))
63.315361931003280  40  pattern_single.sub('', generate_test_string(i))
62.342320287003530  40  pattern_repeat.sub('', generate_test_string(i))
58.249303059004890  40  generate_test_string(i).translate(translation_tb)
73.810345625002810  50  ''.join(c for c in generate_test_string(i) if c.isalnum())
72.593953348005020  50  ''.join(filter(str.isalnum, generate_test_string(i)))
76.048324580995540  50  re.sub(r'[\W]', '', generate_test_string(i))
75.106637657001560  50  re.sub(r'[\W]+', '', generate_test_string(i))
74.681338128997600  50  pattern_single.sub('', generate_test_string(i))
72.430461594005460  50  pattern_repeat.sub('', generate_test_string(i))
69.394243567003290  50  generate_test_string(i).translate(translation_tb)

str.maketrans& str.translateявляется самым быстрым, но включает в себя все не-ASCII символы. re.compile& pattern.subмедленнее, но как-то быстрее чем ''.join& filter.

Соломон Уцко
источник
-1

Если я правильно понял, самый простой способ - это использовать регулярное выражение, поскольку оно предоставляет вам большую гибкость, но другой простой способ - использовать для следования за циклом - код с примером, который я также посчитал вхождения слова и сохранил в словаре.

s = """An... essay is, generally, a piece of writing that gives the author's own 
argument — but the definition is vague, 
overlapping with those of a paper, an article, a pamphlet, and a short story. Essays 
have traditionally been 
sub-classified as formal and informal. Formal essays are characterized by "serious 
purpose, dignity, logical 
organization, length," whereas the informal essay is characterized by "the personal 
element (self-revelation, 
individual tastes and experiences, confidential manner), humor, graceful style, 
rambling structure, unconventionality 
or novelty of theme," etc.[1]"""

d = {}      # creating empty dic      
words = s.split() # spliting string and stroing in list
for word in words:
    new_word = ''
    for c in word:
        if c.isalnum(): # checking if indiviual chr is alphanumeric or not
            new_word = new_word + c
    print(new_word, end=' ')
    # if new_word not in d:
    #     d[new_word] = 1
    # else:
    #     d[new_word] = d[new_word] +1
print(d)

Пожалуйста, оцените это, если этот ответ полезен!

Абхишек Пратап Сингх
источник