2008-10-26 62 views
34

Tôi muốn thực hiện một số mẫu khớp trên danh sách bằng Python. Ví dụ, trong Haskell, tôi có thể làm điều gì đó như sau:So khớp mẫu danh sách bằng Python

fun (head : rest) = ... 

Vì vậy, khi tôi vượt qua trong một danh sách, head sẽ là yếu tố đầu tiên, và rest sẽ là yếu tố dấu.

Tương tự như vậy, trong Python, tôi có thể tự động giải nén các tuple:

(var1, var2) = func_that_returns_a_tuple() 

tôi muốn làm một cái gì đó tương tự với danh sách trong Python. Ngay bây giờ, tôi có một hàm trả về một danh sách, và một đoạn mã mà sau:

ls = my_func() 
(head, rest) = (ls[0], ls[1:]) 

Tôi tự hỏi nếu tôi bằng cách nào đó có thể làm điều đó trong một dòng trong Python, thay vì hai.

Trả lời

55

Cho đến nay như tôi biết không có cách nào để làm cho nó một lớp lót bằng Python hiện tại mà không giới thiệu chức năng khác, ví dụ:

split_list = lambda lst: (lst[0], lst[1:]) 
head, rest = split_list(my_func()) 

Tuy nhiên, trong Python 3.0 cú pháp chuyên dùng cho chữ ký lập luận variadic và giải nén đối số cũng sẽ có sẵn cho loại giải nén chuỗi chung này, vì vậy trong 3.0 bạn sẽ có thể viết:

head, *rest = my_func() 

Xem PEP 3132 để biết chi tiết.

+0

Tất nhiên bạn có thể đặt lambda rằng trên cùng một dòng với mọi thứ khác: đầu, phần còn lại = (lambda lst: (lst [0], lst [1:])) (my_func()) –

+11

Có, nhưng điều đó bắt đầu khơi dậy sự xáo trộn. –

+1

+1 cho tính năng mới Python 3 và liên kết với PEP. – fossilet

4

Đó là một cách tiếp cận 'thuần túy chức năng' rất nhiều và như vậy là một thành ngữ hợp lý trong Haskell nhưng có lẽ không phù hợp với Python. Python chỉ có một khái niệm rất hạn chế của patterns theo cách này - và tôi nghi ngờ bạn có thể cần một hệ thống kiểu cứng nhắc hơn để thực hiện loại cấu trúc đó (erlang buff được mời không đồng ý ở đây).

Những gì bạn có là gần như bạn sẽ nhận được thành ngữ đó, nhưng bạn có lẽ tốt hơn bằng cách sử dụng một cách tiếp cận danh sách hoặc bắt buộc thay vì đệ quy gọi một hàm với đuôi của danh sách.

Như đã từng là statedon a few occasionsbefore, Python không thực sự là ngôn ngữ chức năng. Nó chỉ vay mượn ý tưởng từ thế giới FP. Nó không vốn là Tail Recursive theo cách bạn mong đợi để thấy được nhúng trong kiến ​​trúc của một ngôn ngữ chức năng, do đó bạn sẽ gặp khó khăn khi thực hiện thao tác đệ quy này trên một tập dữ liệu lớn mà không cần sử dụng nhiều không gian ngăn xếp.

2

Vâng, tại sao bạn muốn nó trong 1 dòng ở nơi đầu tiên?

Nếu bạn thực sự muốn, bạn luôn có thể làm một thủ thuật như thế này:

def x(func): 
    y = func() 
    return y[0], y[1:] 

# then, instead of calling my_func() call x(my_func) 
(head, rest) = x(my_func) # that's one line :) 
+2

Chủ yếu là vì - giả sử có cú pháp "đẹp" - dễ đọc hơn. – mipadi

+0

Cũng vì 'đầu' và 'đuôi' là các thành ngữ tiêu chuẩn trong danh sách. Chúng được sử dụng trong tất cả các cấu trúc dữ liệu và các cuốn sách thuật toán vì một lý do. Python không có hàm 'head (my_list)' được xây dựng sẵn là vấn đề thực sự. –

32

Trước hết, xin lưu ý rằng "mô hình phù hợp với" các ngôn ngữ chức năng và việc gán cho các bộ dữ liệu bạn đề cập không thực sự giống nhau. Trong các ngôn ngữ chức năng, các mẫu được sử dụng để đưa ra các định nghĩa một phần của một hàm.Vì vậy, f (x : s) = e không có nghĩa là mất đầu và đuôi của đối số của f và trở e sử dụng chúng, nhưng nó có nghĩa là nếu lập luận của f có dạng x : s (đối với một số xs), sau đóf (x : s) là bằng e.

Việc gán python giống như một phép gán nhiều hơn (tôi nghi ngờ đó là ý định ban đầu của nó). Vì vậy, bạn viết, ví dụ, x, y = y, x để hoán đổi các giá trị trong xy mà không cần một biến tạm thời (như bạn làm với một câu lệnh gán đơn giản). Điều này có rất ít để làm với phù hợp với mô hình vì nó là cơ bản một cách viết tắt cho việc thực hiện "đồng thời" x = yy = x. Mặc dù python cho phép các chuỗi tùy ý thay vì các danh sách được phân tách bằng dấu phẩy, tôi sẽ không đề nghị gọi mẫu này khớp với nhau. Với mẫu phù hợp, bạn kiểm tra xem có phù hợp với một mẫu hay không; trong việc phân công python bạn nên đảm bảo rằng các chuỗi trên cả hai mặt đều giống nhau.

Để làm những gì bạn muốn, thường là các chức năng phụ (như được người khác đề cập) hoặc một cái gì đó tương tự như các cấu trúc let hoặc where (mà bạn có thể coi là sử dụng các chức năng ẩn danh). Ví dụ:

(head, tail) = (x[0], x[1:]) where x = my_func() 

Hoặc, trong python thực tế:

(head, tail) = (lambda x: (x[0], x[1:]))(my_func()) 

Lưu ý rằng điều này về cơ bản cũng giống như các giải pháp được đưa ra bởi những người khác với một chức năng phụ trợ ngoại trừ rằng đây là một trong những-liner bạn muốn . Đó là, tuy nhiên, không nhất thiết phải tốt hơn so với một chức năng riêng biệt.

(Xin lỗi nếu câu trả lời của tôi là một chút trên đầu. Tôi chỉ nghĩ rằng điều quan trọng là phải đảm sự phân biệt rõ ràng.)

1

có một reciepe trong sách dạy nấu ăn trăn để làm điều này. i cant dường như để tìm thấy nó bây giờ nhưng đây là mã (i sửa đổi nó một chút)


def peel(iterable,result=tuple): 
    '''Removes the requested items from the iterable and stores the remaining in a tuple 
    >>> x,y,z=peel('test') 
    >>> print repr(x),repr(y),z 
    't' 'e' ('s', 't') 
    ''' 
    def how_many_unpacked(): 
     import inspect,opcode 
     f = inspect.currentframe().f_back.f_back 
     if ord(f.f_code.co_code[f.f_lasti])==opcode.opmap['UNPACK_SEQUENCE']: 
      return ord(f.f_code.co_code[f.f_lasti+1]) 
     raise ValueError("Must be a generator on RHS of a multiple assignment!!") 
    iterator=iter(iterable) 
    hasItems=True 
    amountToUnpack=how_many_unpacked()-1 
    next=None 
    for num in xrange(amountToUnpack): 
     if hasItems:   
      try: 
       next = iterator.next() 
      except StopIteration: 
       next = None 
       hasItems = False 
     yield next 
    if hasItems: 
     yield result(iterator) 
    else: 
     yield None 

tuy nhiên bạn nên lưu ý rằng chỉ có tác dụng khi sử dụng giải nén phân công vì cách nó inespects khung trước ... vẫn khá hữu ích.

2

Ngoài các câu trả lời khác, lưu ý rằng hoạt động đầu/đuôi tương đương trong Python, bao gồm phần mở rộng của cú pháp * python3 thường kém hiệu quả hơn so khớp mẫu của Haskell.

Danh sách Python được triển khai dưới dạng vectơ, do đó, lấy đuôi sẽ cần lấy một bản sao của danh sách. Đây là O (n) wrt kích thước của danh sách, trong khi một implementaion sử dụng danh sách liên kết như Haskell chỉ có thể sử dụng con trỏ đuôi, một O (1) hoạt động.

Ngoại lệ duy nhất có thể là phương pháp tiếp cận dựa trên vòng lặp, trong đó danh sách không thực sự được trả về, nhưng trình lặp lại là. Tuy nhiên, điều này có thể không áp dụng được cho tất cả những nơi có danh sách mong muốn (ví dụ: lặp nhiều lần).

Ví dụ: cách tiếp cận Cipher's, nếu được sửa đổi để trả về trình lặp thay vì chuyển đổi nó thành bộ tuple sẽ có hành vi này.Ngoài ra một đơn giản 2-item phương pháp duy nhất không dựa vào bytecode sẽ là:

def head_tail(lst): 
    it = iter(list) 
    yield it.next() 
    yield it 

>>> a, tail = head_tail([1,2,3,4,5]) 
>>> b, tail = head_tail(tail) 
>>> a,b,tail 
(1, 2, <listiterator object at 0x2b1c810>) 
>>> list(tail) 
[3, 4] 

Rõ ràng mặc dù bạn vẫn phải quấn trong một chức năng tiện ích chứ không phải là có được thoải mái đường cú pháp cho nó.

2

Không giống như Haskell hoặc ML, Python không có kết hợp mẫu phù hợp với cấu trúc. Cách Pythonic nhất để thực hiện khớp mẫu là với khối thử ngoại trừ:

def recursive_sum(x): 
    try: 
     head, tail = x[0], x[1:] 
     return head + recursive-sum(tail) 
    except IndexError: # empty list: [][0] raises IndexError 
     return 0 

Lưu ý rằng điều này chỉ hoạt động với các đối tượng có chỉ mục cắt lát. Ngoài ra, nếu chức năng trở nên phức tạp, một cái gì đó trong cơ thể sau đường dây head, tail có thể làm tăng chỉ số IndexError, điều này sẽ dẫn đến các lỗi nhỏ. Tuy nhiên, điều này không cho phép bạn làm những việc như:

Trong Python, đuôi đệ quy thường được thực hiện tốt hơn như một vòng lặp với một accumulator, ví dụ:

def iterative_sum(x): 
    ret_val = 0 
    for i in x: 
     ret_val += i 
    return ret_val 

Đây là một rõ ràng, đúng cách để làm điều đó 99% thời gian. Không chỉ rõ ràng hơn để đọc, nó nhanh hơn và nó sẽ làm việc trên những thứ khác ngoài danh sách (bộ, ví dụ). Nếu có một ngoại lệ chờ đợi xảy ra trong đó, chức năng sẽ vui vẻ thất bại và cung cấp nó lên chuỗi.

2

Tôi đang làm việc trên pyfpm, thư viện để khớp mẫu trong Python bằng cú pháp giống Scala. Bạn có thể sử dụng nó để giải nén đối tượng như thế này:

from pyfpm import Unpacker 

unpacker = Unpacker() 

unpacker('head :: tail') << (1, 2, 3) 

unpacker.head # 1 
unpacker.tail # (2, 3) 

Hoặc trong arglist một chức năng:

from pyfpm import match_args 

@match_args('head :: tail') 
def f(head, tail): 
    return (head, tail) 

f(1)   # (1,()) 
f(1, 2, 3, 4) # (1, (2, 3, 4)) 
+0

rất, rất unpythonic :) –

Các vấn đề liên quan