Python基础系列之--函数【9】

目标

  1. 掌握 Python 函数–定义、参数类型
  2. 掌握 Python 函数–变量作用域

正确理解 Python 函数,能够更好地理解 Python 装饰器、匿名函数(lambda)、函数式编程等高阶技术。

文章较长,整体内容包含如下思维导图

概述

第一部分 重新认识函数

1. 定义

Python 中函数的定义如下:

def 函数名(参数):
# 内部代码
return 表达式

注意点:

  1. 函数代码块以def关键词开头,一个空格之后接函数标识符名称圆括号(),再接个冒号
  2. 圆括号中间存放参数
  3. 使用 return 结束函数。默认返回 None。
>>> def sum(a, b):
... return a + b
...
>>> sum
# 函数地址
<function sum at 0x10af4f158>
## 函数方法
>>> dir(sum)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> sum.__name__
'sum'
>>> sum.__module__
'__main__'
>>>

2 调用函数

使用函数名后跟圆括号的方式调用函数。
调用的同时要根据函数的定义体,提供相应个数和类型的参数,每个参数之间用逗号分隔。

3. 函数是对象

在 Python 中万物皆为对象,函数也不例外,

  1. 可以作为对象可以赋值给一个变量
  2. 可以作为元素添加到集合对象中
  3. 可作为参数值传递给其它函数
  4. 可以当做函数的返回值

函数身为一个对象,拥有对象模型的三个通用属性:id、类型、和值。

>>> def add(a, b):
... return a + b
...
>>> id(add)
4472644200
>>> type(add)
<class 'function'>
>>> add
<function add at 0x10a972268>

变量赋值

>>> newAdd = add
>>> newAdd(1, 2)
3
>>> add is newAdd
True

其实上侧的,只是不同的名字指向同一个内存地址,同时这块地址被多次引用。

变量

了解了函数是对象,以及上侧的几个性质,更得牢记一切都是对象。
既然是对象,性质都类似,其余的集合、参数、返回值,思考下就明白了

第二部分 参数类型

其实就是下侧那个括号里的数据,但是需要注意的是,传的是什么怎么传

定义

def 函数名(参数):
# 内部代码
return 表达式

完整的语法

func(positional_args, keyword_args, *tuple_nonkw_args, **dict_kw_args)

函数的参数的分类

  1. 位置参数(必选参数)
  2. 默认参数
  3. 可变参数 *args
  4. 可变参数 **kwargs
  5. 必选参数

1. 位置参数

定义:调用时候的传的参数必须与定义时的参数一一对应。
注意事项:
不能多也不能少。

>>> def sum(a, b):
... return a + b
# 一一对应
>>> print(sum(1, 2))
3
# 参数缺失
>>> print(sum(1))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sum() missing 1 required positional argument: 'b'
# 关键字参数,顺序不重要,存在即可
>>> print(sum(b=10, a=4))
14

2. 默认值

定义:在函数声明的时候,可以给某个参数指定默认值
注意事项:
位置:需要放在位置参数之后

默认参数需要在位置参数的后边

def power(x, n = 2):
return x**n
ret1 = power(10) # 使用默认的参数值n=2
ret2 = power(10, 4) # 将4传给n,实际计算10**4的值
ret3 = power(n=1, x=100) # 指定参数名传值

来个例子
主要涉及:内存地址、参数默认值

def func(a=[]):
a.append("A")
return a

print(func())
print(func())
print(func())

三思
三思
三思

答案如下:

['A']
['A', 'A']
['A', 'A', 'A']

如果真正理解了,Python 对象的变量名与内存地址,这个问题还是很简单的。

  1. 默认参数 a 指向的空列表对象就会被创建,假如内存地址为0x123;
  2. 之后每次调用,都会在原地址后追加A;
  3. 可以试着在函数内部打印 a 的内存地址,应该是一致的。

如何避免这种操作?

让 a 的默认值指向一个不可变对象即可,如:数字、None 等

3. 可变参数 *args

在 python 里面,函数在声明的时候,参数中可以使用(变量名)的方式来接受不确定长度的参数,但是在 python 里面大家约定俗成使用`args接受不定长参数,之后将这些参数放到一个tuple`里面,可以通过访问 args 来获取这些不定长参数。

例子一

>>> def changeFunc(*args):
... print(type(args))
... print(args)
...
>>> changeFunc("a", "b", 3)
<class 'tuple'>
('a', 'b', 3)

例子二

>>> changeFunc(["a", "b", 3])
<class 'tuple'>
(['a', 'b', 3],)

为何打印出来是这样?
因为是数组是作为一个整体传过来的。
如何将 list 的元素转换为 tuple 的元素?结果与例子一一致?

答案: 参数前加个星号,解压参数列表.

>>> tmpList = ["a", "b", 3]
>>> print(*tmpList)
a b 3
>>> print(tmpList)
['a', 'b', 3]
# 调用函数时,数组➕星号,解压参数列表
>>> changeFunc(*tmpList)
<class 'tuple'>
('a', 'b', 3)

当参数已经在列表或元组中但需要为需要单独位置参数的函数, 需要解压参数, 然后再一一传进去

针对字典带上*号,解压的是 key

>>> dict = {"a": 1, "b": 2, "c": 3}
>>> print(*dict)
a b c

4. 可变参数 *args

定义:*args参数表示未知的位置参数序列长度,而\*\*kwargs代表包含关键字和值映射关系的字典,它可以包含任意数量的关键字和值映射,并且在函数定义中“*args”必须位于“**kwargs”前面。

*args 必须在**kwargs 之前。
默认值参数必须再必选参数之前。

>>> def kwargs(first, second=2, *args, **kwargs):
... print(first, second)
... print(args)
... print(kwargs)
...
# kwargs为空是因为被*args吃掉了
>>> kwargs(1, 3, "a", "b", "c", {"key1": "value"})
1 3
('a', 'b', 'c', {'key1': 'value'})
{}
>>> list = ["a", "b", "c"]
>>> dict = {"key1": "value"}
# 解压参数,
>>> kwargs(1, 3, *list, **dict)
1 3
('a', 'b', 'c')
{'key1': 'value'}
# 后边不能追加参数了。。
>>> def test(**kw, error):
File "<stdin>", line 1
def test(**kw, error):
^
SyntaxError: invalid syntax

5. 关键字参数

位置参数可以不带参数名,但是星号后边的就必选带上参数名

# 星号后必选加上参数名字
>>> def func(loction, second, *, must):
... print(loction, second, must)
...
>>> func(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes 2 positional arguments but 3 were given
# 符合预期
>>> func(1, 2, must=3)
1 2 3

来一个汇总的骚操作

>>> def student(name, age=18, *args, sex, classroom, **kwargs):
... print(name, age, args, sex, classroom, kwargs)
...
# *args 之后的一般参数默认更改为关键字参数,sex/classroom未传递,报错
>>> student("howard")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: student() missing 2 required keyword-only arguments: 'sex' and 'classroom'
# 位置参数与关键字参数【必选】,可选参数【可选】
>>> student("howard", sex="1", classroom="python")
howard 10 () 1 python {}
# dict
>>> dict
{'key1': 'value'}
>>> student("howard", sex="1", classroom="python", **dict)
howard 10 () 1 python {'key1': 'value'}

本文重点注意事项:

  1. 牢记函数为第一类对象;
  2. 可选参数注意解压参数;

参考
函数作为参数的问题
Python 中的函数详解