Зняття всього, крім буквено-цифрових знаків, з рядка в Python


337

Який найкращий спосіб позбавити всіх не буквено-цифрових символів із рядка, використовуючи Python?

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

Для запису я не просто хочу знімати періоди та коми (та інші розділові знаки), а й лапки, дужки тощо.


7
Чи хвилюєтесь ви про міжнародні буквено-цифрові символи, такі як 'æøå', 'مرحبا', 'สวัสดี', 'こ ん に ち は'?
Пімін Костянтин Кефалукос

4
@PiminKonstantinKefaloukos Так, я дбаю про міжнародні правила, звідси мій коментар щодо прийнятої відповіді на використання re.UNICODE.
Марк ван Лент

Відповіді:


336

Я просто приуротив деякі функції з цікавості. У цих тестах я виймаю з рядка не алфавітно-цифрові символи 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 _] +', '', пропозиція, прапори = re.UNICODE)
PALEN

268

Регулярні вирази на допомогу:

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

За визначенням Python '\W== [^a-zA-Z0-9_], що виключає всі numbers, lettersі_


2
Що робить знак плюс у регулярному вираженні? (Я знаю, що це означає, просто цікаво, навіщо це потрібно для повторного підключення.)
Марк ван Лент

7
@Mark: Я думаю, що це пришвидшить заміну, оскільки заміна за один раз позбудеться від усіх символів, що не містять слів, в блоці, а не видаляючи їх по одному.
DrAl

2
Так, я це визначив під час налаштування критичного коду щодо ефективності. Якщо є значні проміжки символів для заміни швидкості, це величезна кількість.
Мурахи Аасма

20
Це може не бути актуальним у цьому випадку, але \Wтакож збереже підкреслення.
Blixt

12
Дотримуючись підказки @Blixt, якщо ви хочете лише букви та цифри, ви можете зробити re.sub (r '[^ a-zA-Z0-9]', '', your_string)
Nigini

68

Використовуйте метод 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
Це, безумовно, найбільш пітонічне рішення.
кодигмен

1
Це майже переконує мене, але я б запропонував string.punctuationзамість цього''.join(c for c in map(chr, range(256)) if not c.isalnum())
ArnauOrriols

1
Зауважте, що це працює для strоб'єктів, але не для unicodeоб'єктів.
Явар

@John Machin Це по суті розуміння списку, яке передається як аргумент .join()?
AdjunctProfessorFalcon

41

Ви можете спробувати:

print ''.join(ch for ch in some_string if ch.isalnum())

15
>>> import re
>>> string = "Kl13@£$%[};'\""
>>> pattern = re.compile('\W')
>>> string = re.sub(pattern, '', string)
>>> print string
Kl13

мені сподобалась ваша відповідь, але вона також знімає арабські символи, чи можете ви мені сказати, як їх утримати
Charif DZ

13

Як щодо:

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рядках. Потім він об'єднує список разом у рядок.


Здається, що string.ascii_letters містить лише букви (duh), а не цифри. Мені також потрібні номери ...
Марк ван Лент

Додавання string.digits дійсно вирішить проблему, про яку я тільки що згадував. :)
Марк ван Лент

Так, я зрозумів, що коли повернувся читати ваше запитання. Зауважте до себе: навчіться читати!
DrAl

4

Як відхилення від деяких інших відповідей тут, я пропоную дійсно простий і гнучкий спосіб визначити набір символів, яким ви хочете обмежити вміст рядка. У цьому випадку я дозволяю буквено-цифровим знакам PLUS штрих та підкреслення. Просто додайте або видаляйте символи з моїх, PERMITTED_CHARSяк це відповідає вашому випадку використання.

PERMITTED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" 
someString = "".join(c for c in someString if c in PERMITTED_CHARS)

2
Замість жорсткого кодування дозволених символів, які схильні до тонких помилок, використовуйте string.digits + string.ascii_letters + '_-'.
Reti43

Ваша пропозиція не є помилковою, але вона також не рятує багато символів "набору тексту", якщо це ваша мета. Якщо ви скопіюєте моє повідомлення, ви також не будете друкувати! Справжній сенс моєї відповіді полягає в тому, щоб дозволити чіткий, відкритий і простий спосіб точно визначити, які символи ви хочете дозволити.
BuvinJ

Ви можете поєднати ці пропозиції, SPECIAL_CHARS = '_-'а потім використовуватиstring.digits + string.ascii_letters + SPECIAL_CHARS
BuvinJ

Це було пропозицією з точки зору того, що є розумним, якщо ми не робимо гольф коду. "Ходіння" по клавіатурі для набору 52 букв алфавіту займає значно більше часу, ніж імпорт пакету для використання об'єкта чи двох. І це не включає час для повторної перевірки, ви все правильно ввели. Це про хороші практики, ось і все.
Reti43

Я чую тебе! Моя реальна суть тут - надзвичайна гнучкість, на випадок, якщо ви хочете бути більш конкретними з набором персонажів.
BuvinJ

4
sent = "".join(e for e in sent if e.isalpha())

Я спробую пояснити: він проходить через всі символи рядків e for e in sentі перевіряє через if e.isalpha()оператор, чи поточний символ є алфавітним символом, якщо так - приєднується до sentзмінної через, sent = "".join()і всі не алфавітні символи будуть замінені на ""(порожній рядок), оскільки з joinфункції.
Sysanin

Оскільки це робиться цикл на персонажа, а не покладається на C regex, чи не це надзвичайно повільно?
dcsan


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)

оцініть це, якщо ця відповідь корисна!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.