2012-02-08 2 views
12

Tôi là một lập trình viên của Perl, những người đang cố gắng học Python bằng cách thực hiện một số công việc tôi đã thực hiện trước đó và chuyển đổi nó sang Python. Đây là NOT bản dịch từng dòng. Tôi muốn tìm hiểu kỹ thuật Python để thực hiện loại tác vụ này.Đường phân tích bằng Python: Sử dụng RE hay không?

Tôi đang phân tích tệp Windows INI. Tên các phần có định dạng:

[<type> <description>] 

<type> là một trường từ đơn và không phân biệt chữ hoa chữ thường. <description> có thể là nhiều từ.

Sau một phần, có một loạt thông số và giá trị. Các hình thức này có dạng:

<parameter> = <value> 

Thông số không có khoảng trắng và chỉ có thể chứa dấu gạch dưới, chữ cái và số (phân biệt chữ hoa chữ thường). Do đó, = đầu tiên là dải phân cách giữa tham số và giá trị. Có thể có khoảng trắng tách thông số và giá trị quanh dấu bằng. Có thể có thêm khoảng trắng ở đầu hoặc cuối dòng.

Trong Perl, tôi đã sử dụng biểu thức thông thường để phân tích cú pháp:

while (my $line = <CONTROL_FILE>) { 
    chomp($line); 
    next if ($line =~ /^\s*[#;']/);  #Comments start with "#", ";", or "'" 
    next if ($line =~ /^\s*$/);   #Ignore blank lines 

    if ($line =~ /^\s*\[\s*(\w+)\s+(.*)/) { #Section 
     say "This is a '$1' section called '$2'"; 
    } 
    elsif ($line =~ /^\s*(\w+)\s*=\s*(.*)/) { #Parameter 
     say "Parameter is '$1' with a value of '$2'"; 
    } 
    else {  #Not Comment, Section, or Parameter 
     say "Invalid line"; 
    } 

} 

Vấn đề là tôi đã bị hỏng bởi Perl, vì vậy tôi nghĩ rằng cách dễ nhất để làm điều gì đó là sử dụng một biểu thức chính quy. Dưới đây là đoạn code tôi có cho đến nay ...

for line in file_handle: 
    line = line.strip 

    # Comment lines and blank lines 
    if line.find("#") == 1 \ 
      or line.find(";") == 1 \ 
      or line.whitespace: 
     continue 

    # Found a Section Heading 
    if line.find("[") == 1: 
     print "I want to use a regular expression here" 
     print "to split the section up into two pieces" 
    elif line.find("=") != -1: 
     print "I want to use a regular expression here" 
     print "to split the parameter into key and value" 
    else 
     print "Invalid Line" 

Có một số điều mà kích thích tôi ở đây:

  • Có hai nơi mà một biểu thức chính quy chỉ dường như được gọi ra để được sử dụng. Cách Python làm việc tách này là gì?
  • Tôi đảm bảo tách khoảng trắng ở hai bên của chuỗi và viết lại chuỗi. Bằng cách đó, tôi không phải làm nhiều lần. Tuy nhiên, tôi đang viết lại chuỗi mà tôi hiểu là một hoạt động rất kém hiệu quả trong Python. Cách Python để xử lý vấn đề này là gì?
  • Cuối cùng, thuật toán của tôi trông khá giống thuật toán Perl của tôi, và dường như nói rằng tôi đang để tư duy Perl của tôi cản trở. Mã của tôi nên được cấu trúc bằng Python như thế nào?

Tôi đã trải qua nhiều hướng dẫn khác nhau và họ đã giúp tôi hiểu cú pháp, nhưng không có nhiều cách xử lý ngôn ngữ - đặc biệt là người có xu hướng nghĩ khác ngôn ngữ.

Câu hỏi của tôi:

  • Tôi có nên sử dụng biểu thức thông thường? Hoặc, có cách nào khác và tốt hơn để xử lý việc này không?
  • Logic mã hóa của tôi có đúng không? Làm thế nào tôi nên suy nghĩ về phân tích cú pháp tập tin này?
+3

Hãy chắc chắn xem qua mô-đun ['ConfigParser'] (http://docs.python.org/library/configparser.html). –

+0

Trong Perl bạn không 'tham nhũng', bạn' ban phước ' – joaquin

+0

@SvenMarnach - Cảm ơn bạn đã gợi ý, nhưng tôi đã thấy điều đó. Vấn đề là ConfigParser đặt đầu ra vào một từ điển, và tôi không thể đảm bảo thứ tự của các phần trong một từ điển thực sự quan trọng trong ứng dụng cụ thể này. Tôi đã có cùng một vấn đề với Perl với mô-đun [Config :: Ini] (http://search.cpan.org/~rjbs/Config-INI-0.019/lib/Config/INI.pm). Bên cạnh đó, điều này mang lại cho tôi một cơ hội để thực sự tìm hiểu các ins and outs của Python. –

Trả lời

4

Mặc dù tôi không nghĩ đây là ý định của bạn, định dạng tệp xuất hiện khá giống với mô-đun ConfigParser tích hợp sẵn của Python. Đôi khi cách "Pythonic" nhất đã được cung cấp cho bạn. (:

Trong câu trả lời trực tiếp hơn cho câu hỏi của bạn:. Biểu thức thông thường có thể là một cách tốt để làm điều này Nếu không, bạn có thể thử cơ bản hơn (và ít mạnh mẽ)

(parameter, value) = line.split('=') 

này sẽ ném ra một lỗi . nếu dòng không chứa hoặc nhiều hơn một '=' nhân vật Bạn có thể muốn kiểm tra nó trước với '=' in line

Ngoài ra:.

line.find("[") == 1 

có lẽ là tốt hơn thay thế bằng

line.startswith("[") 

Hy vọng rằng helpls một chút (:

+0

Cảm ơn, tôi đã thấy mô-đun đó, nhưng thật không may, nó lưu trữ kết quả trong từ điển và bạn có thể mất thứ tự các phần được đọc. Cho tôi, thứ tự của các phần là rất quan trọng. Tôi đã có cùng một vấn đề trong Perl với mô-đun [Config :: Ini] (http://search.cpan.org/~rjbs/Config-INI-0.019/lib/Config/INI.pm). Ngoài ra, ý tưởng là học ngôn ngữ. Cảm ơn con trỏ tới phương thức 'startswith'. –

+0

@David Bạn được chào đón. Tôi nghĩ cách dựng sẵn sẽ không hoàn toàn giống nhau, bằng cách nào đó ... :) – tjvr

+0

Để tránh nhiều hơn 1 '=' dấu hiệu, hãy sử dụng 'line.split ('=', 1)' Để giải quyết vấn đề với không có dấu hiệu '=', sử dụng 'tham số, giá trị = (line.split ('=', 1) + ['']) [: 2]'. Không đặt() xung quanh LHS tuple, họ là lộn xộn không cần thiết. Ngoài ra hãy chắc chắn để gọi 'line.strip' bằng cách sử dụng' line.strip() '- mã bạn có sẽ thay thế dòng với dải phương pháp ràng buộc, một cái gì đó tôi chắc chắn là không mong muốn. – PaulMcG

5

Python bao gồm ini parsing library. Nếu bạn muốn xây dựng một thư viện để phân tích cú pháp các tệp ini, thì bạn đang xem một trình phân tích cú pháp thực tế thực tế là. Regex sẽ không cắt nó, sử dụng PLY hoặc móc trong bộ phân tích cú pháp C/flex.Additional python parsing resources are available as well.

Máy quét xử lý tất cả văn bản tiêu thụ và xây dựng cây cho bạn, vì đó là nhiệm vụ cơ học dễ bị lỗi lập trình viên. I E. phần này:

while (my $line = <CONTROL_FILE>) { 
    chomp($line); 
    next if ($line =~ /^\s*[#;']/);  #Comments start with "#", ";", or "'" 
    next if ($line =~ /^\s*$/);   #Ignore blank lines 

    if ($line =~ /^\s*\[\s*(\w+)\s+(.*)/) { #Section 
     say "This is a '$1' section called '$2'"; 
    } 
    elsif ($line =~ /^\s*(\w+)\s*=\s*(.*)/) { #Parameter 
     say "Parameter is '$1' with a value of '$2'"; 
    } 
    else {  #Not Comment, Section, or Parameter 
     say "Invalid line"; 
    } 

} 

Được tạo bởi lexer, bạn chỉ cần xác định Regex chính xác. Trình phân tích cú pháp kéo các thẻ từ lexer và xác định xem chúng có phù hợp với các mẫu mã thông báo cho phép hay không. Đó là:

[<type> <description>] 
<parameter> = <value> 

Xác định các mã thông báo đó, sau đó cách cho phép khớp. Mọi thứ khác chỉ đặt chính nó lại với nhau. Đối với những người bạn nghĩ rằng bạn có thể làm tốt hơn với vòng lặp nhanh và một số regex, tôi khuyên bạn nên đọc Lex & Yacc, 2nd Ed.

Ví dụ về trình phân tích cú pháp tôi đã viết với PLY, go here. Nó phân tích cú pháp tệp "jetLetter", chỉ là một phương ngữ của groff/troff.

+0

+1 để hiển thị cách thức 'Python' thực hiện rất nhiều thứ thường là biết việc xây dựng mạnh mẽ trong các thư viện. –

+0

Chỉ muốn ném vào một liên kết đến [lepl] (http://www.acooke.org/lepl/), một thư viện phân tích cú pháp nhẹ, đẹp mà tôi vừa mới tìm hiểu về trên trang này. –

0

Vâng, bằng mọi cách sử dụng biểu thức thông thường trong trường hợp này. Cú pháp của các dòng tệp .INI mà bạn đang cố gắng phân tích phù hợp với toán học trong các đặc tính của ngữ pháp Chomsky Loại 3 (thông thường), đó chính là loại biểu thức thông thường được thiết kế để phân tích cú pháp.

Các biểu thức thông thường bạn cần là (ra khỏi đỉnh đầu của tôi, chưa được kiểm tra) một cái gì đó như:

r"^\[\s*(\w)\s+(.*)\]$" 

r"^(\w)\s*\=\s*(.*)$" 

Sử dụng re.search, và trong trở Match objects, bạn có thể trích xuất các nhóm tương ứng với các nhóm được lồng trong các biểu thức.