博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
python入门(八) -- 函数
阅读量:6079 次
发布时间:2019-06-20

本文共 8466 字,大约阅读时间需要 28 分钟。

hot3.png

函数类似于Java中的方法,是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

1、函数定义

函数定义的规则:

def 函数名(参数列表):    [文档字符串,可选]    函数体

可以用return [表达式]结束函数,也可以不用,依情况而定。如果不使用return返回值,实际上函数也会返回一个值None

例子:

# 生成指定边界的斐波那契数列def fibonacci(n):    # 文档字符串类似于Java中的文档注释,可以借助一些工具生成API文档    """文档字符串:Print a Fibonacci series up to n."""    a, b = 0, 1    result = []    while a < n:        result.append(a)        a, b = b, a+b    if len(result) != 0:        return result    else:        pass# 在函数定义结束之后,最好空一行print(fibonacci(2000))# 当没有调用pass语句时,函数返回Noneprint(fibonacci(0))

2、全局变量、局部变量和参数调用

python中的函数和其他语言的函数/方法一样:

  • 函数内局部变量在每次函数调用时生成,函数结束时销毁
  • 由于python中没有Java中类似的“无引用的基本类型”,所以一直传递进函数的都是“引用”:形参在初始时,指向和传入的实参相同的地址。“更改形参指向的地址”并不会影响到实参,但是“更改形参指向的地址的内容”则会影响实参。
def method(num,str,tuple,list):    # "="实际上意味着更改了类型的指向    num += 1    str = 'new str'    tuple = ('a', 'b')    list[0] = 1n = 0s = 'str't = ('a', 'b', 'c', ['this', 'is', 'a', 'list'])l = [123, 345, 567]method(n, s, t, l)# 只有l被改变print(n)print(s)print(t)print(l)

和Java中不同:

  • python中的函数无法对全局参数进行赋值(这是由于和Java中不同,python的赋值语句和变量申明语句并没有任何差别,不像Java声明变量时需要指定类型),对外部的参数进行赋值必须通过global或者nonlocal实现
# 全局变量n = 0def method():    # python解释器会认为是在方法中重新声明了一个“同名的局部变量”    n = 1
  • 同样由于赋值语句和变量声明语句没有任何差别,python中有一种“类型可以改变”的假象:
# 看起来是类型改变了,实际上丢弃了原有的对象,新声明了一个对象num = 123num = '123'# 如果Number和String进行运算,则会报错num = 123+'123'

2.1、全局变量、局部变量详解

需要注意的有:

  • 方法内部只能访问全局变量,无法修改全局变量。同样,嵌套的方法也只能访问其外层方法的变量,而无法修改。
  • “无法修改”仅限于无法修改值,实际上对于“引用”,仍然可以修改。例如:

如上面所说,修改无效:

In [16]: num = 1In [17]: def fun1():    ...:     num = 2    ...:     print(num)    ...:     In [18]: fun1()2In [19]: numOut[19]: 1

但是对引用对象,不改变引用的情况下,可以改变值:

In [20]: list = [1,2,3,4,5]In [21]: def fun1():    ...:     list[0] = 'a new number'    ...:     print(list)    ...:     In [22]: fun1()['a new number', 2, 3, 4, 5]In [23]: listOut[23]: ['a new number', 2, 3, 4, 5]
  • 如果在方法内对全局变量(或上级局部变量)进行修改,或造成“shadowing”,导致无法对全局比变量进行访问,同时抛出错误“UnboundLocalError”。即如果下文中要对某个全局变量进行修改,就不能在这之前访问他。比如:
In [1]: num = 1In [2]: def fun1():   ...:     print(num)   ...:     In [3]: fun1()1In [4]: def fun1():   ...:     print(num)   ...:     num = 2   ...:     In [5]: fun1()---------------------------------------------------------------------------UnboundLocalError                         Traceback (most recent call last)
in
()----> 1 fun1()
in fun1() 1 def fun1():----> 2 print(num) 3 num = 2 4 UnboundLocalError: local variable 'num' referenced before assignment
  • 局部变量不能在函数外部被访问、修改。这个原则同样适用于函数内部的“函数”,因为和C语言类似,python中的函数其实也是一种特殊的对象(这点和Java不同)。例如:
In [26]: def fun1():    ...:     def fun2():    ...:         print("定义在fun1()内部的fun2()只能在fun1()内部调用")    ...:     fun2()    ...:     In [27]: fun1()定义在fun1()内部的fun2()只能在fun1()内部调用In [28]: fun2()---------------------------------------------------------------------------NameError                                 Traceback (most recent call last)
in
()----> 1 fun2()NameError: name 'fun2' is not defined
  • 想要访问内部的函数,可以通过“闭包”来实现,见下方闭包的说明。
  • 可以使用Global和nonlocal强行在内部作用域中对外部变量进行修改,具体见:https://my.oschina.net/pierrecai/blog/906419

3、深入函数定义

注意,其它高级语言常见的函数重载,Python 是没有的,这是因为 Python 有默认参数这个功能,函数重载 的功能大都可以使用默认参数达到。

3.1、函数默认参数

  • 在定义参数时,可以给参数设定默认值
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):    """可以设定默认参数"""    while True:        ok = input(prompt)        if ok in ('y', 'ye', 'yes'):            return True        if ok in ('n', 'no'):            return False        retries -= 1        if retries < 0:            raise OSError('uncooperative user')        print(complaint)# 函数调用时,没有默认值的参数必须给出,但是有默认值的参数可以不给出ask_ok('Do you really want to quit?')ask_ok('Do you really want to quit?',2)ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

需要注意的是:

  • 函数的默认参数只被赋值一次,这在参数是“不可变类”和普通类的情况下会导致不同的结果:
i = 5def method(num=i):    print(num)i = 6# 由于Number为不可变类,每次print出的num一样method()method()
def method(list1=[]):    list1.append('a')    print(list1)# 由于默认参数只赋值一次,每次方法调用都操作的是同一个List,效果会叠加method()method()method()# 最后一个输出['a', 'a', 'a', 'a']method()
  • 如果不想让普通类默认值在后续调用中累积,可以使用下面的方法:
def method(list1=None):    if list1 is None:        list1 = []    list1.append('a')    print(list1)

3.2、关键字参数

python中在调用函数时,可以用“keyword = value”的形式进行:

def method(var1, var2='var2', var3='var3', var4=['var', '4']):    print(var1)    print(var2)    print(var3)    print(var4)method(111)method(var1='asd')method(var1=123, var2='zxc')method(111, '123', 123, '123')method(111, var2='123')method(var1='asd',var2=123)

需要注意的是:

  • 没有默认值的参数必须提供,有默认值的参数可以不提供
  • 如果不使用keyword,参数会按顺序读取
  • 如果使用keyword,keyword必须和形参名相匹配,此时可以不按顺序
  • 如果混合使用,非keyword参数必须在keyword之前
  • 任何参数都不可重复赋值

3.2.1、强制关键字参数

我们也能将函数的参数标记为只允许使用关键字参数。用户调用函数时将只能对每一个参数使用相应的关键字参数。

设定方法是将方法的第一个参数设置为*,后续全部使用关键字,比如:

def method(*,name="User",password="asdf"):    print(name,password)# 这样可以正常调用method(name='alibaba')# 这样则会报错method('alibaba','asadf')

3.3、可变参数列表

在python中也像Java一样可以设置可变的参数列表。

  • 在Java中使用"参数类型... 参数名"表示可变的参数列表,并以“数组”的形式接受传入的数量不定的参数:
/** * @param strings   以数组的形式接受可变数量的参数 */public void method(String... strings){    for (int i = 0; i < strings.length; i++) {        System.out.println(strings);    }}
  • 在python中,同样可以用“数组”(python中称为元组)来接收数量不定的参数,形式为“*args”
  • 在python中,还可以使用“字典”(dictionary)来接收,形式为“**args”,但是不可以用在"*args"之前
def method(arg, *args, **keywords):    print(arg)    for arg in args:        print(arg)    for keyword in keywords:        print(keyword, ":", keywords[keyword])method("arg",       "arg1", "arg2", "arg3",       keyword1='arg4', keyword2='arg5')

注意:

  • 如果要定义可变列表,**args不能用在*args之前,普通参数不能在*args或者**args之后。
  • 可以在*args或者**args之后使用关键字参数

3.4、参数列表的分拆

和3.3中的相反,如果要传递的参数已经是元组或者字典,但是调用的函数却要求把这些拆开来传入,这是同样可以用“*”和“**”把元组和字典拆开:

# 正常调用l = list(range(3, 6))print(l)# 拆分元组list1 = list(range(*[3, 6]))print(list1)def method(var1, var2, var3, var4):    print(var1)    print(var2)    print(var3)    print(var4)# 正常调用method(1, 2, 3, 4)# 拆分字典method(**{"var1":1,"var2":2,"var3":3,"var4":4})

3.5、闭包

所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

一般展示为下面的形式:

  • 函数b嵌套在函数a内部
  • 函数a返回函数b
  • 函数b可以对函数a的参数进行修改
In [30]: def fun1(x):    ...:     def fun2(y):    ...:         return x*y    ...:     return fun2    ...: In [31]: fun = fun1(5)In [32]: funOut[32]: 
.fun2>In [33]: fun(4)Out[33]: 20In [34]: fun1(5)(4)Out[34]: 20In [35]: fun2(4)---------------------------------------------------------------------------NameError Traceback (most recent call last)
in
()----> 1 fun2(4)NameError: name 'fun2' is not defined
  • 这样实现了前两点,但是如果要实现内部函数对外部函数的参数进行修改的话,还是需要引入nonlocal,如:
In [40]: def fun1(x):    ...:     def fun2(y):    ...:         x += 5    ...:         print("+=实际上相当于先访问,后赋值")    ...:         return x*y    ...:     return fun2    ...: In [41]: fun1(5)(4)---------------------------------------------------------------------------UnboundLocalError                         Traceback (most recent call last)
in
()----> 1 fun1(5)(4)
in fun2(y) 1 def fun1(x): 2 def fun2(y):----> 3 x += 5 4 print("+=实际上相当于先访问,后赋值") 5 return x*yUnboundLocalError: local variable 'x' referenced before assignmentIn [42]: def fun1(x): ...: def fun2(y): ...: nonlocal x ...: x += 5 ...: print("+=实际上相当于先访问,后赋值") ...: return x*y ...: return fun2 ...: In [43]: fun1(5)(4)+=实际上相当于先访问,后赋值Out[43]: 40

3.6、Lambda形式

和Java8中新增的Lambda特性相同,lambda表达式实际上是一个函数指针,常用于:

  • 替代只需要调用一两次的函数
  • 代表短小的匿名函数

Java中Lambda表达式的语法:

变量列表 -> 函数体

对应的python中的Lambda表达式的语法:

lambda 变量列表: 函数体

例如:

def add(n):    return lambda x: x+nf1 = add(42)def f2(x):    return x+42# f1和f2相同print(f1(0))print(f2(0))
In [12]: fun = lambda x,y: x+yIn [13]: fun(1,2)Out[13]: 3

lambda表达式还可以作为参数传递:

pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]# 意为根据元组对的第一个元素排序,即1234pairs.sort(key=lambda pair: pair[0])print(pairs)pairs.sort(key=lambda pair: pair[1])

注意:

  • Java中Lambda表达式的参数列表需要用括号括起来,而python中不用

3.7、函数注解

函数注解以字典的形式存储在函数的__annotations__属性中,对函数的其他部分没有任何影响。

函数注解有三种:

  • 参数注解:定义在参数的":"后面
  • 返回注解:定义在参数列表的"->"后面
def method(var1: "参数1注解", var2: int='参数2初始值',var3:'参数3注解'='参数3初始值') -> "方法注解":    print(method.__annotations__)    print(var1)    print(var2)    print(var3)method(1)

注意:

  • 如果参数有初始值,注解形式为(:后面跟注解,=后面跟初始值)
参数名:注解=初始值
  • 注解不只可以用字符串,还可以用int等,表示类型注解

和注解不同的是,函数字符串保存在__doc__属性中:

def method1():    """这是一个文档字符串"""    pass# 直接通过调用__doc__属性print(method1.__doc__)# 通过help方法help(method1)

 

转载于:https://my.oschina.net/pierrecai/blog/896803

你可能感兴趣的文章
从与星瑞格软件的合作看浪潮深化主机生态布局
查看>>
中国人工智能学会通讯——当巧妇遇到“大米”——机器翻译启示录
查看>>
享未来就现在 聚VR一体机春天已经到来
查看>>
针对Android的Pegasus恶意软件版本和针对iOS的有什么不同?
查看>>
政府拥抱大数据 治理迎来新格局
查看>>
零件检测如何保证出色的质量 光切传感器成为理想替代方案
查看>>
“大声bb”–攻击Linux和FreeBSD的恶意软件
查看>>
绿盟科技发布2014互联网金融安全报告
查看>>
《计算机视觉:模型、学习和推理》一2.7 期望
查看>>
立志让国内用户不再依赖国外DLP技术 天空卫士发布UCS新品
查看>>
浪潮M5设计解读:打破通用均衡,聚焦场景极致
查看>>
使用Apache Spark和MySQL打造强大的数据分析
查看>>
2016年全球10大数据中心提供商概览
查看>>
这就是我喜欢 Bootstrap的五个原因
查看>>
主流服务器虚拟化产品中的优势与短板概述
查看>>
3.5万个MongoDB数据库的约680TB数据存被盗风险!
查看>>
【原创】RabbitMQ 之 HTTP server 插件(翻译)
查看>>
php跨平台总结 常用预定义常量
查看>>
linux 下 apache启动、停止、重启命令
查看>>
阿里云网络系列之经典网络和专有网络
查看>>