扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
检查调用语句和函数定义。
创新互联主营金湾网站建设的网络公司,主营网站建设方案,app开发定制,金湾h5重庆小程序开发搭建,金湾网站营销推广欢迎金湾等地区企业咨询
函数要先定义才能调用,如果函数已经定义则检查调用语句是否出错,仔细分析报错原因。
Python由荷兰数学和计算机科学研究学会的吉多·范罗苏姆于1990年代初设计,作为一门叫做ABC语言的替代品,Python提供了高效的高级数据结构,还能简单有效地面向对象编程Python语法和动态类型,以及解释型语言的本质,使它成为多数平台上写脚本和快速开发应用的编程语言,随着版本的不断更新和语言新功能的添加,逐渐被用于独立的、大型项目的开发。
python将自函数的变量传给主函数:通过变量传递就可以。
def func():global x,print 'x is', x,x=2,print 'Changed local x to', xx = 50func()print 'Value of x is', x。执行结果是这样的:x is 50Changed global x to 2Value of x is 2。
可移植性:
由于它的开源本质,Python已经被移植在许多平台上(经过改动使它能够工作在不同平台上)。这些平台包括Linux、Windows、FreeBSD、Macintosh、Solaris、OS/2。
Amiga、AROS、AS/400、BeOS、OS/390、z/OS、Palm OS、QNX、VMS、Psion、Acom RISC OS、VxWorks、PlayStation、Sharp Zaurus、Windows CE、PocketPC、Symbian以及Google基于linux开发的android平台。
一个用编译性语言比如C或C++写的程序可以从源文件(即C或C++语言)转换到一个你的计算机使用的语言(二进制代码,即0和1)。这个过程通过编译器和不同的标记、选项完成。
print 'main'当脚本作为执行脚本时__name__的值为__main__当脚本作为模块时__name__为模块文件名。
main函数在程序中大多数是必须存在的。C语言标准中强制要求main函数的返回值类型为int,main函数的返回值是传递给操作系统,让操作系统判断程序的执行情况。
一个程序,无论复杂或简单,总体上都是一个“函数”;这个函数就称为“main 函数”,也就是“主函数”。比如有个“做菜”程序,那么“ 做菜 ”这个过程就是“主函数”。在主函数中,根据情况,你可能还需要调用“买菜,切菜,炒菜”等子函数。
main函数在程序中大多数是必须存在的,但是依然有例外情况,比如windows编程中可以编写一个动态链接库模块,这是其他windows程序可以使用的代码。由于DLL模块不是独立的程序,因此不需要main函数。
从函数的形势看,函数分两类:
无参函数。printstar和print_message就是无参函数。在调用无参函数时,主调函数并不将数据传送给被调用函数,一般用来执行指定的一组操作。
有参函数。在调用函数时,在主调函数和被调用函数之间有数据传递。也就是说,主调函数可以将数据传递给被调用函数使用,被调用函数中的数据也可以带回来供主调函数使用。
目录
许多编程语言都有一个特殊的函数,当操作系统开始运行程序时会自动执行该函数。这个函数通常被命名为main(),并且依据语言标准具有特定的返回类型和参数。另一方面,Python解释器从文件顶部开始执行脚本,并且没有自动执行的特殊函数。
尽管如此,为程序的执行定义一个起始点有助于理解程序是如何运行的。Python程序员提出了几种方式对此进行实现。
本文结束时,您将了解以下内容:
Python中的基本main()函数
一些Python脚本中,包含一个函数定义和一个条件语句,如下所示:
此代码中,包含一个main()函数,在程序执行时打印Hello World!。此外,还包含一个条件(或if)语句,用于检查__name__的值并将其与字符串"__main__"进行比较。当if语句为True时,Python解释器将执行main()函数。更多关于Python条件语句的信息可以由此获得。
这种代码模式在Python文件中非常常见,它将作为脚本执行并导入另一个模块。为了帮助理解这段代码的执行方式,首先需要了解Python解释器如何根据代码的执行方式设置__name__。
Python中的执行模式
Python解释器执行代码有两种方式:
更多内容可参考如何运行Python脚本。无论采用哪种方式,Python都会定义一个名为__name__的特殊变量,该变量包含一个字符串,其值取决于代码的使用方式。
本文将如下示例文件保存为execution_methods.py,以 探索 代码如何根据上下文改变行为:
在此文件中,定义了三个对print()函数的调用。前两个打印一些介绍性短语。第三个print()会先打印短语The value __name__ is,之后将使用Python内置的repr()函数打印出__name__变量。
在Python中,repr()函数将对象转化为供解释器读取的形式。上述示例通过使用repr()函数来强调__name__的值为字符串。更多关于repr()的内容可参考Python文档。
在本文中,您将随处可见文件(file),模块(module)和脚本(script)这三个字眼。实际上,三者之间并无太大的差别。不过,在强调代码目的时,还是存在细微的差异:
“如何运行Python脚本”一文也讨论了三者的差别。
基于命令行执行
在这类方法中,Python脚本将通过命令行来执行。
执行脚本时,无法与Python解释器正在执行的代码交互。关于如何通过命令行执行代码的详细信息对本文而言并不重要,但您可以通过展开下框阅读更多有关Windows,Linux和macOS之间命令行差异的内容。
命令行环境
不同的操作系统在使用命令行执行代码时存在细微的差异。
在Linux和macOS中,通常使用如下命令:
美元符号($)之前的内容可能有所不同,具体取决于您的用户名和计算机名称。您键入的命令位于$之后。在Linux或macOS上,Python3的可执行文件名为python3,因此可以通过输入python3 script_name.py来运行python脚本。
在Windows上,命令提示符通常如下所示:
根据您的用户名,之前的内容可能会有所不同,您输入的命令位于之后。在Windows上,Python3的可执行文件通常为python。因此可以通过输入python script_name.py来运行python脚本。
无论哪种操作系统,本文的Python脚本的输出结果都是相同的。因此本文以Linux和macOS为例。
使用命令行执行execution_methods.py,如下所示:
在这个示例中,__name__具有值'__main__',其中引号(')表明该值为字符串类型。
请记住,在Python中,使用单引号(')和双引号(")定义的字符串没有区别。更多关于字符串的内容请参考Python的基本数据类型。
如果在脚本中包含"shebang行"并直接执行它(./execution_methods.py),或者使用IPython或Jupyter Notebook的%run,将会获取相同的结果。
您还可以通过向命令行添加-m参数的方法实现以模块的方式执行。通常情况下,推荐如下方式pip: python3 -m pip install package_name。
添加-m参数将会运行包中__main__.py的代码。更多关于__main__.py文件的内容可参考如何将开源Python包发布到PyPI中。
在三种情况中,__name__都具有相同的值:字符串'__main__'。
技术细节:Python文档中具体定义了__name__何时取值为'__main__'。
当通过标准输入,脚本或者交互提示中读取数据时,模块的__name__将取值为'__main__'。(来源)
__name__与__doc__,__package__和其他属性一起存储在模块的全局命名空间。更多关于属性的信息可参考Python数据模型文档,特别是关于模块和包的信息,请参阅Python Import文档。
导入模块或解释器
接下来是Python解释器执行代码的第二种方式:导入。在开发模块或脚本时,可以使用import关键字导入他人已经构建的模块。
在导入过程中,Python执行指定模块中定义的语句(但仅在第一次导入模块时)。要演示导入execution_methods.py文件的结果,需要启动Python解释器,然后导入execution_methods.py文件:
在此代码输出中,Python解释器执行了三次print()函数调用。前两行由于没有变量,在输出方面与在命令行上作为脚本执行时完全相同。但是第三个输出存在差异。
当Python解释器导入代码时,__name__的值与要导入的模块的名称相同。您可以通过第三行的输出了解这一点。__name__的值为'execution_methods',是Python导入的.py文件。
注意如果您在没有退出Python时再次导入模块,将不会有输出。
注意:更多关于导入在Python中如何工作的内容请参考官方文档和Python中的绝对和相对导入。
Main函数的最佳实践
既然您已经了解两种执行方式上的差异,那么掌握一些最佳实践方案还是很有用的。它们将适用于编写作为脚本运行的代码或者在另一个模块导入的代码。
如下是四种实践方式:
将大部分代码放入函数或类中
请记住,Python解释器在导入模块时会执行模块中的所有代码。有时如果想要实现用户可控的代码,会导致一些副作用,例如:
在这种情况下,想要实现用户控制触发此代码的执行,而不是让Python解释器在导入模块时执行代码。
因此,最佳方法是将大部分代码包含在函数或类中。这是因为当Python解释器遇到def或class关键字时,它只存储这些定义供以后使用,并且在用户通知之前不会实际执行。
将如下代码保存在best_practices.py以证明这个想法:
在此代码中,首先从time模块中导入sleep()。
在这个示例中,参数以秒的形式传入sleep()函数中,解释器将暂停一段时间再运行。随后,使用print()函数打印关于代码描述的语句。
之后,定义一个process_data()函数,执行如下五项操作:
在命令行中执行
当你将此文件作为脚本用命令行执行时会发生什么呢?
Python解释器将执行函数定义之外的from time import sleep和print(),之后将创建函数process_data()。然后,脚本将退出而不做任何进一步的操作,因为脚本没有任何执行process_data()的代码。
如下是这段脚本的执行结果:
我们在这里看到的输出是第一个print()的结果。注意,从time导入和定义process_data()函数不产生结果。具体来说,调用定义在process_data()内部的print()不会打印结果。
导入模块或解释器执行
在会话(或其他模块)中导入此文件时,Python解释器将执行相同的步骤。
Python解释器导入文件后,您可以使用已导入模块中定义的任何变量,类或函数。为了证明这一点,我们将使用可交互的Python解释器。启动解释器,然后键入import best_practices:
导入best_practices.py后唯一的输出来自process_data()函数外定义的print()。导入模块或解释器执行与基于命令行执行类似。
使用__name__控制代码的执行
如何实现基于命令行而不使用Python解释器导入文件来执行呢?
您可以使用__name__来决定执行上下文,并且当__name__等于"__main__"时才执行process_data()。在best_practices.py文件中添加如下代码:
这段代码添加了一个条件语句来检验__name__的值。当值为"__main__"时,条件为True。记住当__name__变量的特殊值为"__main__"时意味着Python解释器会执行脚本而不是将其导入。
条件语块内添加了四行代码(第12,13,14和15行):
现在,在命令行中运行best_practices.py,并观察输出的变化:
首先,输出显示了process_data()函数外的print()的调用结果。
之后,data的值被打印。因为当Python解释器将文件作为脚本执行时,变量__name__具有值"__main__",因此条件语句被计算为True。
接下来,脚本将调用process_data()并传入data进行修改。当process_data执行时,将输出一些状态信息。最终,将输出modified_data的值。
现在您可以验证从解释器(或其他模块)导入best_practices.py后发生的事情了。如下示例演示了这种情况:
注意,当前结果与将条件语句添加到文件末尾之前相同。因为此时__name__变量的值为"best_practices",因此条件语句结果为False,Python将不执行process_data()。
创建名为main()的函数来包含要运行的代码
现在,您可以编写作为脚本由从命令行执行并导入且没有副作用的Python代码。接下来,您将学习如何编写代码并使其他程序员能轻松地理解其含义。
许多语言,如C,C++,Java以及其他的一些语言,都会定义一个叫做main()的函数,当编译程序时,操作系统会自动调用该函数。此函数通常被称为入口点(entry point),因为它是程序进入执行的起始位置。
相比之下,Python没有一个特殊的函数作为脚本的入口点。实际上在Python中可以将入口点定义成任何名称。
尽管Python不要求将函数命名为main(),但是最佳的做法是将入口点函数命名为main()。这样方便其他程序员定位程序的起点。
此外,main()函数应该包含Python解释器执行文件时要运行的任何代码。这比将代码放入条件语块中更好,因为用户可以在导入模块时重复使用main()函数。
修改best_practices.py文件如下所示:
在这个示例中,定义了一个main()函数,它包含了上面的条件语句块。之后修改条件语块执行main()。如果您将此代码作为脚本运行或导入,将获得与上一节相同的输出。
在main()中调用其他函数
另一种常见的实现方式是在main()中调用其他函数,而不是直接将代码写入main()。这样做的好处在于可以实现将几个独立运行的子任务整合。
例如,某个脚本有如下功能:
如果在单独的函数中各自实现这些子任务,您(或其他用户)可以很容易地实现代码重用。之后您可以在main()函数中创建默认的工作流。
您可以根据自己的情况选择是否使用此方案。将任务拆分为多个函数会使重用更容易,但会增加他人理解代码的难度。
修改best_practices.py文件如下所示:
在此示例代码中,文件的前10行具有与之前相同的内容。第12行的第二个函数创建并返回一些示例数据,第17行的第三个函数模拟将修改后的数据写入数据库。
第21行定义了main()函数。在此示例中,对main()做出修改,它将调用数据读取,数据处理以及数据写入等功能。
首先,从read_data_from_web()中创建data。将data作为参数传入process_data(),之后将返回modified_data。最后,将modified_data传入write_data_to_database()。
脚本的最后两行是条件语块用于验证__name__,并且如果if语句为True,则执行main()。
在命令行中运行如下所示:
根据执行结果,Python解释器在执行main()函数时,将依次执行read_data_from_web(),process_data()以及write_data_to_database()。当然,您也可以导入best_practices.py文件并重用process_data()作为不同的数据输入源,如下所示:
在此示例中,导入了best_practices并且将其简写为bp。
导入过程会导致Python解释器执行best_practices.py的全部代码,因此输出显示解释文件用途的信息。
然后,从文件中存储数据而不是从Web中读取数据。之后,可以重用best_practices.py文件中的process_data()和write_data_to_database()函数。在此情况下,可以利用代码重写来取代在main()函数中实现全部的代码逻辑。
实践总结
以下是Python中main()函数的四个关键最佳实践:
结论
恭喜!您现在已经了解如何创建Python main()函数了。
本文介绍了如下内容:
现在,您可以开始编写一些非常棒的关于Python main()函数代码啦!
一般来说,Python程序员可能是这样写main()函数的:
"""Module docstring.
This serves as a long usage message.
"""import sysimport getoptdef main():
# parse command line options
try:
opts, args = getopt.getopt(sys.argv[1:], "h", ["help"]) except getopt.error, msg: print msg print "for help use --help"
sys.exit(2) # process options
for o, a in opts: if o in ("-h", "--help"): print __doc__
sys.exit(0) # process arguments
for arg in args:
process(arg) # process() is defined elsewhereif __name__ == "__main__":
main()1234567891011121314151617181920212223242526
Guido也承认之前自己写的main()函数也是类似的结构,但是这样写的灵活性还不够高,尤其是需要解析复杂的命令行选项时。为此,他向大家提出了几点建议。
添加可选的 argv 参数
首先,修改main()函数,使其接受一个可选参数 argv,支持在交互式shell中调用该函数:
def main(argv=None):
if argv is None:
argv = sys.argv # etc., replacing sys.argv with argv in the getopt() call.1234
这样做,我们就可以动态地提供 argv 的值,这比下面这样写更加的灵活:
def main(argv=sys.argv):
# etc.12
这是因为在调用函数时,sys.argv 的值可能会发生变化;可选参数的默认值都是在定义main()函数时,就已经计算好的。
但是现在sys.exit()函数调用会产生问题:当main()函数调用sys.exit()时,交互式解释器就会推出!解决办法是让main()函数的返回值指示退出状态(exit status)。因此,最后面的那行代码就变成了这样:
if __name__ == "__main__":
sys.exit(main())12
并且,main()函数中的sys.exit(n)调用全部变成return n。
定义一个Usage()异常
另一个改进之处,就是定义一个Usage()异常,可以在main()函数最后的except子句捕捉该异常:
import sysimport getoptclass Usage(Exception):
def __init__(self, msg):
self.msg = msgdef main(argv=None):
if argv is None:
argv = sys.argv try: try:
opts, args = getopt.getopt(argv[1:], "h", ["help"]) except getopt.error, msg: raise Usage(msg) # more code, unchanged
except Usage, err: print sys.stderr, err.msg print sys.stderr, "for help use --help"
return 2if __name__ == "__main__":
sys.exit(main())123456789101112131415161718192021222324
这样main()函数就只有一个退出点(exit)了,这比之前两个退出点的做法要好。而且,参数解析重构起来也更容易:在辅助函数中引发Usage的问题不大,但是使用return 2却要求仔细处理返回值传递的问题。
在前面已经多次提到函数这个概念,之所以没有解释什么是函数,是因为程序中的函数和数学中的函数差不多,如input()、range()等都是函数,这些都是Python的标准函数,直接使用就可以了。根据需要,用户也可以自定义函数。
12.1 函数
函数的结构:
def 函数名(参数):
函数体
return 返回值
例如:数学中的函数f(x)=2x+5在Python中可以定义如下:
def f(x):
y=2*x+5
return(y)
如果x取值为3,可以使用如下语句调用函数:
f(3)
下面给出完整的程序代码:
def f(x):
y=2*x+5
return(y)
res=f(3)
print(res)
运行结果:11
如上例中的x是函数f(x)的参数,有时也被称为形式参数(简称形参),在函数被调用时,x被具体的值3替换y就是函数的返回值,这个值3也被称为实际参数(简称实参)。
上例中的y是函数f(x)的返回值。并不是所有的函数都有参数和返回值。如下面的函数:
def func():
print('此为无参数传递、无返回值的函数')
func()
输出结果:此为无参数传递、无返回值的函数
可以看出,该函数func()无参数,故调用时不用赋给参数值。
函数也可以有多个参数,如f(x,y)=x²+y²,可用Python语言定义如下:
def f(x,y):
z=x**2+y**2
return z
print(f(2,3)) #调用函数f(x,y)
输出结果:13
也可以通过直接给参数列表中的参数赋值的方法,为参数添加默认值,如果用户赋予参数值,则按照用户赋值执行,否则使用默认值。例如:
def f(x,y=3):
z=x**2+y**2
return z
若调用时参数列表为(2,1),即x赋值为2,y赋值为1:
print(f(2,1))
输出结果为:5
若调用时参数列表为(2),即x赋值为2,y赋值省缺,则y使用默认值:
print(f(2))
输出结果为:13
回调函数,又称函数回调,是将函数作为另一函数的参数。
例如:
def func(fun,m,n):
fun(m,n)
def f_add(m,n):
print('m+n=',m+n)
def f_mult(m,n):
print('m*n=',m*n)
func(f_add,2,3)
func(f_mult,2,3)
输出结果:
m+n= 5
m*n= 6
在f_add(m,n)和f_mult(m,n)被定义前,func(fun,m,n)中的fun(m,n)就已经调用了这两个函数,即“先调用后定义”,这也是回调函数的特点。
如果无法预知参数的个数,可以在参数前面加上*号,这种参数实际上对应元组类型。譬如,参会的人数事先不能确定,只能根据与会人员名单输入:
def func(*names):
print('今天与会人员有:')
for name in names:
print(name)
func('张小兵','陈晓梅','李大海','王长江')
运行后,输出结果为:
今天与会人员有:
张小兵
陈晓梅
李大海
王长江
参数为字典类型,需要在参数前面加上**号。
def func(**kwargs):
for i in kwargs:
print(i,kwargs[i])
func(a='a1',b='b1',c='c1')
输出结果为:
a a1
b b1
c c1
一个有趣的实例:
def func(x,y,z,*args,**kwargs):
print(x,y,z)
print(args)
print(kwargs)
func('a','b','c','Python','is easy',py='python',j='java',ph='php')
输出结果:
a b c # 前三个实参赋给前三个形参
('Python', 'is easy') # *args接收元组数据
{'py': 'python', 'j': 'java', 'ph': 'php'} # **kwargs接收字典数据
12.2 变量的作用域
变量的作用域即变量的有效范围,可分为全局变量和局部变量。
局部变量
在函数中定义的变量就是局部变量,局部变量的作用域仅限于函数内部使用。
全局变量
在主程序中定义的变量就是全局变量,但在函数中用关键字global修饰的变量也可以当做全局变量来使用。
全局变量的作用域是整个程序,也就是说,全局变量可以在整个程序中可以访问。
下面通过实例去讨论:
程序1:
a=1 # a为全局变量
def a_add():
print('a的初值:',a) # 在函数中读取a的值
a_add() # 调用函数a_add()
a+=1 # 主程序语句,a增加1
print('a现在的值是:',a) # 主程序语句,读取a的值
运行结果:
a的初值: 1
a现在的值是: 2
这个结果和我们想象的一样,全局变量a既可以在主程序中读取,也可以在子程序(函数)中读取。
程序2:
a=1
def a_add():
a+=1
print('a的初值:',a)
a_add()
print('a现在的值是:',a)
运行程序1时出现如下错误提示:
UnboundLocalError: local variable 'a' referenced before assignment
意思是:局部变量'a'在赋值之前被引用。
从语法上来讲,该程序没有错误。首先定义了一个全局变量a并赋值为1,又定义了一个函数a_add(),函数内的语句a+=1就是出错的根源,虽然我们的初衷是想让全局变量a的值增加1,但从错误提示看,这个语句中的a并不是全局变量,而是局部变量。看来,在函数中读取全局变量的值是没有问题的(在程序1中已经得到了验证),但要在函数中改变全局变量的值是不行的(在程序2的错误提示a+=1中的a 是局部变量,而非全局变量)。
怎样解决这个问题?
程序3:
a=1
def a_add(x):
x+=1
return x
print('a的初值:',a)
a=a_add(a)
print('a现在的值是:',a)
运行结果:
a的初值: 1
a现在的值是: 2
结果的确是正确的,但在函数a_add(x)中没有调用变量a(没有出现变量a)。
程序4:
a=1
def a_add(a):
a+=1
return a
print('a的初值:',a)
a=a_add(a)
print('a现在的值是:',a)
运行结果:
a的初值: 1
a现在的值是: 2
对比程序4和程序3不难发现,其实程序4只是简单的把函数的参数x变成了a,这个a的实质和程序3中的x还是一样的。这进一步证实,函数中的a是局部变量,与主程序的全局变量a有着本质的区别。
程序5:
a=1
def a_add():
global a
a+=1
print('a的初值:',a)
a_add()
print('a现在的值是:',a)
运行结果:
a的初值: 1
a现在的值是: 2
程序5和程序2相比较,仅仅是在函数中添加了一个定义“global a”,此时的局部变量a就可以当做全局变量使用,由于它和全局变量a同名,自然也就不用区分a究竟是全局变量还是局部变量了,在主程序和该函数内都可以访问、修改变量a的值了。
虽然使用global可使变量使用起来非常方便,但也容易引起混淆,故在使用过程中还是谨慎为好。
12.3 函数的递归与嵌套
递归,就是函数调用它自身。递归必须设置停止条件,否则函数将无法终止,形成死循环。
以计算阶乘为例:
def func(n):
if n==1:
return 1
else:
return n*func(n-1) #func( )调用func( )
print(func(5))
运行结果为:120
嵌套,指在函数中调用另外的函数。这是程序中常见的一种结构,在此不再赘述。
匿名函数
Python中可以在参数前加上关键字lambda定义一个匿名函数,这样的函数一般都属于“一次性”的。
例如:
程序1:这是一个常规的函数定义和调用。
def f_add(x,y):
return x+y
print(f_add(2,3))
输出结果:5
程序2:使用lambda定义匿名函数。
f_add=lambda x,y:x+y
print(f_add(2,3))
输出结果:5
从上面的代码可以看出,使用lambda仅仅减少了一行代码。f_add=lambda x,y:x+y中的f_add不是变量名,而是函数名。程序1和程序2的print( )语句中的参数都是一样的——调用函数f_add( )。所以,匿名函数并没有太多的优点。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流