Làm cách nào để xác minh rằng một chuỗi chỉ chứa các chữ cái, số, dấu gạch dưới và dấu gạch ngang?


86

Tôi biết cách thực hiện việc này nếu tôi lặp qua tất cả các ký tự trong chuỗi nhưng tôi đang tìm kiếm một phương pháp thanh lịch hơn.


5
Bạn đang nói về các chữ cái ascii, ngôn ngữ cụ thể hoặc unicode?
jfs

Câu trả lời:


122

Một biểu thức chính quy sẽ thực hiện thủ thuật với rất ít mã:

import re

...

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

25
Bạn có thể đơn giản hóa điều đó thành: ^ [\ w \ d _-] * $
Prestaul

13
Giải pháp này sẽ phù hợp với các chuỗi có độ dài bằng không. sử dụng + thay vì * để làm cho nó khớp với các chuỗi gồm 1 hoặc nhiều ký tự.
Jerub 18/09/08

10
@Prestaul: \wbao gồm \d_do đó isvalid = re.match(r'[\w-]+$', astr)hoặc isinvalid = re.search(r'[^\w-]', astr). Có thể có locale.setlocalehoặc chuỗi unicode cần phải xem xét thêm.
jfs

1
Sửa lại: isvalid = re.match(r'[\w-]*$', astr)- chuỗi trống là hợp lệ.
jfs

Làm cách nào bạn cũng có thể cho phép dấu chấm / chấm (.) Trong regex đó? Chỉnh sửa, đây là cách thực hiện: ^ [a-zA-Z0-9 -_ \ s \.] + $
fredrik

24

[Chỉnh sửa] Có một giải pháp khác chưa được đề cập và nó dường như hoạt động tốt hơn những giải pháp khác được đưa ra cho đến nay trong hầu hết các trường hợp.

Sử dụng string.translate để thay thế tất cả các ký tự hợp lệ trong chuỗi và xem chúng tôi có còn sót lại ký tự không hợp lệ nào không. Quá trình này diễn ra khá nhanh vì nó sử dụng hàm C bên dưới để thực hiện công việc, với rất ít mã bytecode của python tham gia.

Rõ ràng hiệu suất không phải là tất cả - tìm kiếm các giải pháp dễ đọc nhất có lẽ là cách tiếp cận tốt nhất khi không nằm trong một đường dẫn quan trọng về hiệu suất, nhưng chỉ để xem cách các giải pháp xếp chồng lên nhau, đây là so sánh hiệu suất của tất cả các phương pháp được đề xuất cho đến nay. check_trans là một trong những sử dụng phương thức string.translate.

Mã kiểm tra:

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()

Kết quả trên hệ thống của tôi là:

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

Phương pháp dịch có vẻ tốt nhất trong hầu hết các trường hợp, đáng kể như vậy với các chuỗi dài hợp lệ, nhưng bị đánh bại bởi các regex trong test_long_invalid (Có lẽ vì regex có thể cứu trợ ngay lập tức, nhưng dịch luôn phải quét toàn bộ chuỗi). Các phương pháp tiếp cận tập hợp thường kém nhất, chỉ đánh bại regex đối với trường hợp chuỗi rỗng.

Việc sử dụng all (x trong allow_set cho x in s) hoạt động tốt nếu kết quả sớm, nhưng có thể không tốt nếu phải lặp qua mọi ký tự. isSubSet và set chênh lệch có thể so sánh được và luôn tỷ lệ thuận với độ dài của chuỗi bất kể dữ liệu.

Có một sự khác biệt tương tự giữa các phương thức regex khớp với tất cả các ký tự hợp lệ và tìm kiếm các ký tự không hợp lệ. So khớp hoạt động tốt hơn một chút khi kiểm tra một chuỗi dài nhưng hoàn toàn hợp lệ, nhưng tệ hơn đối với các ký tự không hợp lệ ở gần cuối chuỗi.


1
Sử dụng string.ascii_lettersthay vì string.lettersnếu bạn không sử dụng re.LOCALE cờ cho regexps (nếu không bạn có thể nhận được kết quả dương tính giả trong check_trans(). string.maketrans()Sẽ không làm việc cho chuỗi unicode.
JFS

Đối với Python 3 / Unicode / from __future__ import unicode_literals), hãy sử dụng trans_table3 = dict((ord(char), '') for char in allowed_chars)và định nghĩa check_trans(s): return not s.translate(trans_table3). Nhưng nói chung, nó hoạt động kém hơn các phiên bản RE.
Hugo

14

Có nhiều cách để đạt được mục tiêu này, một số cách rõ ràng hơn những cách khác. Đối với mỗi ví dụ của tôi, 'True' có nghĩa là chuỗi được truyền vào là hợp lệ, 'False' có nghĩa là nó chứa các ký tự không hợp lệ.

Trước hết, có cách tiếp cận ngây thơ:

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

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

Sau đó, sử dụng một biểu thức chính quy, bạn có thể làm điều này với re.match (). Lưu ý rằng '-' phải ở cuối [] nếu không nó sẽ được sử dụng làm dấu phân cách 'phạm vi'. Cũng lưu ý $ có nghĩa là 'cuối chuỗi'. Các câu trả lời khác được lưu ý trong câu hỏi này sử dụng một lớp ký tự đặc biệt, '\ w', tôi luôn thích sử dụng một dải lớp ký tự rõ ràng bằng cách sử dụng [] vì nó dễ hiểu hơn mà không cần phải tra cứu hướng dẫn tham khảo nhanh và dễ đặc biệt hơn- trường hợp.

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

Một giải pháp khác lưu ý rằng bạn có thể thực hiện đối sánh nghịch đảo với các biểu thức chính quy, tôi đã đưa điều đó vào đây ngay bây giờ. Lưu ý rằng [^ ...] đảo ngược lớp ký tự vì ^ được sử dụng:

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

Bạn cũng có thể làm điều gì đó khó khăn với đối tượng 'set'. Hãy xem ví dụ này, ví dụ này sẽ xóa khỏi chuỗi ban đầu tất cả các ký tự được phép, để lại cho chúng tôi một tập hợp chứa a) không có gì hoặc b) các ký tự vi phạm khỏi chuỗi:

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

Trong lần kiểm tra regex đầu tiên của bạn, không nên "[a-zA-Z0-9 _-] + $" là "[a-zA-Z0-9 _-] * $". Chuỗi trống có thể được coi là phù hợp.
Brian

Sử dụng string.ascii_lettersnếu bạn sử dụng regexps '[a-zA-Z]'.
jfs


4

Để thay thế cho việc sử dụng regex, bạn có thể làm điều đó trong Bộ:

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

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

3
 pat = re.compile ('[^\w-]')

 def onlyallowed(s):
    return not pat.search (s)

1

Biểu thức chính quy có thể rất linh hoạt.

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

\w: Chỉ có [a-zA-Z0-9_]

Vì vậy, bạn cần thêm ký tự -cho dấu gạch ngang căn lề.

+: Khớp một hoặc nhiều lần lặp lại ký tự trước đó. Tôi đoán bạn không chấp nhận đầu vào trống. Nhưng nếu bạn làm vậy, hãy thay đổi thành *.

^: Khớp với phần bắt đầu của chuỗi.

$: Khớp với phần cuối của chuỗi.

Bạn cần hai ký tự đặc biệt này vì bạn cần tránh trường hợp sau. Các ký tự không mong muốn như &ở đây có thể xuất hiện giữa các mẫu phù hợp.

&&&PATTERN&&PATTERN


0

Vâng, bạn có thể yêu cầu sự giúp đỡ của regex, tuyệt vời ở đây :)

mã:

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'

Đầu ra:

yes  

Hi vọng điêu nay co ich :)


-1

Bạn luôn có thể sử dụng toàn bộ danh sách và kiểm tra kết quả, sẽ ít tốn tài nguyên hơn một chút so với sử dụng regex: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])


Vui lòng kiểm tra mã của bạn trước khi đăng. Một giải pháp dựa trên câu trả lời của bạn bị hỏng mà chạy là: tất cả (c trong string.letters + string.digits + "_" cho c trong mystring)
Jerub

2
Thats có được nhiều hơn nguồn lực chuyên sâu hơn một regex. Nó thực hiện quét tuyến tính cho mọi ký tự (tốt hơn là nên xây dựng một bộ trước thời hạn) và bạn không cần thiết phải xây dựng một danh sách khi khả năng hiểu của trình tạo sẽ nhẹ hơn.
Brian

-1

Đây là điều gì đó dựa trên "cách tiếp cận ngây thơ" của Jerub (ngây thơ là lời của anh ấy, không phải của tôi!):

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

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

Nếu ALLOWEDlà một chuỗi thì tôi nghĩ c in ALLOWEDsẽ liên quan đến việc lặp lại từng ký tự trong chuỗi cho đến khi nó tìm thấy khớp hoặc đến cuối. Mà, để trích dẫn Joel Spolsky, là một cái gì đó của thuật toán Shlemiel the Painter .

Nhưng việc kiểm tra sự tồn tại trong một tập hợp sẽ hiệu quả hơn, hoặc ít nhất là ít phụ thuộc hơn vào số lượng ký tự được phép. Chắc chắn cách tiếp cận này nhanh hơn một chút trên máy của tôi. Nó rõ ràng và tôi nghĩ rằng nó hoạt động đủ tốt cho hầu hết các trường hợp (trên máy chậm của tôi, tôi có thể xác nhận hàng chục nghìn chuỗi ish ngắn trong một phần của giây). Tôi thích nó.

THỰC TẾ trên máy tính của tôi, regexp hoạt động nhanh hơn nhiều lần và đơn giản như thế này (được cho là đơn giản hơn). Vì vậy, đó có lẽ là cách tốt nhất về phía trước.


-4

sử dụng regex và xem nó có khớp không!

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

1
Tất cả các ký tự này phải nằm trong một lớp, nếu không bạn sẽ nhận được âm sai. Ngoài ra, bạn đã quên bao gồm các điểm đánh dấu đầu chuỗi và cuối chuỗi ... như thế này, nó sẽ luôn khớp với điều kiện là có một ký tự hợp lệ.
Thomas

1
Điều này thực sự sẽ khớp ngay cả khi không có ký tự hợp lệ. Độ dài khớp bằng không. Ngoài ra, nó không có trong python.
Jerub 18/09/08
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.