2016-04-25 37 views
8

Tôi đã triển khai thực hiện truyền tải đồ thị dưới dạng một hàm tạo ra cho nút được truy cập.Sử dụng trình tạo send() trong vòng lặp for

Đôi khi người dùng cần báo cho hàm truyền tải rằng các cạnh ra khỏi một nút cụ thể không được theo sau; để hỗ trợ điều đó, traversal kiểm tra giá trị được gửi lại cho nó (sử dụng phương thức máy phát điện send()) và nếu đó là True, coi nút đó là một chiếc lá cho mục đích truyền tải.

Vấn đề là vòng lặp sử dụng đơn giản nhất là kinda dài:

# simplified thanks to @tobias_k 
# bfs is the traversal generator function 
traversal = bfs(g, start_node) 
try: 
    n = next(traversal) 
    while True: 
    # process(n) returns True if don't want to follow edges out of n 
    n = traversal.send(process(n)) 
except StopIteration: 
    pass 

Có cách nào để cải thiện điều này?

Tôi nghĩ một cái gì đó như thế này nên làm việc:

for n in bfs(g, start_node): 
    ???.send(process(n)) 

nhưng tôi cảm thấy tôi là thiếu kiến ​​thức về một số cú pháp python.

+2

Vâng, bạn có thể làm cho nó ngắn hơn nhiều bằng cách đặt thử/ngoại trừ bên ngoài vòng lặp; điều này sẽ giúp bạn tiết kiệm một bộ thử/trừ và điều kiện if. –

+0

@tobias_k đã được khắc phục, cảm ơn. – max

Trả lời

3

Tôi không thấy cách thực hiện việc này trong vòng lặp for thông thường. Tuy nhiên, bạn có thể tạo một trình tạo khác, lặp lại một trình tạo khác, sử dụng một số "hàm theo dõi" để xác định xem có làm theo phần tử hiện tại hay không, do đó đóng gói các phần phức tạp của mã của bạn thành một hàm riêng biệt.

def checking_generator(generator, follow_function): 
    try: 
     x = next(generator) 
     while True: 
     yield x 
     x = generator.send(follow_function(x)) 
    except StopIteration: 
     pass 

for n in checking_generator(bfs(g, start_node), process): 
    print(n) 
+0

Công trình này hoạt động! Tôi đoán nhược điểm duy nhất, ngoài việc phải tạo ra một chức năng tiện ích bổ sung, là giả thuyết '.. Send()' có thể được sử dụng ở nhiều nơi trong vòng lặp, buộc vòng lặp tiếp tục. Với cách tiếp cận này, giá trị chỉ có thể được gửi ở cuối của vòng lặp. Con trăn quá xấu thiếu cú ​​pháp để hỗ trợ trường hợp sử dụng cơ bản như vậy. – max

+0

@max bạn có thể gửi thêm giá trị vào máy phát bằng cách giữ tham chiếu đến giá trị ban đầu: 'traversal = bfs (g, start_node); cho n trong checks_generator (traversal, process): ... traversal.send (...) 'mặc dù trong trường hợp này' checking_generator' sẽ vẫn xử lý dựa trên nút cuối cùng mà nó đã xử lý. –

+1

@tobias_k khi một hàm tạo ra kết thúc, nó làm tăng một StopIteration. Vì vậy, để quấn toàn bộ nội dung của mã trong một 'try' để chặn một' StopIteration' và thoát khỏi hàm ...mà sau đó tăng một 'StopIteration' có vẻ hơi ngớ ngẩn một chút. :) –

1

Tôi không coi đây là một trường hợp sử dụng thường xuyên, xem xét việc này như các máy phát điện ban đầu:

def original_gen(): 
    for x in range(10): 
     should_break = yield x 
     if should_break: 
      break 

Nếu giá trị của should_break luôn tính toán dựa trên một số gọi hàm với x thì tại sao không chỉ viết máy phát như thế này:

def processing_gen(check_f): 
    for x in range(10): 
     yield x 
     should_break = check_f(x) 
     if should_break: 
      break 

Tuy nhiên, tôi thường nghĩ về mã xử lý các giá trị được tạo ra như được viết bên trong vòng lặp (nếu không điểm của việc có một vòng ở tất cả là bao nhiêu?)

gì nó thực sự có vẻ như bạn muốn làm là tạo ra một máy phát điện ở đâu gọi phương thức __next__ thực sự ngụ ý send(process(LAST_VALUE)) mà có thể được thực hiện với một lớp:

class Followup_generator(): #feel free to use a better name 
    def __init__(self,generator,following_function): 
     self.gen = generator 
     self.process_f = following_function 
    def __iter__(self): 
     return self 
    def __next__(self): 
     if hasattr(self,"last_value"): 
      return self.send(self.process_f(self.last_value)) 
     else: 
      self.last_value = next(self.gen) 
      return self.last_value 
    def send(self,arg): 
     self.last_value = self.gen.send(arg) 
     return self.last_value 
    def __getattr__(self,attr): 
     "forward other lookups to the generator (.throw etc.)" 
     return getattr(self.gen, attr) 

# call signature is the exact same as @tobias_k's checking_generator 
traversal = Followup_generator(bfs(g, start_node), process) 
for n in traversal: 
    print(n) 
    n = traversal.send(DATA) #you'd be able to send extra values to it 

tuy nhiên điều này vẫn không coi đây là thường xuyên sử dụng, tôi muốn được hoàn toàn tốt đẹp với một vòng lặp while, mặc dù tôi muốn đặt .send gọi ở đầu trang:

traversal = bfs(g, start_node) 
send_value = None 
while True: 
    n = traversal.send(send_value) 
    #code for loop, ending in calculating the next send_value 
    send_value = process(n) 

Và bạn có thể quấn rằng trong một try: ... except StopIteration:pass mặc dù tôi thấy rằng chỉ đơn giản là chờ đợi một lỗi để nâng cao được thể hiện tốt hơn với một người quản lý bối cảnh:

class Catch: 
    def __init__(self,exc_type): 
     if issubclass(exc_type,BaseException): 
      self.catch_type = exc_type 
     else: 
      raise TypeError("can only catch Exceptions") 
    def __enter__(self): 
     return self 
    def __exit__(self,exc_type,err, tb): 
     if issubclass(exc_type, self.catch_type): 
      self.err = err 
      return True 


with Catch(StopIteration): 
    traversal = bfs(g, start_node) 
    send_value = None 
    while True: 
     n = traversal.send(send_value) 
     #code for loop, ending in calculating the next send_value 
     send_value = process(n) 
2

Để đơn giản hóa mã khách hàng, bạn có thể sử dụng một bsf() máy phát điện thông thường và kiểm tra node.isleaf thuộc tính trong nó:

for node in bfs(g, start_node): 
    node.isleaf = process(node) # don't follow if `process()` returns True 

Điểm bất lợi là node là có thể thay đổi. Hoặc bạn phải chuyển cấu trúc dữ liệu được chia sẻ theo dõi các nút lá: leaf[node] = process(node) trong đó leaf từ điển được chuyển vào bfs() trước đó.

Nếu bạn muốn sử dụng phương thức .send() một cách rõ ràng; bạn phải xử lý StopIteration. Xem PEP 479 -- Change StopIteration handling inside generators. Bạn có thể giấu nó trong một hàm helper:

def traverse(tree_generator, visitor): 
    try: 
     node = next(tree_generator) 
     while True: 
      node = tree_generator.send(visitor(node)) 
    except StopIteration: 
     pass 

Ví dụ:

traverse(bfs(g, start_node), process) 
0

tôi phát hiện ra rằng câu hỏi của tôi sẽ có một câu trả lời một dòng, sử dụng kéo dài "tiếp tục" tuyên bố đề xuất trong earlier version of PEP 342 :

for n in bfs(g, start_node): 
    continue process(n) 

Tuy nhiên, trong khi PEP 342 đã được chấp nhận, đó là tính năng đặc biệt được rút sau this June 2005 discussion giữa Raymond và Guido:

Raymond hettinger nói:

Hãy để tôi đi vào kỷ lục như một -1 mạnh mẽ cho "tiếp tục EXPR". Vòng lặp là cấu trúc cơ bản nhất của chúng tôi và có thể dễ dàng được hiểu trong biểu mẫu hiện tại . Điều tương tự có thể được nói cho "tiếp tục" và "phá vỡ" mà có thêm lợi thế của một đường cong học tập gần bằng không cho người dân di chuyển từ các ngôn ngữ khác.

Bất kỳ yêu cầu nào làm phức tạp những tuyên bố cơ bản này phải nghiêm túc được xem xét kỹ lưỡng và được tổ chức theo tiêu chuẩn cao về sự rõ ràng, giải thích, tính rõ ràng, hữu ích và cần thiết. IMO, nó không vượt qua hầu hết các thử nghiệm này.

Tôi sẽ không mong muốn giải thích "tiếp tục EXPR" trong hướng dẫn và nghĩ rằng nó sẽ nổi bật như một tính năng chống.

[...] Đối số chính xác chống lại "tiếp tục EXPR" là có chưa có trường hợp sử dụng nào; nếu có trường hợp sử dụng tốt, giải thích sẽ dễ dàng theo dõi.

Guido

Nếu nhà phát triển python lõi đã từ thay đổi suy nghĩ của họ về tính hữu ích của mở rộng "tiếp tục", có lẽ điều này có thể được giới thiệu lại vào một PEP trong tương lai. Tuy nhiên, với một trường hợp sử dụng gần như giống hệt như trong câu hỏi này đã được thảo luận trong chủ đề được trích dẫn, và không được tìm thấy thuyết phục, có vẻ như không.

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