Nemo
Nemo
笔记:自定义函数
笔记:自定义函数

提醒
整理之前的课程笔记,极客时间:Python核心技术与实战,不错的课程。

函数基础

def my_func(message):
    print('Got a message: {}'.format(message))

# 调用函数 my_func()
my_func('Hello World')
# 输出
Got a message: Hello World
  • def 是函数的声明;
  • my_func 是函数的名称;
  • 括号里面的 message 则是函数的参数;
  • print 那行则是函数的主体部分,可以执行相应的语句;
  • 在函数最后,你可以返回调用结果(returnyield),也可以不返回。

函数结构可以总结为:

def name(param1, param2, ..., paramN):
    statements
    return/yield value # optional

Python和其他需要编译的语言(比如 C 语言)不一样的是,def 是可执行语句,这意味着函数直到被调用前,都是不存在的。当程序调用函数时,def 语句才会创建一个新的函数对象,并赋予其名字。

在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为 def 是可执行语句,函数在调用之前都不存在,只需保证调用时,所需的函数都已经声明定义:

def my_func(message):
    my_sub_func(message) # 调用my_sub_func()在其声明之前不影响程序执行

def my_sub_func(message):
    print('Got a message: {}'.format(message))

my_func('hello world')

# 输出
Got a message: hello world

Python 函数的另一大特性,是 Python 支持函数的嵌套。所谓的函数嵌套,就是指函数里面又有函数,比如:

def f1():
    print('hello')
    def f2():
        print('world')
    f2()
f1()

# 输出
hello
world

函数的嵌套,主要有下面两个方面的作用:

  • 第一,函数的嵌套能够保证内部函数的隐私内部函数只能被外部函数所调用和访问,不会暴露在全局作用域,因此,如果函数内部有一些隐私数据(比如数据库的用户、密码等),不想暴露在外,那就可以使用函数的的嵌套,将其封装在内部函数中,只通过外部函数来访问。比如:
def connect_DB():
    def get_DB_configuration():
        ...
        return host, username, password
    conn = connector.connect(get_DB_configuration())
    return conn

这里的函数 get_DB_configuration,便是内部函数,它无法在 connect_DB() 函数以外被单独调用。也就是说,下面这样的外部直接调用是错误的:

get_DB_configuration()

# 输出
NameError: name 'get_DB_configuration' is not defined

只能通过调用外部函数 connect_DB() 来访问它,这样一来,程序的安全性便有了很大的提高。

  • 第二,合理的使用函数嵌套,能够提高程序的运行效率:
def factorial(input):
    # validation check
    if not isinstance(input, int):
        raise Exception('input must be an integer.')
    if input < 0:
        raise Exception('input must be greater or equal to 0' )
    ...

    def inner_factorial(input):
        if input <= 1:
            return 1
        return input * inner_factorial(input-1)
    return inner_factorial(input)


print(factorial(5))

这里,使用递归的方式计算一个数的阶乘。因为在计算之前,需要检查输入是否合法,所以写成了函数嵌套的形式,这样一来,输入是否合法就只用检查一次。而如果不使用函数嵌套,那么每调用一次递归便会检查一次,这是没有必要的,也会降低程序的运行效率。

函数变量作用域

如果变量是在函数内部定义的,就称为局部变量,只在函数内部有效。一旦函数执行完毕,局部变量就会被回收,无法访问

def read_text_from_file(file_path):
    with open(file_path) as file:
        ...

在函数内部定义了 file 这个变量,这个变量只在read_text_from_file这个函数里有效,在函数外部则无法访问。

全局变量则是定义在整个文件层次上的:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    if value < MIN_VALUE or value > MAX_VALUE:
        raise Exception('validation check fails')

这里的 MIN_VALUEMAX_VALUE 就是全局变量,可以在文件内的任何地方被访问,当然在函数内部也是可以的。不过,不能在函数内部随意改变全局变量的值。下面的写法就是错误的:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    ...
    MIN_VALUE += 1
    ...
validation_check(5)

# 如果运行这段代码,程序便会报错:

UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment

这是因为,Python 的解释器会默认函数内部的变量为局部变量,但是又发现局部变量 MIN_VALUE并没有声明,因此就无法执行相关操作。所以,如果一定要在函数内部改变全局变量的值,就必须加上 global 这个声明

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    global MIN_VALUE
    ...
    MIN_VALUE += 1
    ...
validation_check(5)

这里的 global关键字,并不表示重新创建了一个全局变量 MIN_VALUE,而是告诉 Python 解释器,函数内部的变量MIN_VALUE,就是之前定义的全局变量,并不是新的全局变量,也不是局部变量。这样,程序就可以在函数内部访问全局变量,并修改它的值了。

如果遇到函数内部局部变量和全局变量同名的情况,那么在函数内部,局部变量会覆盖全局变量

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    MIN_VALUE = 3
    ...

在函数 validation_check()内部,定义了和全局变量同名的局部变量MIN_VALUE,那么MIN_VALUE在函数内部的值,就应该是 3 而不是 1 了。


类似的,对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上 nonlocal这个关键字

def outer():
    x = "local"
    def inner():
        nonlocal x # nonlocal关键字表示这里的x就是外部函数outer定义的变量x
        x = 'nonlocal'
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: nonlocal

如果不加上nonlocal这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量

def outer():
    x = "local"
    def inner():
        x = 'nonlocal' # 这里的x是inner这个函数的局部变量
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: local

闭包

外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。

计算一个数的 n 次幂:

def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是exponent_of函数

square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

print(square(2))  # 计算2的平方
print(cube(2)) # 计算2的立方
# 输出
4 # 2^2
8 # 2^3

这里外部函数nth_power() 返回值,是函数exponent_of(),而不是一个具体的数值。需要注意的是,在执行完square = nth_power(2)cube = nth_power(3)后,外部函数 nth_power()的参数exponent,仍然会被内部函数exponent_of()记住。这样,之后调用 square(2)或者 cube(2)时,程序就能顺利地输出结果,而不会报错说参数exponent没有定义了。

如果不使用闭包:

def nth_power_rewrite(base, exponent):
    return base ** exponent

对比一下:

# 不适用闭包
res1 = nth_power_rewrite(base1, 2)
res2 = nth_power_rewrite(base2, 2)
res3 = nth_power_rewrite(base3, 2)
...

# 使用闭包
square = nth_power(2)
res1 = square(base1)
res2 = square(base2)
res3 = square(base3)
...

总结

  1. Python 中函数的参数可以接受任意的数据类型,使用起来需要注意,必要时请在函数开头加入数据类型的检查;
  2. 和其他语言不同,Python 中函数的参数可以设定默认值;
  3. 嵌套函数的使用,能保证数据的隐私性,提高程序运行效率;
  4. 合理地使用闭包,则可以简化程序的复杂度,提高可读性。
赞赏
Nemo版权所有丨如未注明,均为原创丨本网站采用BY-NC-SA协议进行授权,转载请注明转自:https://kanghaov.com/644.html

kanghaov

文章作者

推荐文章

发表评论

textsms
account_circle
email

Nemo

笔记:自定义函数
 提醒整理之前的课程笔记,极客时间:Python核心技术与实战,不错的课程。 函数基础 def my_func(message): print('Got a message: {}'.format(message)) # 调用函数 my_f…
扫描二维码继续阅读
2020-08-06