是否可以在Python中向前声明一个函数?


189

是否可以在Python中向前声明一个函数?我想cmp在声明之前使用自己的函数对列表进行排序。

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

我已经组织了代码,以cmp_configs在调用之后放置方法的定义。它因以下错误而失败:

NameError: name 'cmp_configs' is not defined

有没有办法cmp_configs在使用之前“声明” 方法?这会使我的代码看起来更干净吗?

我认为有些人很想告诉我,我应该重新组织我的代码,以免出现此问题。但是,在某些情况下这可能是不可避免的,例如在实施某种形式的递归时。如果您不喜欢此示例,请假设我有一种情况确实需要转发声明一个函数。

考虑以下情况,在Python中有必要向前声明一个函数:

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

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

end_conditionend_result先前已经被定义。

是唯一的组织代码并始终在调用之前放置定义的解决方案吗?

Answers:


76

如果您不想使用某个函数之前先定义它,而以后再定义它是不可能的,那么在其他模块中定义该函数呢?

从技术上讲,您仍然要先定义它,但这是干净的。

您可以创建如下所示的递归:

def foo():
    bar()

def bar():
    foo()

Python的函数是匿名的,就像值是匿名的一样,但是它们可以绑定到名称。

在上面的代码中,foo()没有调用名称为foo的函数,而是调用了恰好在调用时绑定到名称的函数foo。可以在foo其他地方重新定义,bar然后调用新函数。

您的问题无法解决,因为这就像要求获取尚未声明的变量一样。


47
简而言之,如果您将__name__ =='__main__':main()作为脚本的最后一行,那么一切都会好起来!
菲利普·皮纳

3
@FilipePina我不明白您的评论-为什么您不能将代码的最后一行简单地放在main()
Sanjay Manohar

11
@SanjayManohar:避免在import your_module
jfs

2
我想补充一下-有时可以使用lambda规避这些问题,因为稍后会对其进行评估。

2
WRT是“匿名的”,您的意思是“一流的对象”。
丹尼尔2015年

118

您可以做的是将调用包装成它自己的函数。

以便

foo()

def foo():
    print "Hi!"

会破裂,但是

def bar():
    foo()

def foo():
    print "Hi!"

bar()

将正常工作。

一般的规则Python不是该功能应与代码(如中定义更高Pascal),但它应该它的使用之前定义。

希望有帮助。


20
+1最直接的答案,其关键概念是:Pascal =定义更高,Python =定义更早。
鲍勃·斯坦

1
这是正确的答案,它也说明了if __name__=="__main__":解决方案起作用的原因。
00prometheus

2
如果我理解正确,则表示OP的spam()和egg()示例如所编写的一样好。那是对的吗?
krubo

1
@krubo是的,没事没事
lxop

92

如果您通过以下方式启动脚本:

if __name__=="__main__":
   main()

那么您可能不必担心“转发声明”之类的问题。您会看到,解释器将加载所有函数,然后启动main()函数。当然,也请确保所有进口货都正确;-)

想一想,我从未在python中听到过“前向声明”之类的信息……但话又说回来,我可能错了;-)


14
+1最实际的答案:如果将此代码放在最外面的源文件的底部,则可以随意定义任何顺序。
Bob Stein 2014年

2
大提示;真的对我有帮助;因为我更喜欢“自上而下”的编程而不是自下而上的编程。
GhostCat

10

如果对cmp_configs的调用在其自己的函数定义中,则应该没问题。我举一个例子。

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.

通常,将代码放在函数(例如main())中可以解决您的问题;只需在文件末尾调用main()即可。


10

我为恢复该线程表示歉意,但是这里没有讨论可能适用的策略。

使用反射可以做一些类似于转发声明的事情。例如,假设您有一段代码如下:

# 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]()

因此,通过这种方式,我们确定了要在实际定义之前要调用的函数,实际上是前向声明。在蟒蛇的说法globals()[function_name]()是一样的foo(),如果function_name = 'foo'由于上述原因的讨论,因为蟒蛇必须在调用它之前查找各项功能。如果要使用该timeit模块来查看这两个语句的比较结果,则它们具有完全相同的计算成本。

当然,这里的示例是没有用的,但是如果要有一个复杂的结构来执行一个函数,但是必须在执行之前声明它(或者从结构上讲,在事后拥有它就没有意义了),那么可以只存储一个字符串并稍后尝试调用该函数。


8

在python中没有像前向声明这样的东西。您只需要确保在需要使用函数之前就对其进行了声明。请注意,在执行函数之前,不会解释函数的主体。

考虑以下示例:

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.

您可以认为函数的主体只是调用该函数后将被解释的另一个脚本。


7

不,我不相信有任何方法可以在Python中向前声明一个函数。

假设您是Python解释器。当你上线时

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

您要么知道cmp_configs是什么,要么不知道。为了继续,您必须了解cmp_configs。是否存在递归无关紧要。


9
好吧,这是如果您只对代码进行了一次传递。一些编译器(并且我在解释中意识到python)执行两次遍历,以便可以弄清楚这些事情。前向声明,或至少某种范围的发现,将是非常好的。
马克·莱克伍德

7

有时,从总体结构开始并深入研究细节,从上而下最容易理解算法。

您可以不使用前向声明而这样做:

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()

输出:

Hello, world!

3

您无法在Python中向前声明一个函数。如果在定义函数之前先执行逻辑,那么无论如何都可能会遇到问题。将您的动作放在if __name__ == '__main__'脚本的末尾(通过执行一个函数,如果不平凡则将其命名为“ main”),并且您的代码将更具模块化,并且如果需要,您可以将其用作模块至。

另外,用生成器表达(即print "\n".join(str(bla) for bla in sorted(mylist, cmp=cmp_configs)))替换该列表理解

另外,请勿使用cmp已弃用的。使用key并提供小于功能。


如何提供小于功能?
内森·费尔曼

您可以定义一个带有两个参数的函数,而不是cmp_configs,如果第一个小于第二个,则返回True,否则返回False。
迈克·格雷厄姆

对于那些具有C类背景的人来说,在定义函数之前执行逻辑并不是没有道理的。认为:“多遍编译器”。有时需要一段时间才能适应新的语言:)
Luke H

3

导入文件本身。假设文件名为test.py:

import test

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

1

“只是重新组织我的代码,这样我就不会遇到这个问题。” 正确。容易做。一直有效。

您始终可以在引用之前提供该功能。

“但是,在某些情况下,这可能是不可避免的,例如当实施某种形式的递归时”

看不到这是怎么可能的。请提供一个无法使用该功能的地方的示例。


我有这种情况。我正在尝试在函数装饰器中传递类型,并且在模块的下方定义了这些类型。我不能向上移动有问题的类型,因为那样会破坏继承链。
2015年

我通过将lambda传递给装饰器而不是实际的类型来修复它。但我不知道该如何解决(不需要我重新安排继承)

0

现在等一下。当您的模块到达示例中的print语句时,cmp_configs定义,您希望它执行什么操作?

如果您使用印刷品发布问题确实是在尝试代表以下内容:

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

那么就不需要定义 cmp_configs在执行此语句之前,只需在代码的稍后部分进行定义就可以了。

现在,如果您尝试引用cmp_configs作为lambda参数的默认值,那么情况就不同了:

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

现在,您需要cmp_configs先定义一个变量,然后才能到达此行。

[编辑-下一部分证明是不正确的,因为在编译函数时将分配默认参数值,即使以后更改cmp_configs的值,该值也会被使用。]

幸运的是,Python的如此类型容纳,因为它是不关心什么你作为定义cmp_configs,所以你可以只用这种说法前言:

cmp_configs = None

并且编译器会很高兴。只要确保cmp_configs在调用之前声明实数即可fn


-1

一种方法是创建处理程序函数。尽早定义处理程序,并将处理程序置于您需要调用的所有方法的下方。

然后,当您调用处理程序方法来调用函数时,它们将始终可用。

处理程序可以接受争论nameOfMethodToCall。然后使用一堆if语句来调用正确的方法。

这样可以解决您的问题。

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)

看起来非常不可思议。Python应该自己处理所有这类事情。
内森·费尔曼

重新阅读接受的答案。直到调用它,Python才需要定义该函数,而不仅仅是在定义中使用它。
塔卡斯韦尔

-3

是的,我们可以检查一下。

输入项

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()

输出量

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.

正如BJ Homer在上述评论中提到的那样,Python中的一般规则不是在函数中(如在Pascal中)在函数中定义更高的函数,而是应在使用函数之前对其进行定义。

希望有帮助。


2
print_lyrics()定义之前在第1行中没有调用(使用)吗?我复制了这段代码并尝试运行它,它使我NameError: name 'print_lyrics' is not defined在第1行出现错误。您能解释一下吗?
Bugs Buggy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.