ฉันจะตรวจสอบได้อย่างไรว่าสตริงมีเฉพาะตัวอักษรตัวเลขขีดล่างและขีดกลางเท่านั้น


87

ฉันรู้วิธีการทำเช่นนี้หากฉันวนซ้ำตัวอักษรทั้งหมดในสตริง แต่ฉันกำลังมองหาวิธีที่หรูหรากว่านี้


5
คุณกำลังพูดถึง ascii อักษรเฉพาะสถานที่หรือ Unicode?
jfs

คำตอบ:


124

นิพจน์ทั่วไปจะทำเคล็ดลับโดยใช้โค้ดน้อยมาก:

import re

...

if re.match("^[A-Za-z0-9_-]*$", my_little_string):
    # do something here

25
คุณสามารถลดความซับซ้อนเป็น: ^ [\ w \ d _-] * $
Prestaul

13
โซลูชันนี้จะจับคู่สตริงที่มีความยาวเป็นศูนย์ ใช้ + แทน * เพื่อให้ตรงกับสตริงที่มีอักขระ 1 ตัวขึ้นไป
Jerub

10
@Prestaul: \wรวม\dและ_ดังนั้นหรือisvalid = re.match(r'[\w-]+$', astr) isinvalid = re.search(r'[^\w-]', astr)การมีอยู่ของlocale.setlocaleสตริงหรือ Unicode ที่เป็นไปได้จำเป็นต้องพิจารณาเพิ่มเติม
jfs

1
การแก้ไข: isvalid = re.match(r'[\w-]*$', astr)- สตริงว่างถูกต้อง
jfs

คุณจะอนุญาตจุด / จุด (.) ในนิพจน์ทั่วไปได้อย่างไร แก้ไขนี่คือวิธี: ^ [a-zA-Z0-9 -_ \ s \.] + $
fredrik

25

[แก้ไข] ยังไม่มีวิธีแก้ปัญหาอื่นที่ยังไม่ได้กล่าวถึงและดูเหมือนว่าจะมีประสิทธิภาพดีกว่าวิธีอื่น ๆ ที่ให้ไว้ในกรณีส่วนใหญ่

ใช้ string.translate เพื่อแทนที่อักขระที่ถูกต้องทั้งหมดในสตริงและดูว่าเรามีอักขระที่ไม่ถูกต้องเหลืออยู่หรือไม่ สิ่งนี้ค่อนข้างเร็วเนื่องจากใช้ฟังก์ชัน C พื้นฐานในการทำงานโดยมีไบโทโค้ด python น้อยมากที่เกี่ยวข้อง

เห็นได้ชัดว่าประสิทธิภาพไม่ใช่ทุกอย่างการไปหาโซลูชันที่อ่านได้มากที่สุดน่าจะเป็นแนวทางที่ดีที่สุดเมื่อไม่ได้อยู่ใน codepath ที่สำคัญด้านประสิทธิภาพ แต่เพียงเพื่อดูว่าโซลูชันซ้อนกันอย่างไรนี่คือการเปรียบเทียบประสิทธิภาพของวิธีการทั้งหมดที่เสนอจนถึงตอนนี้ check_trans เป็นวิธีการที่ใช้ string.translate

รหัสทดสอบ:

import string, re, timeit

pat = re.compile('[\w-]*$')
pat_inv = re.compile ('[^\w-]')
allowed_chars=string.ascii_letters + string.digits + '_-'
allowed_set = set(allowed_chars)
trans_table = string.maketrans('','')

def check_set_diff(s):
    return not set(s) - allowed_set

def check_set_all(s):
    return all(x in allowed_set for x in s)

def check_set_subset(s):
    return set(s).issubset(allowed_set)

def check_re_match(s):
    return pat.match(s)

def check_re_inverse(s): # Search for non-matching character.
    return not pat_inv.search(s)

def check_trans(s):
    return not s.translate(trans_table,allowed_chars)

test_long_almost_valid='a_very_long_string_that_is_mostly_valid_except_for_last_char'*99 + '!'
test_long_valid='a_very_long_string_that_is_completely_valid_' * 99
test_short_valid='short_valid_string'
test_short_invalid='/$%$%&'
test_long_invalid='/$%$%&' * 99
test_empty=''

def main():
    funcs = sorted(f for f in globals() if f.startswith('check_'))
    tests = sorted(f for f in globals() if f.startswith('test_'))
    for test in tests:
        print "Test %-15s (length = %d):" % (test, len(globals()[test]))
        for func in funcs:
            print "  %-20s : %.3f" % (func, 
                   timeit.Timer('%s(%s)' % (func, test), 'from __main__ import pat,allowed_set,%s' % ','.join(funcs+tests)).timeit(10000))
        print

if __name__=='__main__': main()

ผลลัพธ์ในระบบของฉันคือ:

Test test_empty      (length = 0):
  check_re_inverse     : 0.042
  check_re_match       : 0.030
  check_set_all        : 0.027
  check_set_diff       : 0.029
  check_set_subset     : 0.029
  check_trans          : 0.014

Test test_long_almost_valid (length = 5941):
  check_re_inverse     : 2.690
  check_re_match       : 3.037
  check_set_all        : 18.860
  check_set_diff       : 2.905
  check_set_subset     : 2.903
  check_trans          : 0.182

Test test_long_invalid (length = 594):
  check_re_inverse     : 0.017
  check_re_match       : 0.015
  check_set_all        : 0.044
  check_set_diff       : 0.311
  check_set_subset     : 0.308
  check_trans          : 0.034

Test test_long_valid (length = 4356):
  check_re_inverse     : 1.890
  check_re_match       : 1.010
  check_set_all        : 14.411
  check_set_diff       : 2.101
  check_set_subset     : 2.333
  check_trans          : 0.140

Test test_short_invalid (length = 6):
  check_re_inverse     : 0.017
  check_re_match       : 0.019
  check_set_all        : 0.044
  check_set_diff       : 0.032
  check_set_subset     : 0.037
  check_trans          : 0.015

Test test_short_valid (length = 18):
  check_re_inverse     : 0.125
  check_re_match       : 0.066
  check_set_all        : 0.104
  check_set_diff       : 0.051
  check_set_subset     : 0.046
  check_trans          : 0.017

วิธีการแปลดูเหมือนจะดีที่สุดในกรณีส่วนใหญ่เป็นอย่างมากดังนั้นด้วยสตริงที่ยาวใช้ได้ แต่ถูกตีกลับโดย regexes ใน test_long_invalid (น่าจะเป็นเพราะ regex สามารถประกันตัวได้ทันที แต่การแปลจะต้องสแกนทั้งสตริงเสมอ) วิธีการ set มักจะแย่ที่สุดโดยจะตี regexes สำหรับสตริงว่างเท่านั้น

การใช้ all (x ใน allow_set สำหรับ x in s) จะทำงานได้ดีถ้ามันออกมาก่อนเวลา แต่อาจไม่ดีหากต้องวนซ้ำทุกตัวอักษร isSubSet และความแตกต่างของชุดสามารถเปรียบเทียบได้และเป็นสัดส่วนที่สม่ำเสมอกับความยาวของสตริงโดยไม่คำนึงถึงข้อมูล

มีความแตกต่างที่คล้ายกันระหว่างเมธอด regex ที่ตรงกับอักขระที่ถูกต้องทั้งหมดและค้นหาอักขระที่ไม่ถูกต้อง การจับคู่จะทำงานได้ดีขึ้นเล็กน้อยเมื่อตรวจสอบสตริงที่ยาว แต่ใช้ได้อย่างสมบูรณ์ แต่แย่กว่าสำหรับอักขระที่ไม่ถูกต้องใกล้กับส่วนท้ายของสตริง


1
ใช้string.ascii_lettersแทนstring.lettersถ้าคุณไม่ได้ใช้ธง re.LOCALE สำหรับ regexps (มิฉะนั้นคุณอาจได้รับผลบวกปลอมในcheck_trans(). string.maketrans()จะไม่ทำงานสำหรับสตริง Unicode.
JFS

1
สำหรับงูหลาม 3 / Unicode / from __future__ import unicode_literals), การใช้งานtrans_table3 = dict((ord(char), '') for char in allowed_chars)และ check_trans(s): return not s.translate(trans_table3)def แต่โดยทั่วไปแล้วจะทำงานได้แย่กว่ารุ่น RE
Hugo

15

มีหลายวิธีในการบรรลุเป้าหมายนี้บางวิธีชัดเจนกว่าวิธีอื่น ๆ สำหรับแต่ละตัวอย่างของฉัน 'True' หมายความว่าสตริงที่ส่งถูกต้อง 'False' หมายความว่ามีอักขระที่ไม่ถูกต้อง

ก่อนอื่นมีวิธีการที่ไร้เดียงสา:

import string
allowed = string.letters + string.digits + '_' + '-'

def check_naive(mystring):
    return all(c in allowed for c in mystring)

จากนั้นมีการใช้นิพจน์ทั่วไปคุณสามารถทำได้ด้วย re.match () โปรดทราบว่า "-" จะต้องอยู่ท้าย [] มิฉะนั้นจะใช้เป็นตัวคั่น "ช่วง" โปรดสังเกตว่า $ ซึ่งหมายถึง 'end of string' คำตอบอื่น ๆ ที่ระบุไว้ในคำถามนี้ใช้คลาสอักขระพิเศษ '\ w' ฉันมักจะชอบใช้ช่วงคลาสอักขระที่ชัดเจนโดยใช้ [] เพราะง่ายต่อการเข้าใจโดยไม่ต้องค้นหาคู่มืออ้างอิงฉบับย่อและง่ายต่อการใช้พิเศษ กรณี.

import re
CHECK_RE = re.compile('[a-zA-Z0-9_-]+$')
def check_re(mystring):
    return CHECK_RE.match(mystring)

อีกวิธีหนึ่งตั้งข้อสังเกตว่าคุณสามารถจับคู่ผกผันกับนิพจน์ทั่วไปได้ฉันได้รวมไว้ที่นี่แล้ว โปรดสังเกตว่า [^ ... ] สลับคลาสอักขระเนื่องจากใช้ ^:

CHECK_INV_RE = re.compile('[^a-zA-Z0-9_-]')
def check_inv_re(mystring):
   return not CHECK_INV_RE.search(mystring)

นอกจากนี้คุณยังสามารถทำบางสิ่งที่ยุ่งยากด้วยวัตถุ 'set' ดูตัวอย่างนี้ซึ่งจะลบอักขระทั้งหมดที่ได้รับอนุญาตออกจากสตริงเดิมโดยปล่อยให้เรามีชุดที่ประกอบด้วย a) nothing หรือ b) อักขระที่ละเมิดออกจากสตริง:

def check_set(mystring):
    return not set(mystring) - set(allowed)

ในการทดสอบ regex ครั้งแรกของคุณไม่ควร "[a-zA-Z0-9 _-] + $" be "[a-zA-Z0-9 _-] * $" สตริงว่างควรได้รับการพิจารณาว่าตรงกัน
Brian

ใช้string.ascii_lettersถ้าคุณใช้ regexps '[a-zA-Z]'
jfs

12

หากไม่ใช่สำหรับขีดกลางและขีดล่างวิธีแก้ปัญหาที่ง่ายที่สุดคือ

my_little_string.isalnum()

(ส่วน3.6.1ของการอ้างอิงไลบรารี Python)


น่าเสียดายที่ลิงก์ไม่ทำงานอีกต่อไป แต่นี่คือส่วนที่เกี่ยวข้องPython » 3.3.6 เอกสารประกอบ»ไลบรารีมาตรฐาน Python » 4.7.1 วิธีสตริง ขอบคุณ @ นี่คือสิ่งที่ฉันต้องการจริงๆ
Thanos

4

อีกทางเลือกหนึ่งนอกเหนือจากการใช้ regex คุณสามารถทำได้ในชุด:

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

if Set(my_little_sting).issubset(allowed_chars):
    # your action
    print True


1

นิพจน์ทั่วไปสามารถยืดหยุ่นได้มาก

import re;
re.fullmatch("^[\w-]+$", target_string) # fullmatch looks also workable for python 3.4

\w: เท่านั้น [a-zA-Z0-9_]

ดังนั้นคุณต้องเพิ่ม-char เพื่อปรับค่าขีดกลาง char

+: จับคู่การทำซ้ำอย่างน้อยหนึ่งครั้งของอักขระก่อนหน้า ฉันเดาว่าคุณไม่ยอมรับการป้อนข้อมูลเปล่า *แต่ถ้าคุณทำเปลี่ยนไป

^: ตรงกับจุดเริ่มต้นของสตริง

$: ตรงกับจุดสิ้นสุดของสตริง

คุณต้องใช้อักขระพิเศษสองตัวนี้เนื่องจากคุณต้องหลีกเลี่ยงกรณีต่อไปนี้ ตัวอักษรที่ไม่ต้องการเช่น&นี้อาจปรากฏขึ้นระหว่างรูปแบบที่ตรงกัน

&&&PATTERN&&PATTERN


0

คุณสามารถขอความช่วยเหลือจาก regex ได้ที่นี่ :)

รหัส:

import re

string = 'adsfg34wrtwe4r2_()' #your string that needs to be matched.
regex = r'^[\w\d_()]*$' # you can also add a space in regex if u want to allow it in the string  
if re.match(regex,string):
    print 'yes'
else: 
    print 'false'

เอาท์พุต:

yes  

หวังว่านี่จะช่วยได้ :)


-1

คุณสามารถใช้การทำความเข้าใจรายการและตรวจสอบผลลัพธ์ได้ตลอดเวลามันจะใช้ทรัพยากรน้อยกว่าการใช้ regex เล็กน้อย: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])


โปรดทดสอบรหัสของคุณก่อนโพสต์ วิธีแก้ปัญหาตามคำตอบที่เสียไปของคุณที่รันคือ all (c ใน string.letters + string.digits + "_" สำหรับ c ใน mystring)
Jerub

2
Thats ไปได้มากมากขึ้นทรัพยากรเข้มกว่านิพจน์ทั่วไป กำลังทำการสแกนเชิงเส้นสำหรับทุกอักขระ (ดีกว่าที่จะสร้างชุดล่วงหน้า) และคุณไม่จำเป็นต้องสร้างรายการเมื่อความเข้าใจของเครื่องกำเนิดไฟฟ้าจะมีน้ำหนักเบามากขึ้น
Brian

-1

นี่คือสิ่งที่อิงจาก "วิธีการไร้เดียงสา" ของ Jerub (คำพูดของเขาไร้เดียงสาไม่ใช่ของฉัน!)

import string
ALLOWED = frozenset(string.ascii_letters + string.digits + '_' + '-')

def check(mystring):
    return all(c in ALLOWED for c in mystring)

ถ้าALLOWEDเป็นสตริงฉันคิดว่าc in ALLOWEDจะเกี่ยวข้องกับการวนซ้ำอักขระแต่ละตัวในสตริงจนกว่าจะพบรายการที่ตรงกันหรือถึงจุดสิ้นสุด ซึ่งจะพูดโจ Spolsky เป็นอะไรบางอย่างที่Shlemiel อัลกอริทึมจิตรกร

แต่การทดสอบการมีอยู่ในชุดควรมีประสิทธิภาพมากกว่าหรืออย่างน้อยก็น้อยขึ้นอยู่กับจำนวนอักขระที่อนุญาต แน่นอนว่าวิธีนี้เร็วกว่าเล็กน้อยในเครื่องของฉัน ชัดเจนและฉันคิดว่ามันทำงานได้ดีพอสำหรับกรณีส่วนใหญ่ (ในเครื่องที่ช้าฉันสามารถตรวจสอบสตริง short-ish นับหมื่นได้ในเสี้ยววินาที) ฉันชอบมัน.

จริงๆแล้ว regexp บนเครื่องของฉันทำงานได้เร็วขึ้นหลายเท่าและทำได้ง่ายเพียงเท่านี้ (เนื้อหาง่ายกว่า) นั่นอาจเป็นวิธีที่ดีที่สุดในอนาคต


-4

ใช้ regex แล้วดูว่าตรงกันไหม!

([a-z][A-Z][0-9]\_\-)*

1
อักขระเหล่านี้ทั้งหมดต้องอยู่ในคลาสเดียวมิฉะนั้นคุณจะได้รับเชิงลบที่ผิดพลาด นอกจากนี้คุณลืมใส่เครื่องหมายจุดเริ่มต้นของสตริงและจุดสิ้นสุดของสตริง ... เช่นนี้มันจะจับคู่เสมอตราบเท่าที่มีอักขระที่ถูกต้องหนึ่งตัว
Thomas

1
สิ่งนี้จะจับคู่ได้จริงแม้ว่าจะไม่มีอักขระที่ถูกต้องก็ตาม จับคู่ความยาวเป็นศูนย์ นอกจากนี้ยังไม่ได้อยู่ใน python
Jerub
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.