Python--函数

函数是对程序逻辑进行结构化或过程化的一种编程方法。其实,说简单一点,就是我们将完成某项功能的运算封装在一个单独的结构内。这样,将代码隔离成易于管理的小块,在实现大的功能时,再调用这些小块即可。很明显,函数的使用使得代码的意图更加清晰,同时,也大大方便了程序调试的过程。

当然,函数的知识并不像列表,字典这些数据类型那样系统,而是比较分散。所以,我在这篇博文中只是写一些需要注意的小点。算是自己的一个备忘。若是有幸帮助到哪位读者,也算是做了一点好事吧。

函数创建

def 语句用来完成对函数的创建,语法是 def function_name(arguments) , 比如

def fun(a):
    return a + 1

fun 为函数名,a为函数的参数。

需要注意的一点是Python中函数的声明和定义是在一起的,不像C++等语言,函数的声明和定义有着明显的区分。

此外,函数的命名需要养成好的习惯,一般命名的原则有两条:

  1. 尽量能清楚地表达函数的功能,而不要害怕名字长。比如我们写判断整数素性的函数,一般叫 isPrime(), 在实现二叉树的前序遍历时的函数一般叫 preorderTraversal()。当然,不是说非要按哪个名字起,而是这个名字一定要起的“有意义”。让别人也能一眼就知道这个函数的作用。
  2. 不要跟一些Python内建的函数,关键字等重名,比如要写一个生成字符串的函数,就尽量不要用 str, string 这样的名字,你可以起名叫 genStr

在做一些大型程序的时候,一般可能不是一个人的工作,所以,为了更好地同别人合作,我们尽量为自己写的函数加上注释或者文档字符串。

def fun(a):
    "add 1 to parameter"
    return a + 1

print(fun.__doc__) # >>> "add 1 to parameter"

上面是一个加文档字符串的例子。文档字符串需放在函数定义中的第一行,负责说明函数,当然,直接给函数加注释也行,但是注释不会像文档字符串这样,能直接输出,令调用者看到。

函数参数

函数名后面的括号里面放的就是函数的参数。我们把在函数定义时写在括号里边的参数称为形参,而调用时带入的参数称为实参。这些都是太基础的东西,不再赘述。这里我主要说两点

1. 默认参数

默认参数的使用为我们提供了极大地方便。函数设计者会对函数中的某些参数设定默认值,这样,调用者即使不显式地给出相应的实参,函数也能顺利完成,给出的默认参数一般情况下能满足大部分的需求。而同时,调用者也可以根据情况调整默认参数,实现对特殊情况的处理。

默认参数的用法是这样:

def my_divide(a, b, precision=2):
    return round(a / b, precision)

print(my_divide(2, 3)) # >>> 0.67
print(my_divide(2, 3, 5)) # >>> 0.66667

这是一个除法函数,函数中,精度 precision 是默认参数,默认为2,也就是说默认输出的商小数点后保留两位。当精度没有给出明确的参数时,就使用默认参数,若给出了,则按带入的实参执行。

2. 参数顺序

正常情况下,实参需要保持和形参一样的顺序。比如还是上面的除法函数,我们调用 my_divide(2, 3)my_divide(3, 2) 所得到的结果肯定是不同的。但是当参数较多的时候,这样的规则就会对调用产生不便,所以,为了不纠结于参数的顺序,在调用函数的时候可以用参数名来区分参数。

还是上面的例子,我们想要让函数计算 a / b,于是,可以这样调用:

def my_divide(a, b, precision=2):
    return round(a / b, precision)

print(my_divide(b=3, a=2)) # >>> 0.67

直接将实参赋值给相应的参数名。这样,即使调用过程中参数的顺序与定义中不同,也不会影响。

前向引用

和其他语言一致,Python不允许在函数定义之前,对函数调用或者引用。但是与其他语言严格的语法规则相比,Python的规则会显得轻松许多。来看个具体例子:

print(f()) # Wrong! 在函数定义之前对函数调用

def f():
    return True

上面这种情况,要特别注意,是错误的,不能因为Python太方便,就太随意了,认为只要函数在一个脚本中,可以任意调用。

但是,这样写却是对的:

def f1():
    return f2()
def f2():
    return True

print(f1()) # >>> True

虽然在定义 f1() 时,调用了当时还未声明(定义)的函数 f2() ,但是那只是对函数的定义,我们并没有真正调用 f2() 。这个过程可以这样理解,先声明了 f1() ,再声明 f2() ,再调用f1() ,此时,解释器在调用 f1() 时会调用 f2() ,而此时 f2() 已经声明了。

同理,写成下面这样就肯定不对了:

def f1():
    return f2()

print(f1()) # Wrong, f2()未声明

def f2():
    return True

变量作用域

代码中,当然会存在变量,且大多时候有不止一个变量。经常会发生的情况是同一个变量名在不同函数,不同模块间穿插使用,令人眼花缭乱。因此,搞清楚变量的作用域问题就显得尤为重要,否则我们的程序必然会出错,且这些错误很难被肉眼直接看出来。

1. 全局变量与局部变量

首先要分清局部变量与全局变量的区别。简单理解,所谓全局变量就是一个脚本中最高级别的变量,如果不将它删除,它会贯穿脚本运行的始终。而局部变量则是随着所在函数存在的。“函数在,局部在,函数亡,局部亡”。也就是说,当局部变量所在的函数被调用时,局部变量创建并在函数内发挥作用,而一旦函数执行完毕,则局部变量也随之释放。看下面的例子:

a = 1
def loc(num):
    b = 0
    if num == 1:
        b = num
    return b

print(loc(a)) # >>> 1
print(a) # >>> 1
print(b) # >>> NameError!

在上面的例子中,a是全局变量,先是被当做实参传递给函数 loc() ,再打印出来,依然存在。但是b就是局部变量了,只存活于函数 loc() 运行的始终,一旦函数运行完毕,b也就销毁,所以再打印出来时,会引发”NameError”异常。

这里需要注意的一点是,一旦局部变量与全局变量发生冲突时,在运行的函数内部,解释器会优先寻找局部变量,找不到时,才去寻找全局变量:

num = 1

def loc1():
    print(num)

def loc2():
    num = 2
    print(num)

loc1() # >>> 1
loc2() # >>> 2

当然,像上面这种函数外变量名与函数内变量名冲突的情况,我们要尽量避免,也就是说,一个脚本当中,甚至是一个项目当中,尽量保证每个变量都是“独一无二”的,尽量不要“串用”。

2. global语句

根据上面的分析,我们知道,一旦全局变量和局部变量发生了冲突,函数运行时会令局部变量“挤”掉全局变量。但是这样一来有个坏处,就是假如现在我设计了一个函数,我要它完成对我所定义的全局变量的更改(只是更改,并不作为函数的返回值返回),在做这种事情时,局部变量的机制就会带来不便

num = 3

def toOne():
    num = 1
    print(num)

toOne() # >>> 1
print(num) # >>> 3

这里,函数 toOne() 的作用是将任意的对象num(全局变量)转换成整数1,并将整数1打印出来。我们看到,因为解释器会先寻找局部变量,所以这里的意思就是在函数内部新建了一个引用为num的整数1对象,而并没有影响全局变量。

那么怎样才能实现我刚才说的,令函数更改全局变量呢?可以用global 语句,在函数定义的一开始声明此变量贯穿始终,只要碰到该引用,就知道是那个全局变量,而不是新创建局部变量。算是给全局变量发了一张“身份证”,到哪都没人能冒充了。

还是刚才的例子

num = 3

def toOne():
    global num
    num = 1
    print(num)

toOne() # >>> 1
print(num) # >>> 1

我们看到,当函数运行结束之后,num所指向的对象变成了整数1.

你可能感兴趣的:(Python,Python--基础)