2017-07-06 52 views
23

Tôi hiện đang viết một dự án yêu cầu mã của bên thứ ba sử dụng một phương thức trả về một trình lặp của chính nó, ví dụ về cách mã này trông giống như sau:Gọi đệ quy một phương thức đối tượng trả về một biến lặp của chính nó

def generate(): 
    for x in obj.children(): 
     for y in x.children(): 
      for z in y.children(): 
       yield z.thing 

Hiện tại điều này chỉ đơn giản là cắt mã của tôi và trở nên khó đọc sau 3 cấp. Lý tưởng nhất tôi muốn làm điều gì đó như thế này:

x = recursive(obj, method="children", repeat=3).thing 

Có cách nào được xây dựng để làm điều này bằng Python không?

Trả lời

24

Bắt đầu từ python3.3, bạn có thể sử dụng cú pháp yield from để mang lại toàn bộ biểu thức trình tạo.

Vì vậy, bạn có thể sửa đổi chức năng của bạn một chút, để có một vài thông số:

def generate(obj, n): 
    if n == 1: 
     for x in obj.children(): 
      yield x.thing 
    else: 
     for x in obj.children(): 
      yield from generate(x, n - 1) 

Khái niệm yield from sẽ mang lại sự biểu hiện máy phát điện toàn bộ của cuộc gọi đệ quy.

Gọi chức năng của bạn như thế này:

x = generate(obj, 3) 

Lưu ý rằng điều này trả về cho bạn một máy phát điện của x.things.


Căn cứ vào yêu cầu cụ thể của bạn, đây là phiên bản chung hơn sử dụng getattr hoạt động với các thuộc tính tùy ý.

def generate(obj, iterable_attr, attr_to_yield, n): 
    if n == 1: 
     for x in getattr(obj, iterable_attr): 
      yield getattr(x, attr_to_yield) 
    else: 
     for x in getattr(obj, iterable_attr): 
      yield from generate(x, iterable_attr, attr_to_yield, n - 1) 

Và bây giờ, hãy gọi chức năng của bạn như:

x = generate(obj, 'children', 'thing', 3) 
+0

Tuy nhiên, giải pháp chung có thể hoạt động trong trường hợp này nhưng chưa có cách nào để thực hiện việc này? – Paradoxis

+0

Tôi có ý nghĩa nhiều hơn khi gọi 'trẻ em' và' x.thing' động – Paradoxis

+0

@Paradoxis Bạn chắc chắn có thể. Giả sử 'children' trả về một iterable. Hãy để tôi sửa đổi câu trả lời của tôi. –

5

Ví dụ yield from trên là tốt, nhưng tôi nghiêm túc nghi ngờ param mức/sâu là cần thiết. A/giải pháp đơn giản hơn generic mà làm việc cho bất kỳ cây:

class Node(object): 
    def __init__(self, thing, children=None): 
    self.thing = thing 
    self._children = children 
    def children(self): 
    return self._children if self._children else [] 

def generate(node): 
    if node.thing: 
    yield node.thing 
    for child in node.children(): 
    yield from generate(child) 

node = Node('mr.', [Node('derek', [Node('curtis')]), Node('anderson')]) 
print(list(generate(node))) 

Returns:

$ python3 test.py 
['mr.', 'derek', 'curtis', 'anderson'] 

Chú giải này sẽ trở lại nút hiện tại của thing trước khi bất kỳ dành cho trẻ em của nó. (IE nó thể hiện chính nó trên con đường xuống đi bộ.) Nếu bạn muốn nó thể hiện chính nó trên con đường trở lại đi bộ, trao đổi các câu hỏi iffor. (DFS vs BFS) Nhưng có khả năng không quan trọng trong trường hợp của bạn (nơi tôi nghi ngờ một nút có hoặc là thing hoặc trẻ em, không bao giờ cả hai).

5

Nếu sử dụng Python 2.7 bạn cần để giữ chồng của riêng bạn iterables và thực hiện vòng lặp:

from operator import methodcaller 

def recursive(obj, iterater, yielder, depth): 
    iterate = methodcaller(iterater) 
    xs = [iterate(obj)] 
    while xs: 
     try: 
      x = xs[-1].next() 
      if len(xs) != depth: 
       xs.append(iterate(x)) 
      else: 
       yield getattr(x, yielder) 
     except StopIteration: 
      xs.pop() 

Đây là một trường hợp đặc biệt của một ichain đệ quy tổng quát hơn từ chức năng iterable:

def recursive_ichain(iterable_tree): 
    xs = [iter(iterable_tree)] 
    while [xs]: 
     try: 
      x = xs[-1].next() 
      if isinstance(x, collections.Iterable): 
       xs.append(iter(x)) 
      else: 
       yield x 
     except StopIteration: 
      xs.pop() 

Và một số đối tượng kiểm tra:

class Thing(object): 
    def __init__(self, thing): 
     self.thing = thing 

class Parent(object): 
    def __init__(self, *kids): 
     self.kids = kids 

    def children(self): 
     return iter(self.kids) 

test_obj = Parent(
    Parent(
     Parent(Thing('one'), Thing('two'), Thing('three')), 
     Parent(Thing('four')), 
     Parent(Thing('five'), Thing('six')), 
    ), 
    Parent(
     Parent(Thing('seven'), Thing('eight')), 
     Parent(), 
     Parent(Thing('nine'), Thing('ten')), 
    ) 
) 

Và thử nghiệm nó:

>>>for t in recursive(test_obj, 'children', 'thing', 3): 
>>> print t 
one 
two 
three 
four 
five 
six 
seven 
eight 
nine 
ten 

Cá nhân tôi muốn có khuynh hướng thay đổi số yield getattr(x, yielder) thành yield x để tự truy cập vào các đối tượng lá và truy cập một cách rõ ràng. tức là

for leaf in recursive(test_obj, 'children', 3): 
    print leaf.thing 
Các vấn đề liên quan