2010-11-05 31 views
43

Tại thời điểm này, tôi đang làm những thứ như sau, đó là nhận được tẻ nhạt:cách hiệu quả của việc có một chức năng chỉ thực hiện một lần trong một vòng lặp

run_once = 0 
while 1: 
    if run_once == 0: 
     myFunction() 
     run_once = 1: 

Tôi đoán có một số cách được chấp nhận hơn xử lý công cụ này?

Điều tôi đang tìm kiếm là có chức năng thực thi một lần, theo yêu cầu. Ví dụ: khi nhấn một nút nhất định. Nó là một ứng dụng tương tác có rất nhiều công tắc điều khiển người dùng. Có một biến rác cho mỗi chuyển đổi, chỉ để theo dõi xem liệu nó có được chạy hay không, có vẻ như không hiệu quả.

+8

Nếu bạn phải chạy một lần, tại sao bạn không chỉ gọi hàm -> myFunction() một lần !!. Vui lòng xem mã của bạn, giải thích mục đích tốt hơn.! – pyfunc

+4

Tại sao bạn không thể rời khỏi vòng lặp đó? Thêm bối cảnh xin vui lòng. – EightyEight

+0

@pyfunc Hãy cho tôi biết nếu mã chỉnh sửa rõ ràng hơn chuyển tải vấn đề – Ron

Trả lời

76

Tôi sẽ sử dụng một trang trí trên các chức năng để xử lý theo dõi bao nhiêu lần nó chạy.

def run_once(f): 
    def wrapper(*args, **kwargs): 
     if not wrapper.has_run: 
      wrapper.has_run = True 
      return f(*args, **kwargs) 
    wrapper.has_run = False 
    return wrapper 


@run_once 
def my_function(foo, bar): 
    return foo+bar 

Bây giờ my_function sẽ chỉ chạy một lần. Các cuộc gọi khác đến số này sẽ trả về None. Chỉ cần thêm một mệnh đề else vào if nếu bạn muốn nó trả về một thứ khác. Từ ví dụ của bạn, nó không cần phải trả lại bất cứ điều gì.

Nếu bạn không kiểm soát việc tạo hàm hoặc chức năng cần được sử dụng bình thường trong các ngữ cảnh khác, bạn cũng có thể áp dụng thủ công trang trí theo cách thủ công.

action = run_once(my_function) 
while 1: 
    if predicate: 
     action() 

Điều này sẽ để lại my_function cho các mục đích sử dụng khác.

Cuối cùng, nếu bạn cần phải chỉ chạy nó một lần hai lần, sau đó bạn có thể chỉ cần làm

action = run_once(my_function) 
action() # run once the first time 

action.has_run = False 
action() # run once the second time 
+0

Điều này có vẻ như những gì tôi có thể đang tìm kiếm, nhưng tôi không chắc chắn về cú pháp của những gì bạn đang sử dụng. Char là gì? –

+4

@Marcus Ottosson The '@' char làm cho nó một trang trí. Nhận xét quá dài để giải thích đó là gì. Ở đây, có [link] (http://www.artima.com/weblogs/viewpost.jsp?thread=240808). Ở mức nào, bạn có thể chỉ muốn sử dụng biểu mẫu thứ hai mà tôi đã trình bày thay vào đó bạn áp dụng nó theo cách thủ công. Có lẽ bạn vẫn nên biết người trang trí là gì. – aaronasterling

+0

Tuyệt, tôi sẽ xem xét điều đó. Cảm ơn –

6

Chạy chức năng trước vòng lặp. Ví dụ:

myFunction() 
while True: 
    # all the other code being executed in your loop 

Đây là giải pháp hiển nhiên. Nếu có nhiều hơn đáp ứng mắt, giải pháp có thể phức tạp hơn một chút.

+2

Điều này sẽ không cắt là vì OP nói "Ví dụ, khi nhấn một nút nhất định. Đây là một ứng dụng tương tác có rất nhiều công tắc điều khiển người dùng." Vì vậy, OP muốn chức năng được chạy một lần từ bên trong vòng lặp chạy chính. –

0

Tại sao điều này khác với mã của bạn?

myFunction() 
while 1: 
    # rest of your code 
    pass 
+0

Tại sao 'run_once' thậm chí cần phải có thêm nữa? –

+0

@Rafe Kettler: Trên thực tế, tôi không bao giờ có ý định trả lời nhưng bạn không thể hiển thị mã trong phần bình luận. Tôi đã bình luận nhưng tôi nghĩ tôi cũng có thể hỏi anh ấy. Tôi không chắc đây có phải là một câu hỏi hay không. – pyfunc

+1

chúng tôi có thể không bao giờ biết. –

-2

Tôi không chắc chắn rằng tôi đã hiểu vấn đề của bạn, nhưng tôi nghĩ bạn có thể chia vòng lặp. Trên một phần của hàm và phần không có nó và lưu hai vòng.

+0

Điều này không trả lời được câu hỏi. Nếu bạn không hiểu vấn đề, sau đó yêu cầu làm rõ trong một bình luận. –

+0

Tôi là Jamie thứ hai. Đăng nhận xét nếu bạn cần giải thích rõ hơn. –

5

Tôi giả định đây là một hành động mà bạn muốn được thực hiện nhiều nhất một lần, nếu một số điều kiện được đáp ứng. Vì bạn sẽ không luôn luôn thực hiện hành động, bạn không thể làm điều đó vô điều kiện bên ngoài vòng lặp. Một cái gì đó như lazily lấy một số dữ liệu (và bộ nhớ đệm nó) nếu bạn nhận được một yêu cầu, nhưng không lấy nó nếu không.

def do_something(): 
    [x() for x in expensive_operations] 
    global action 
    action = lambda : None 

action = do_something 
while True: 
    # some sort of complex logic... 
    if foo: 
     action() 
+0

@Ryan Ginstrom: Thật sao !! – pyfunc

+0

Nếu tôi có thể upvote hai lần .... –

+1

Giả sử bạn muốn (hoặc có thể) thay đổi logic của do_something() hoặc myFunction() cho phù hợp với trường hợp cụ thể này. Có thể do_something/myFunction được gọi ở nơi khác trong một ngữ cảnh khác. (Tất nhiên chúng ta sẽ không biết cho đến khi poster làm rõ.) – snapshoe

1

Giả sử có một số lý do tại sao myFunction() không thể được gọi trước khi vòng lặp

from itertools import count 
for i in count(): 
    if i==0: 
     myFunction() 
+0

Ý tưởng rất thông minh - nhưng có vẻ như OP muốn cái gì đó là một khía cạnh của hàm và có hiệu lực đối với bất kỳ cuộc gọi nào, thay vì được điều khiển từ bên ngoài từ một điểm như thế này. – martineau

-1

Nếu việc kiểm tra điều kiện cần phải xảy ra một lần duy nhất bạn đang ở trong vòng lặp, có một tín hiệu cờ mà bạn có đã chạy chức năng giúp. Trong trường hợp này, bạn đã sử dụng bộ đếm, biến boolean sẽ hoạt động tốt.

signal = False 
count = 0 
def callme(): 
    print "I am being called" 
while count < 2: 
    if signal == False : 
     callme() 
     signal = True 
    count +=1 
+0

Điều này không chính xác giống như những gì OP đã làm chưa? –

4

Có nhiều cách để thực hiện những gì bạn muốn; tuy nhiên, hãy lưu ý rằng nó hoàn toàn có thể - được mô tả trong câu hỏi - bạn không phải gọi hàm bên trong vòng lặp.

Nếu bạn nhấn mạnh trong có gọi hàm bên trong vòng lặp, bạn cũng có thể làm:

needs_to_run= expensive_function 
while 1: 
    … 
    if needs_to_run: needs_to_run(); needs_to_run= None 
    … 
+0

+1 Tôi bị cám dỗ xóa câu trả lời của mình nhưng gần đây tôi đã làm quá nhiều. – aaronasterling

+0

@aaron: bạn chắc chắn không nên xóa câu trả lời của mình, bất kể tỷ lệ câu trả lời đã xóa/còn lại của bạn :) – tzot

+1

rõ ràng 'nếu needs_to_run không phải là None' và định dạng thích hợp có thể phù hợp tại đây. 'needs_to_run, run_once = None, needs_to_run; run_once() 'có thể bảo vệ khỏi các ngoại lệ, làm cho ít khả năng nhiều lời gọi hơn để đáp ứng với các tổ hợp phím của người dùng. – jfs

0

Một cách tiếp cận hướng đối tượng và thực hiện chức năng của mình một lớp học, hay còn gọi là "functor", có trường hợp tự động theo dõi xem liệu chúng có được chạy hay không khi mỗi cá thể được tạo ra.

Vì câu hỏi được cập nhật của bạn cho biết bạn có thể cần nhiều người trong số họ, tôi đã cập nhật câu trả lời của mình để giải quyết vấn đề đó bằng cách sử dụng mẫu class factory. Đây là một chút bất thường, và iy có thể đã bị bỏ phiếu vì lý do đó (mặc dù chúng tôi sẽ không bao giờ biết chắc chắn vì họ không bao giờ để lại một bình luận). Nó cũng có thể được thực hiện với một metaclass, nhưng nó không đơn giản hơn nhiều.

def RunOnceFactory(): 
    class RunOnceBase(object): # abstract base class 
     _shared_state = {} # shared state of all instances (borg pattern) 
     has_run = False 
     def __init__(self, *args, **kwargs): 
      self.__dict__ = self._shared_state 
      if not self.has_run: 
       self.stuff_done_once(*args, **kwargs) 
       self.has_run = True 
    return RunOnceBase 

if __name__ == '__main__': 
    class MyFunction1(RunOnceFactory()): 
     def stuff_done_once(self, *args, **kwargs): 
      print("MyFunction1.stuff_done_once() called") 

    class MyFunction2(RunOnceFactory()): 
     def stuff_done_once(self, *args, **kwargs): 
      print("MyFunction2.stuff_done_once() called") 

    for _ in range(10): 
     MyFunction1() # will only call its stuff_done_once() method once 
     MyFunction2() # ditto 

Output:

MyFunction1.stuff_done_once() called 
MyFunction2.stuff_done_once() called 

Lưu ý: Bạn có thể làm cho một hàm/lớp có khả năng làm công cụ một lần nữa bằng cách thêm một phương pháp reset() để lớp con của nó mà reset chia sẻ has_run thuộc tính. Cũng có thể chuyển đối số thông thường và từ khóa sang phương thức stuff_done_once() khi hàm functor được tạo và phương thức được gọi, nếu muốn.

Và, có, nó sẽ được áp dụng cho thông tin bạn đã thêm vào câu hỏi của mình.

+0

Điều này có vẻ khá tốt. Bạn có nhớ đọc câu hỏi được cập nhật của tôi để xem đó có phải là giải pháp tốt không? –

+0

@MarcusOttosson: Xin lỗi vì câu trả lời muộn màng. Nếu tôi hiểu thông tin bổ sung trong câu hỏi được cập nhật của bạn, thì có, tôi nghĩ nó sẽ hoạt động tốt. – martineau

1

Dưới đây là một cách rõ ràng để mã hóa điều này, trong đó trạng thái của hàm được gọi là được giữ cục bộ (vì vậy tránh tình trạng toàn cục). Tôi không giống như các hình thức không rõ ràng được đề xuất trong các câu trả lời khác: nó quá đáng ngạc nhiên khi thấy f() và điều này không có nghĩa là f() được gọi.

Điều này hoạt động bằng cách sử dụng dict.pop tìm kiếm khóa trong dict, xóa khóa khỏi dict và lấy giá trị mặc định để sử dụng trong trường hợp không tìm thấy khóa.

def do_nothing(*args, *kwargs): 
    pass 

# A list of all the functions you want to run just once. 
actions = [ 
    my_function, 
    other_function 
] 
actions = dict((action, action) for action in actions) 

while True: 
    if some_condition: 
     actions.pop(my_function, do_nothing)() 
    if some_other_condition: 
     actions.pop(other_function, do_nothing)() 
3

Tôi đã nghĩ cách khác, rất hiệu quả để thực hiện việc này không yêu cầu chức năng hoặc lớp trang trí. Thay vào đó, nó chỉ sử dụng một đối số từ khóa có thể thay đổi, mà nên làm việc trong hầu hết các phiên bản của Python. Hầu hết thời gian này là một cái gì đó cần tránh vì thông thường bạn sẽ không muốn một giá trị đối số mặc định để có thể thay đổi từ cuộc gọi đến - nhưng khả năng đó có thể được tận dụng và được sử dụng như cơ chế lưu trữ giá rẻ. Dưới đây là làm thế nào nó sẽ làm việc:

def my_function1(_has_run=[]): 
    if _has_run: return 
    print "my_function1 doing stuff" 
    _has_run.append(1) 

def my_function2(_has_run=[]): 
    if _has_run: return 
    print "my_function2 doing some other stuff" 
    _has_run.append(1) 

for i in range(10): 
    my_function1() 
    my_function2() 

my_function1(_has_run=[]) # force it to run 

# output: 
# my_function1 doing stuff 
# my_function2 doing some other stuff 
# my_function1 doing stuff 

Điều này có thể được đơn giản hóa một chút nữa bằng cách làm what @gnibbler suggested in his answer và sử dụng một iterator (mà đã được giới thiệu trong Python 2.2.):

from itertools import count 

def my_function3(_count=count()): 
    if _count.next(): return 
    print "my_function3 doing something" 

for i in range(10): 
    my_function3() 

# my_function3 doing something 
+0

+1 để sử dụng đối số từ khóa có thể thay đổi. –

0

Nếu tôi hiểu câu hỏi được cập nhật một cách chính xác, một cái gì đó như thế này sẽ hoạt động

def function1(): 
    print "function1 called" 

def function2(): 
    print "function2 called" 

def function3(): 
    print "function3 called" 

called_functions = set() 
while True: 
    n = raw_input("choose a function: 1,2 or 3 ") 
    func = {"1": function1, 
      "2": function2, 
      "3": function3}.get(n) 

    if func in called_functions: 
     print "That function has already been called" 
    else: 
     called_functions.add(func) 
     func() 
+0

Điều đó sẽ ngừng tương tác. Xem, ứng dụng này giám sát đầu vào máy tính bảng của tôi và xử lý nó thông qua các biểu thức khác nhau và toán học cơ bản và cuối cùng kết quả đầu ra trên màn hình. Tôi nghĩ rằng những gì tôi đang tìm kiếm không phải là rất độc đáo trong bất kỳ cách nào, gây ra tất cả những gì tôi đang cố gắng làm là, nói, tại báo chí của B, thêm 2 vào bất kỳ biến để phương trình thay đổi. Hoặc, thay đổi màu sắc của một trong các thanh biểu thị một số dữ liệu trên màn hình. Tôi bắt đầu điều rằng có thể không có một cách khác để làm điều đó hơn những gì tôi hiện đang làm mặc dù .. –

+0

Vì vậy, để làm rõ, nó cần phải không chỉ chạy một lần và sau đó không bao giờ có thể chạy lại. Chỉ cần làm điều đó bên trong vòng lặp, và sau đó ngừng làm việc đó. Giống như, thêm 2 vào phương trình của tôi, sau đó tiếp tục làm bất cứ điều gì bạn đã làm trước đây, nhưng với dữ liệu người dùng mới được nhập vào. Công cụ dễ dàng, tôi sẽ nghĩ :) –

+0

@Marcus, tôi không gợi ý rằng _you_ nên sử dụng 'raw_input()'. Nó chỉ có ở đó để bạn có thể chạy chương trình này và có được một ý tưởng về cách nó hoạt động. Âm thanh với tôi rằng ứng dụng của bạn sẽ hoạt động tốt hơn với các cuộc gọi lại mặc dù –

2

Đây là câu trả lời không liên quan đến việc phân bổ lại các chức năng, nhưng vẫn không cần sự xấu xí "f irst "kiểm tra.

__missing__ được hỗ trợ bởi Python 2.5 trở lên.

def do_once_varname1(): 
    print 'performing varname1' 
    return 'only done once for varname1' 
def do_once_varname2(): 
    print 'performing varname2' 
    return 'only done once for varname2' 

class cdict(dict): 
    def __missing__(self,key): 
     val=self['do_once_'+key]() 
     self[key]=val 
     return val 

cache_dict=cdict(do_once_varname1=do_once_varname1,do_once_varname2=do_once_varname2) 

if __name__=='__main__': 
    print cache_dict['varname1'] # causes 2 prints 
    print cache_dict['varname2'] # causes 2 prints 
    print cache_dict['varname1'] # just 1 print 
    print cache_dict['varname2'] # just 1 print 

Output:

performing varname1 
only done once for varname1 
performing varname2 
only done once for varname2 
only done once for varname1 
only done once for varname2 
+0

+1 đối với tôi học được điều gì đó mới mẻ. –

13

lựa chọn khác là để thiết lập func_codecode object cho chức năng của bạn để trở thành một đối tượng mã cho một chức năng mà không làm gì. Điều này nên được thực hiện ở phần cuối của cơ quan chức năng của bạn.

Ví dụ:

def run_once(): 
    # Code for something you only want to execute once 
    run_once.func_code = (lambda:None).func_code 

Đây run_once.func_code = (lambda:None).func_code thay thế mã thực thi của hàm số của bạn với các mã cho lambda: Không, vì vậy tất cả các cuộc gọi tiếp theo để run_once() sẽ không làm gì cả.

Kỹ thuật này ít linh hoạt hơn cách tiếp cận trang trí được đề xuất trong accepted answer, nhưng có thể súc tích hơn nếu bạn chỉ có một hàm bạn muốn chạy một lần.

+0

Đã học được điều gì đó mới mẻ, cảm ơn. :) –

0

Để khắc phục sự cố. bạn chỉ cần thay đổi một mã nhỏ:

run_once = 0 
while run_once < 1: 
     print("Hello") 
     run_once = 1 
0

Bạn có tất cả 'biến rác' bên ngoài đường chính while True vòng lặp. Để làm cho mã dễ đọc hơn, các biến này có thể được đưa vào bên trong vòng lặp, ngay bên cạnh nơi chúng được sử dụng. Bạn cũng có thể thiết lập quy ước đặt tên biến cho các công tắc điều khiển chương trình này. Vì vậy, ví dụ:

#         # _already_done checkpoint logic 
    try: 
     ran_this_user_request_already_done 
    except: 
     this_user_request() 
     ran_this_user_request_already_done = 1 

Lưu ý rằng trên thực hiện đầu tiên của mã này biến ran_this_user_request_already_done không được định nghĩa cho đến sau this_user_request() được gọi.

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