python入门(八) -- 函数

函数类似于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语句时,函数返回None
print(fibonacci(0))

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

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

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

n = 0
s = '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 = 0


def method():
    # python解释器会认为是在方法中重新声明了一个“同名的局部变量”
    n = 1
  • 同样由于赋值语句和变量声明语句没有任何差别,python中有一种“类型可以改变”的假象:
# 看起来是类型改变了,实际上丢弃了原有的对象,新声明了一个对象
num = 123
num = '123'

# 如果Number和String进行运算,则会报错
num = 123+'123'

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

需要注意的有:

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

如上面所说,修改无效:

In [16]: num = 1

In [17]: def fun1():
    ...:     num = 2
    ...:     print(num)
    ...:     

In [18]: fun1()
2

In [19]: num
Out[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]: list
Out[23]: ['a new number', 2, 3, 4, 5]
  • 如果在方法内对全局变量(或上级局部变量)进行修改,或造成“shadowing”,导致无法对全局比变量进行访问,同时抛出错误“UnboundLocalError”。即如果下文中要对某个全局变量进行修改,就不能在这之前访问他。比如:
In [1]: num = 1

In [2]: def fun1():
   ...:     print(num)
   ...:     

In [3]: fun1()
1

In [4]: def fun1():
   ...:     print(num)
   ...:     num = 2
   ...:     

In [5]: fun1()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-5-ed0619e33a22> in <module>()
----> 1 fun1()

<ipython-input-4-c7d39297e45b> 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)
<ipython-input-28-32d138681f85> in <module>()
----> 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 = 5


def 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]: fun
Out[32]: <function __main__.fun1.<locals>.fun2>

In [33]: fun(4)
Out[33]: 20

In [34]: fun1(5)(4)
Out[34]: 20

In [35]: fun2(4)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-35-95b8bef3aee7> in <module>()
----> 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)
<ipython-input-41-a8dcf2873369> in <module>()
----> 1 fun1(5)(4)

<ipython-input-40-23b40a634958> in fun2(y)
      1 def fun1(x):
      2     def fun2(y):
----> 3         x += 5
      4         print("+=实际上相当于先访问,后赋值")
      5         return x*y

UnboundLocalError: local variable 'x' referenced before assignment

In [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+n

f1 = add(42)


def f2(x):
    return x+42

# f1和f2相同
print(f1(0))
print(f2(0))
In [12]: fun = lambda x,y: x+y

In [13]: fun(1,2)
Out[13]: 3

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

pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
# 意为根据元组对的第一个元素排序,即1234
pairs.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)

 

    原文作者:python入门
    原文地址: https://my.oschina.net/pierrecai/blog/896803
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞