Có thể chuyển tiếp khai báo một hàm trong Python không?


188

Có thể chuyển tiếp khai báo một hàm trong Python không? Tôi muốn sắp xếp một danh sách bằng cách sử dụng cmpchức năng của riêng tôi trước khi nó được khai báo.

print "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

Tôi đã tổ chức mã của mình để đặt định nghĩa của cmp_configsphương thức sau khi gọi. Nó không thành công với lỗi này:

NameError: name 'cmp_configs' is not defined

Có cách nào để "khai báo" cmp_configsphương thức trước khi sử dụng không? Nó sẽ làm cho mã của tôi trông sạch hơn?

Tôi cho rằng một số người sẽ bị cám dỗ nói với tôi rằng tôi nên tổ chức lại mã của mình để không gặp phải vấn đề này. Tuy nhiên, có những trường hợp khi điều này có lẽ là không thể tránh khỏi, ví dụ như khi thực hiện một số hình thức đệ quy. Nếu bạn không thích ví dụ này, giả sử rằng tôi có một trường hợp thực sự cần thiết để chuyển tiếp khai báo một hàm.

Hãy xem xét trường hợp này khi việc khai báo chuyển tiếp một hàm sẽ là cần thiết trong Python:

def spam():
    if end_condition():
        return end_result()
    else:
        return eggs()

def eggs():
    if end_condition():
        return end_result()
    else:
        return spam()

Ở đâu end_conditionend_resultđã được xác định trước đó.

Là giải pháp duy nhất để sắp xếp lại mã và luôn đặt định nghĩa trước khi gọi?

Câu trả lời:


76

Nếu bạn không muốn xác định hàm trước khi nó được sử dụng và việc xác định hàm sau đó là không thể, vậy còn việc xác định hàm đó trong một số mô-đun khác thì sao?

Về mặt kỹ thuật bạn vẫn xác định nó trước, nhưng nó sạch sẽ.

Bạn có thể tạo một đệ quy như sau:

def foo():
    bar()

def bar():
    foo()

Các hàm của Python là ẩn danh giống như các giá trị là ẩn danh, nhưng chúng có thể được liên kết với một tên.

Trong đoạn mã trên, foo()không gọi một hàm với tên foo, nó gọi một hàm tình cờ bị ràng buộc với tên footại điểm mà cuộc gọi được thực hiện. Có thể xác định lại fooở một nơi khác, và barsau đó sẽ gọi hàm mới.

Vấn đề của bạn không thể được giải quyết vì nó giống như yêu cầu lấy một biến chưa được khai báo.


47
Nói tóm lại, nếu bạn có if __name__ == '__main__': main () là dòng cuối cùng trong tập lệnh của bạn thì mọi thứ sẽ ổn thôi!
Filipe Pina

3
@FilipePina Tôi chưa hiểu nhận xét của bạn - tại sao bạn không thể đặt dòng mã cuối cùng đơn giản main()?
Sanjay Manohar

11
@SanjayManohar: để tránh thực hiện nó trênimport your_module
jfs

2
Tôi muốn thêm - đôi khi có thể tránh được các vấn đề này bằng lambdas vì chúng được đánh giá sau.
Joe

2
Wrt "nặc danh", bạn thực sự có nghĩa là "đối tượng hạng nhất".
danielm

117

Những gì bạn có thể làm là bọc lời mời thành một chức năng của riêng nó.

Vậy nên

foo()

def foo():
    print "Hi!"

sẽ phá vỡ, nhưng

def bar():
    foo()

def foo():
    print "Hi!"

bar()

sẽ làm việc đúng

Nguyên tắc chung trong Pythonkhông có chức năng cần được xác định cao hơn trong các mã (như trong Pascal), nhưng điều đó nó cần được xác định trước khi sử dụng nó.

Mong rằng sẽ giúp.


20
+1 câu trả lời trực tiếp nhất, với khái niệm keystone: Pascal = xác định cao hơn, Python = xác định trước đó.
Bob Stein

1
Đây là câu trả lời đúng, nó cũng giải thích tại sao if __name__=="__main__":giải pháp hoạt động.
00prometheus

2
Nếu tôi hiểu chính xác, điều đó có nghĩa là ví dụ về thư rác () và trứng () của OP là tốt như đã viết. Đúng không?
krubo

1
@krubo vâng, nó vẫn ổn như đã viết
lxop 13/03/19

92

Nếu bạn khởi động kịch bản của mình thông qua các mục sau:

if __name__=="__main__":
   main()

sau đó bạn có thể không phải lo lắng về những thứ như "tuyên bố chuyển tiếp". Bạn thấy đấy, trình thông dịch sẽ tải lên tất cả các hàm của bạn và sau đó bắt đầu hàm main () của bạn. Tất nhiên, đảm bảo rằng bạn cũng có tất cả các nhập khẩu chính xác ;-)

Nghĩ lại thì, tôi chưa bao giờ nghe một thứ gọi là "tuyên bố chuyển tiếp" trong python ... nhưng một lần nữa, tôi có thể sai ;-)


14
+1 câu trả lời thiết thực nhất: Nếu bạn đặt mã này ở dưới cùng của tệp nguồn ngoài cùng, thì bạn có thể tự do xác định theo bất kỳ thứ tự nào.
Bob Stein

2
Mẹo tuyệt vời; thực sự giúp tôi vì tôi thích lập trình "từ trên xuống" hơn so với từ dưới lên.
GhostCat

10

Nếu lệnh gọi cmp_configs nằm trong định nghĩa hàm riêng của nó, bạn sẽ ổn. Tôi sẽ đưa ra một ví dụ.

def a():
  b()  # b() hasn't been defined yet, but that's fine because at this point, we're not
       # actually calling it. We're just defining what should happen when a() is called.

a()  # This call fails, because b() hasn't been defined yet, 
     # and thus trying to run a() fails.

def b():
  print "hi"

a()  # This call succeeds because everything has been defined.

Nói chung, việc đặt mã của bạn bên trong các hàm (chẳng hạn như main ()) sẽ giải quyết vấn đề của bạn; chỉ cần gọi hàm main () ở cuối tệp.


10

Tôi xin lỗi vì đã phục hồi chủ đề này, nhưng có một chiến lược không được thảo luận ở đây có thể được áp dụng.

Sử dụng sự phản chiếu có thể làm một cái gì đó giống như để chuyển tiếp khai báo. Chẳng hạn, giả sử bạn có một phần mã giống như thế này:

# We want to call a function called 'foo', but it hasn't been defined yet.
function_name = 'foo'
# Calling at this point would produce an error

# Here is the definition
def foo():
    bar()

# Note that at this point the function is defined
    # Time for some reflection...
globals()[function_name]()

Vì vậy, theo cách này, chúng tôi đã xác định chức năng nào chúng tôi muốn gọi trước khi nó thực sự được xác định, thực sự là một tuyên bố chuyển tiếp. Trong python, câu lệnh globals()[function_name]()giống như foo()thể function_name = 'foo'vì các lý do đã thảo luận ở trên, vì python phải tra cứu từng hàm trước khi gọi nó. Nếu một người sử dụng timeitmô-đun để xem hai câu lệnh này so sánh như thế nào, thì chúng có cùng chi phí tính toán.

Tất nhiên ví dụ ở đây là rất vô dụng, nhưng nếu người ta có một cấu trúc phức tạp cần thực thi một chức năng, nhưng phải được khai báo trước (hoặc về mặt cấu trúc thì sẽ không có ý nghĩa gì sau đó), người ta chỉ có thể lưu trữ một chuỗi và cố gắng gọi chức năng sau.


8

Không có điều đó trong python như tuyên bố chuyển tiếp. Bạn chỉ cần đảm bảo rằng chức năng của bạn được khai báo trước khi cần. Lưu ý rằng phần thân của hàm không được giải thích cho đến khi hàm được thực thi.

Hãy xem xét ví dụ sau:

def a():
   b() # won't be resolved until a is invoked.

def b(): 
   print "hello"

a() # here b is already defined so this line won't fail.

Bạn có thể nghĩ rằng phần thân của hàm chỉ là một tập lệnh khác sẽ được diễn giải khi bạn gọi hàm.


7

Không, tôi không tin có bất kỳ cách nào để chuyển tiếp khai báo một hàm trong Python.

Hãy tưởng tượng bạn là người phiên dịch Python. Khi bạn đến dòng

print "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

hoặc bạn biết cmp_configs là gì hoặc bạn không biết. Để tiến hành, bạn phải biết cmp_configs. Nó không quan trọng nếu có đệ quy.


9
tốt, đây là nếu bạn chỉ thực hiện một lần vượt qua mã. Một số trình biên dịch (và tôi nhận ra python trong diễn giải) thực hiện hai lần, để những điều này có thể được tìm ra. tuyên bố chuyển tiếp, hoặc ít nhất là một loại khám phá phạm vi, sẽ thực sự tốt.
Mark Lakewood

7

Đôi khi một thuật toán dễ hiểu nhất từ ​​trên xuống, bắt đầu với cấu trúc tổng thể và đi sâu vào chi tiết.

Bạn có thể làm như vậy mà không cần khai báo chuyển tiếp:

def main():
  make_omelet()
  eat()

def make_omelet():
  break_eggs()
  whisk()
  fry()

def break_eggs():
  for egg in carton:
    break(egg)

# ...

main()

4
# declare a fake function (prototype) with no body
def foo(): pass

def bar():
    # use the prototype however you see fit
    print(foo(), "world!")

# define the actual function (overwriting the prototype)
def foo():
    return "Hello,"

bar()

Đầu ra:

Hello, world!

3

Bạn không thể chuyển tiếp khai báo một hàm trong Python. Nếu bạn có logic thực thi trước khi bạn xác định hàm, dù sao bạn cũng có thể gặp vấn đề. Đặt hành động của bạn trong mộtif __name__ == '__main__' vào cuối tập lệnh của bạn (bằng cách thực hiện một chức năng mà bạn đặt tên là "chính" nếu nó không tầm thường) và mã của bạn sẽ có tính mô đun hơn và bạn sẽ có thể sử dụng nó làm mô-đun nếu bạn cần đến.

Ngoài ra, thay thế sự hiểu biết danh sách đó bằng một trình tạo tốc độ (nghĩa là print "\n".join(str(bla) for bla in sorted(mylist, cmp=cmp_configs)))

Ngoài ra, không sử dụng cmp, không được dùng nữa. Sử dụng keyvà cung cấp một chức năng ít hơn.


Làm thế nào để tôi cung cấp một chức năng ít hơn?
Nathan Fellman

Thay vì cmp_configs, bạn sẽ xác định hàm nhận hai đối số và trả về True nếu giá trị thứ nhất nhỏ hơn giá trị thứ hai và Sai.
Mike Graham

Đối với những người trong chúng ta đến từ nền tảng giống như C, không có gì bất hợp lý về việc thực thi logic trước khi các chức năng được xác định. Hãy suy nghĩ: "trình biên dịch nhiều lượt". Đôi khi phải mất một thời gian để thích nghi với các ngôn ngữ mới :)
Luke H

3

Nhập tệp chính nó. Giả sử tập tin được gọi là test.py:

import test

if __name__=='__main__':
    test.func()
else:
    def func():
        print('Func worked')

1

"chỉ cần sắp xếp lại mã của tôi để tôi không gặp vấn đề này." Chính xác. Dễ làm. Luôn luôn làm việc.

Bạn luôn có thể cung cấp chức năng trước khi tham chiếu.

"Tuy nhiên, có những trường hợp khi điều này có lẽ là không thể tránh khỏi, ví dụ như khi thực hiện một số hình thức đệ quy"

Không thể thấy điều đó thậm chí có thể từ xa. Vui lòng cung cấp một ví dụ về nơi bạn không thể xác định chức năng trước khi sử dụng.


Tôi có một tình huống như vậy. Tôi đang cố gắng truyền các loại trong một trình trang trí chức năng và các loại được xác định sâu hơn về mô-đun. Tôi không thể di chuyển các loại vi phạm lên, vì điều đó sẽ phá vỡ chuỗi thừa kế.
Joe

Tôi đã sửa nó bằng cách chuyển lambda cho trang trí của tôi thay vì loại thực tế; nhưng tôi sẽ không biết cách khắc phục bằng cách khác (điều đó sẽ không yêu cầu tôi sắp xếp lại các tài sản thừa kế của mình)
Joe

0

Bây giờ hãy đợi một phút. Khi mô-đun của bạn đạt đến câu lệnh in trong ví dụ của bạn, trước đây cmp_configsđã được xác định, chính xác thì bạn mong đợi nó sẽ làm gì?

Nếu việc đăng câu hỏi của bạn bằng cách sử dụng in thực sự đang cố gắng thể hiện một cái gì đó như thế này:

fn = lambda mylist:"\n".join([str(bla)
                         for bla in sorted(mylist, cmp = cmp_configs)])

sau đó không có yêu cầu xác định cmp_configstrước khi thực hiện câu lệnh này, chỉ cần xác định nó sau trong mã và tất cả sẽ ổn.

Bây giờ nếu bạn đang cố gắng tham chiếu cmp_configsnhư một giá trị mặc định của một đối số cho lambda, thì đây là một câu chuyện khác:

fn = lambda mylist,cmp_configs=cmp_configs : \
    "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

Bây giờ bạn cần một cmp_configsbiến được xác định trước khi bạn đạt đến dòng này.

[EDIT - phần tiếp theo này hóa ra không chính xác, vì giá trị đối số mặc định sẽ được gán khi hàm được biên dịch và giá trị đó sẽ được sử dụng ngay cả khi bạn thay đổi giá trị của cmp_configs sau.]

May mắn thay, Python là nên type-sức chứa như nó có, không quan tâm đến những gì bạn định nghĩa như cmp_configs, vì vậy bạn chỉ có thể lời nói đầu với tuyên bố này:

cmp_configs = None

Và trình biên dịch sẽ được hạnh phúc. Chỉ cần chắc chắn để khai báo thực tế cmp_configstrước khi bạn gọi fn.


-1

Một cách là tạo ra một hàm xử lý. Xác định trình xử lý sớm và đặt trình xử lý bên dưới tất cả các phương thức bạn cần gọi.

Sau đó, khi bạn gọi phương thức xử lý để gọi các hàm của mình, chúng sẽ luôn khả dụng.

Người xử lý có thể đưa ra một cuộc tranh luận nameOfMethodToCall. Sau đó sử dụng một loạt các câu lệnh if để gọi phương thức đúng.

Điều này sẽ giải quyết vấn đề của bạn.

def foo():
    print("foo")
    #take input
    nextAction=input('What would you like to do next?:')
    return nextAction

def bar():
    print("bar")
    nextAction=input('What would you like to do next?:')
    return nextAction

def handler(action):
    if(action=="foo"):
        nextAction = foo()
    elif(action=="bar"):
        nextAction = bar()
    else:
        print("You entered invalid input, defaulting to bar")
        nextAction = "bar"
    return nextAction

nextAction=input('What would you like to do next?:')

while 1:
    nextAction = handler(nextAction)

Điều đó có vẻ rất không linh hoạt. Python nên tự xử lý loại công cụ này.
Nathan Fellman

đọc lại câu trả lời được chấp nhận Python không cần định nghĩa hàm cho đến khi bạn gọi nó, không chỉ sử dụng nó trong một định nghĩa.
tacaswell

-3

Vâng, chúng tôi có thể kiểm tra điều này.

Đầu vào

print_lyrics() 
def print_lyrics():

    print("I'm a lumberjack, and I'm okay.")
    print("I sleep all night and I work all day.")

def repeat_lyrics():
    print_lyrics()
    print_lyrics()
repeat_lyrics()

Đầu ra

I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

Như BJ Homer đã đề cập ở trên các nhận xét ở trên, Một quy tắc chung trong Python không phải là hàm đó nên được xác định cao hơn trong mã (như trong Pascal), nhưng nó phải được xác định trước khi sử dụng.

Mong rằng sẽ giúp.


2
Không print_lyrics()được gọi (được sử dụng) trong dòng 1 trước khi được xác định? Tôi đã sao chép đoạn mã này và cố gắng chạy nó, và nó báo lỗi cho tôi NameError: name 'print_lyrics' is not definedtrên Dòng 1. Bạn có thể giải thích điều này không?
Lỗi Buggy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.