Python学习与实战

Python 基础入门 4--函数

2020-05-25  本文已影响0人  材才才

Python 基础入门前三篇:

第四篇内容,这次介绍下函数的基本用法,包括函数的定义、参数的类型、匿名函数、变量作用域以及从模块导入函数的方法,目录如下所示:

image

4. 函数

定义:函数是组织好的,可重复使用,用于实现单一或者相关联功能的代码段。

在 Python 中既有内建函数,比如 print()sum() ,也可以用户自定义函数。

4.1 定义函数

自定义一个函数需要遵守一些规则:

一个函数的一般格式如下:

def 函数名(参数列表):
    函数体

默认情况下,参数值和参数名称是按照函数声明中定义的顺序匹配的

简单的定义和调用函数的例子如下所示:

def hello():
    print("Hello, world!")
# 计算面积的函数
def area(width, height):
    return width * height

hello()
width = 2
height = 3
print('width={}, height={}, area={}'.format(width, height, area(width, height)))

输出结果:

Hello, world!
width=2, height=3, area=6

上述例子定义了两个函数,第一个是没有参数的 hello(), 而第二个函数定义了两个参数。

4.2 参数传递

在 python 中,类型属于对象,变量是没有类型的

a = [1, 2, 3]
a = "abc"

上述代码中,[1,2,3] 是 List 类型,"abc" 是 String 类型,但变量 a 是没有类型的,它仅仅是一个对象的引用(一个指针),可以指向 List 类型,也可以指向 String 类型。

可更改(mutable)与不可更改(immutable)对象

python 中,strings, tuples, numbers 是不可更改对象,而 list, dict 是可修改的对象。

在之前的第二节介绍变量类型中,介绍了如何判断数据类型是否可变,介绍了两种方法:

这里用 id() 的方法来做一个简单的例子,代码如下:

# 判断类型是否可变
a = 5
print('a id:{}, val={}'.format(id(a), a))
a = 3
print('a id:{}, val={}'.format(id(a), a))

la = [1, 2, 3]
print('la id:{}, val={}'.format(id(la), la))
la[2] = 5
print('la id:{}, val={}'.format(id(la), la))

输出结果,可以发现变量 aid 是发生了变化,而列表变量 laid 没有变化,这证明了 a 的类型 int 是不可变的,而 list 是可变类型。

a id:1831338608, val=5
a id:1831338544, val=3

la id:1805167229448, val=[1, 2, 3]
la id:1805167229448, val=[1, 2, 5]

然后在 Python 中进行函数参数传递的时候,根据传递的变量是否可变,也需要分开讨论:

当然了,Python 中一切都是对象,这里应该说是传递可变对象和不可变对象,而不是引用传递和值传递,但必须注意应该慎重选择传递可变对象的参数,下面会分别给出传递两种对象的例子。

首先是传递不可变对象的实例:

# 传递不可变对象的实例
def change_int(a):
    a = 10

b = 2
print('origin b=', b)
change_int(b)
print('after call function change_int(), b=', b)

输出结果,传递的变量 b 并没有发生改变。

origin b= 2
after call function change_int(), b= 2

接着,传递可变对象的例子:

# 传递可变对象的实例
def chang_list(la):
    """
    修改传入的列表参数
    :param la:
    :return:
    """
    la.append([2, 3])
    print('函数内部: ', la)
    return


la = [10, 30]
print('调用函数前, la=', la)
chang_list(la)
print('函数外取值, la=', la)

输出结果,可以看到在函数内部修改列表后,也会影响在函数外部的变量的数值。

调用函数前, la= [10, 30]
函数内部:  [10, 30, [2, 3]]
函数外取值, la= [10, 30, [2, 3]]

当然,这里如果依然希望传递列表给函数,但又不希望修改列表本来的数值,可以采用传递列表的副本给函数,这样函数的修改只会影响副本而不会影响原件,最简单实现就是切片 [:] ,例子如下:

# 不修改 lb 数值的办法,传递副本
lb = [13, 21]
print('调用函数前, lb=', lb)
chang_list(lb[:])
print('传递 la 的副本给函数 change_list, lb=', lb)

输出结果:

调用函数前, lb= [13, 21]
函数内部:  [13, 21, [2, 3]]
传递 lb 的副本给函数 change_list, lb= [13, 21]

4.3 参数

参数的类型主要分为以下四种类型:

位置参数

位置参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。其定义如下,arg 就是位置参数,docstring 是函数的说明,一般说明函数作用,每个参数的含义和类型,返回类型等;statement 表示函数内容。

def function_name(arg):
    """docstring"""
    statement

下面是一个例子,包括一个正确调用例子,和两个错误示例

# 位置参数
def print_str(str1, n):
    """
    打印输入的字符串 n 次
    :param str1: 打印的字符串内容
    :param n: 打印的次数
    :return:
    """
    for i in range(n):
        print(str1)


strs = 'python '
n = 3
# 正确调用
print_str(strs, n)
# 错误例子1
print_str()
# 错误例子2
print_str(n, strs)

对于正确例子,输出:

python python python 

错误例子1--print_str(),也就是没有传入任何参数,返回错误:

TypeError: print_str() missing 2 required positional arguments: 'str1' and 'n'

错误例子1--print_str(n, strs),也就是传递参数顺序错误,返回错误:

TypeError: 'str' object cannot be interpreted as an integer
默认参数

默认参数定义如下,其中 arg2 就是表示默认参数,它是在定义函数的时候事先赋予一个默认数值,调用函数的时候可以不需要传值给默认参数。

def function_name(arg1, arg2=v):
    """docstring"""
    statement

代码例子如下:

# 默认参数
def print_info(name, age=18):
    '''
    打印信息
    :param name:
    :param age:
    :return:
    '''
    print('name: ', name)
    print('age: ', age)

print_info('jack')
print_info('robin', age=30)

输出结果:

name:  jack
age:  18
name:  robin
age:  30

注意:默认参数必须放在位置参数的后面,否则程序会报错。

可变参数

可变参数定义如下,其中 arg3 就是表示可变参数,顾名思义就是输入参数的数量可以是从 0 到任意多个,它们会自动组装为元组

def function_name(arg1, arg2=v, *arg3):
    """docstring"""
    statement

这里是一个使用可变参数的实例,代码如下:

# 可变参数
def print_info2(name, age=18, height=178, *args):
    '''
    打印信息函数2
    :param name:
    :param age:
    :param args:
    :return:
    '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)
    print(args)
    for language in args:
        print('language: ', language)

print_info2('robin', 20, 180, 'c', 'javascript')
languages = ('python', 'java', 'c++', 'go', 'php')
print_info2('jack', 30, 175, *languages)

输出结果:

name:  robin
age:  20
height:  180
('c', 'javascript')
language:  c
language:  javascript

name:  jack
age:  30
height:  175
('python', 'java', 'c++', 'go', 'php')
language:  python
language:  java
language:  c++
language:  go
language:  php

这里需要注意几点:

  1. 首先如果要使用可变参数,那么传递参数的时候,默认参数应该如上述例子传递,不能如print_info2('robin', age=20, height=180, 'c', 'javascript'),这种带有参数名字的传递是会出错的;
  2. 可变参数有两种形式传递:
关键字参数

关键字参数定义如下,其中 arg4 就是表示关键字参数,关键字参数其实和可变参数类似,也是可以传入 0 个到任意多个,不同的是会自动组装为一个字典,并且是参数前 ** 符号。

def function_name(arg1, arg2=v, *arg3, **arg4):
    """docstring"""
    statement

一个实例如下:

def print_info3(name, age=18, height=178, *args, **kwargs):
    '''
    打印信息函数3,带有关键字参数
    :param name:
    :param age:
    :param height:
    :param args:
    :param kwargs:
    :return:
    '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)

    for language in args:
        print('language: ', language)
    print('keyword: ', kwargs)


# 不传入关键字参数的情况
print_info3('robin', 20, 180, 'c', 'javascript')

输出结果如下:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {}

传入任意数量关键字参数的情况:

# 传入任意关键字参数
print_info3('robin', 20, 180, 'c', 'javascript', birth='2000/02/02')
print_info3('robin', 20, 180, 'c', 'javascript', birth='2000/02/02', weight=125)

结果如下:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02'}

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02', 'weight': 125}

第二种传递关键字参数方法--字典:

# 用字典传入关键字参数
keys = {'birth': '2000/02/02', 'weight': 125, 'province': 'Beijing'}
print_info3('robin', 20, 180, 'c', 'javascript', **keys)

输出结果:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02', 'province': 'Beijing', 'weight': 125}

所以,同样和可变参数相似,也是两种传递方式:

命名关键字参数

命名关键字参数定义如下,其中 *, nkw 表示的就是命名关键字参数,它是用户想要输入的关键字参数名称,定义方式就是在 nkw 前面添加 *, ,这个参数的作用主要是限制调用者可以传递的参数名

def function_name(arg1, arg2=v, *arg3, *,nkw, **arg4):
    """docstring"""
    statement

一个实例如下:

# 命名关键字参数
def print_info4(name, age=18, height=178, *, weight, **kwargs):
    '''
    打印信息函数4,加入命名关键字参数
    :param name:
    :param age:
    :param height:
    :param weight:
    :param kwargs:
    :return:
    '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)

    print('keyword: ', kwargs)
    print('weight: ', weight)

print_info4('robin', 20, 180, birth='2000/02/02', weight=125)

输出结果如下:

name:  robin
age:  20
height:  180
keyword:  {'birth': '2000/02/02'}
weight:  125

这里需要注意:

参数组合

通过上述的介绍,Python 的函数参数分为 5 种,位置参数、默认参数、可变参数、关键字参数以及命名关键字参数,而介绍命名关键字参数的时候,可以知道它和可变参数是互斥的,是不能同时出现的,因此这些参数可以支持以下两种组合及其子集组合:

一般情况下,其实只需要位置参数和默认参数即可,通常并不需要过多的组合参数,否则函数会很难懂。

4.4 匿名函数

上述介绍的函数都属于同一种函数,即用 def 关键字开头的正规函数,Python 还有另一种类型的函数,用 lambda 关键字开头的匿名函数

它的定义如下,首先是关键字 lambda ,接着是函数参数 argument_list,其参数类型和正规函数可用的一样,位置参数、默认参数、关键字参数等,然后是冒号 :,最后是函数表达式 expression ,也就是函数实现的功能部分。

lambda argument_list: expression

一个实例如下:

# 匿名函数
sum = lambda x, y: x + y

print('sum(1,3)=', sum(1, 3))

输出结果:

sum(1,3)= 4

4.5 变量作用域

Python 中变量是有作用域的,它决定了哪部分程序可以访问哪个特定的变量,作用域也相当于是变量的访问权限,一共有四种作用域,分别是:

寻找的规则是 L->E->G->B ,也就是优先在局部寻找,然后是局部外的局部(比如闭包),接着再去全局,最后才是内置中寻找。

下面是简单介绍这几个作用域的例子,除内置作用域:

g_count = 0  # 全局作用域
def outer():
    o_count = 1  # 闭包函数外的函数中
    # 闭包函数 inner()
    def inner():
        i_count = 2  # 局部作用域

内置作用域是通过一个名为 builtin 的标准模块来实现的,但这个变量名本身没有放入内置作用域,需要导入这个文件才可以使用它,使用代码如下,可以查看预定义了哪些变量:

import builtins
print(dir(builtins))

输出的预定义变量如下:

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

注意:只有模块(module),类(class)以及函数(def, lambda)才会引入新的作用域,其他代码块(比如 if/elif/else、try/except、for/while)是不会引入新的作用域,在这些代码块内定义的变量,外部也可以使用。

下面是两个例子,一个在函数中新定义变量,另一个在 if 语句定义的变量,在外部分别调用的结果:

g_count = 0  # 全局作用域
def outer():
    o_count = 1  # 闭包函数外的函数中
    # 闭包函数 inner()
    def inner():
        i_count = 2  # 局部作用域
        
if 1:
    sa = 2
else:
    sa = 3
print('sa=', sa)
print('o_count=', o_count)

输出结果,对于在 if 语句定义的变量 sa 是可以正常访问的,但是函数中定义的变量 o_count 会报命名错误 NameError ,提示该变量没有定义。

sa= 2
NameError: name 'o_count' is not defined
全局变量和局部变量

全局变量和局部变量的区别主要在于定义的位置是在函数内部还是外部,也就是在函数内部定义的是局部变量,在函数外部定义的是全局变量。

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:

# 局部变量和全局变量
total = 3  # 全局变量

def sum_nums(arg1, arg2):
    total = arg1 + arg2  # total在这里是局部变量.
    print("函数内是局部变量 : ", total)
    return total


# 调用 sum_nums 函数
sum_nums(10, 20)
print("函数外是全局变量 : ", total)

输出结果:

函数内是局部变量 :  30
函数外是全局变量 :  3
global 和 nonlocal 关键字

如果在内部作用域想修改外部作用域的变量,比如函数内部修改一个全局变量,那就需要用到关键字 globalnonlocal

这是一个修改全局变量的例子:

# 函数内部修改全局变量
a = 1

def print_a():
    global a
    print('全局变量 a=', a)
    a = 3
    print('修改全局变量 a=', a)


print_a()
print('调用函数 print_a() 后, a=', a)

输出结果:

全局变量 a= 1
修改全局变量 a= 3
调用函数 print_a() 后, a= 3

而如果需要修改嵌套作用域,也就是闭包作用域,外部并非全局作用域,则需要用关键字 nonlocal ,例子如下:

# 修改闭包作用域中的变量
def outer():
    num = 10

    def inner():
        nonlocal num  # nonlocal关键字声明
        num = 100
        print('闭包函数中 num=', num)

    inner()
    print('调用函数 inner() 后, num=',num)


outer()

输出结果:

闭包函数中 num= 100
调用函数 inner() 后, num= 100

4.6 从模块中导入函数

一般我们会需要导入一些标准库的函数,比如 ossys ,也有时候是自己写好的一个代码文件,需要在另一个代码文件中导入使用,导入的方式有以下几种形式:

# 导入整个模块
import module_name
# 然后调用特定函数
module_name.func1()

# 导入特定函数
from module_name import func1, func2

# 采用 as 给函数或者模块指定别名
import module_name as mn
from module_name import func1 as f1

# * 表示导入模块中所有函数
from module_name import *

上述几种形式都是按照实际需求来使用,但最后一种方式并不推荐,原因主要是 Python 中可能存在很多相同名称的变量和函数,这种方式可能会覆盖相同名称的变量和函数。最好的导入方式还是导入特定的函数,或者就是导入整个模块,然后用句点表示法调用函数,即 module_name.func1()


参考


小结

本文主要是简单介绍了 Python 函数的基础内容,正规函数和匿名函数的定义和用法,参数的5种类型和使用,当然函数还有更多内容,比如高阶函数 map, filter, reduce,还有本文中间提过的闭包函数等,这部分计划放到后续进阶中介绍。

此外,本文的代码都上传到我的 github 上了:

https://github.com/ccc013/Python_Notes/blob/master/Practise/function_example.py

欢迎关注我的微信公众号--算法猿的成长,或者扫描下方的二维码,大家一起交流,学习和进步!

qrcode_0601.png
上一篇下一篇

猜你喜欢

热点阅读