python函数基础与变量作用域

文章发布时间:

最后更新时间:

文章总字数:
3.4k

预计阅读时间:
16 分钟

一.函数基础知识

  • 函数是面向过程编程的最小单位,也是一种数据
  • 目的就是封装方便以后复用
  • 函数分自定义函数,内置函数(python官方),第三方函数(第三方库)
1
2
3
4
5
6
7
# 使用def 语句定义一个函数a
def function_name(parameters):
# 函数体
# 进行一些操作
return result # 可选的返回值

print(type(function_name))
<class 'function'>
  1. def:用于声明一个函数,告诉 Python 这是一个函数的定义。

  2. function_name:函数名,一个有效的标识符,规则和变量名一致。

  3. parameters:形参,可以是0 ~ n 个,参数之间用逗号分隔。

  4. 函数体:定义函数执行的具体操作。

  5. return:指定函数的返回值,没有则默认返回None。

  6. return之后的代码不会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 示例
def test():
print('test')
return 1
print('test2')
return
print('test3')
return

# 直接调用,最后显示的1是因为ipynb文件会直接渲染最后一个值,而.py文件就不会
test()

# 比如这里,x是最后一个值,所以会直接渲染,没有渲染上面的1
x = 10
x
test
10
1
2
3
4
5
6
7
8
9
10
11
12
def test():
print('test')
return 1
print('test2')
return
print('test3')
return

test()
# 先执行return后面的语句,再执行return后面的语句,x实际为1
x = test()
print(x)
test
test
1

二.函数调用

  • 函数调用是一个表达式,可以参与运算

2.1 位置传参

1
2
3
4
5
6
7
8
# 指形参与实参会互相匹配顺序数量,参数(argument)
def func(a,b):
print(a,b)
return

func(1,2)
func(2,1)
func(1,2,3)
1 2
2 1

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[21], line 8
    6 func(1,2)
    7 func(2,1)
----> 8 func(1,2,3)

TypeError: func() takes 2 positional arguments but 3 were given
1
2
3
4
# 只要匹配的上,用不用参数无所谓
def func(a,b):
print(a)
func(1,2)
1

2.2 关键词传参

1
2
3
4
5
6
7
8
9
# 直接指定特定参数传什么数据,没有顺序问题
def func(a,b,c):
print(a,b,c)
func(b = 1,c = 2,a = 3)

# 只传几个,关键词传参必须放在所有位置参数后面
def func(a,b,c):
print(a,b,c)
func(1,c = 2,b = 3 )
3 1 2
1 3 2
1
2
3
def func(a,b,c):
print(a,b,c)
func(1,c = 3,2)
Cell In[36], line 3
    func(1,c = 3,2)
                ^
SyntaxError: positional argument follows keyword argument

2.3 参数默认值

1
2
3
4
5
6
7
8
# 直接在构建函数时给形参赋值
def test(a, b = 2 , c = 1):
print(a,b,c)
return
test(1)
test(1,2,3)
test(1,c = 3)
test(1,None,3)
1 2 1
1 2 3
1 2 3
1 None 3
1
2
3
4
# 注意,默认值参数必须放在形参后面,除非全有默认值
def test(a = 1, b, c = 3):
print(a,b,c)
return
Cell In[46], line 2
    def test(a = 1, b, c = 3):
                    ^
SyntaxError: parameter without a default follows parameter with a default

2.4 可变位置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 在变量名前加上 * 可以将所有数据收集成一个元组,不传都行,是空元组
def test(*args):
print(args)
print(type(args))
print()
return

test(1,2,3)
test()

# 也可以先传普通形参
def test(a,*args):
print(a)
print(args)
print(type(args))
return
test(1,2)
(1, 2, 3)
<class 'tuple'>

()
<class 'tuple'>

1
(2,)
<class 'tuple'>

2.5 可变关键词参数(字典)

1
2
3
4
5
6
7
8
# 在变量名前加 ** ,可以将后面传入的参数以字典的形式传入,且必须以关键词传参
def test(a,**kwargs):
print(kwargs)
print(type(kwargs))
for i in kwargs:
print(i,kwargs[i])

test(1,b=2,c=3)
{'b': 2, 'c': 3}
<class 'dict'>
b 2
c 3
  • 两个可变参数的联合使用
  • 实质是剩余参数传递,即普通形参传完后剩余的参数传递给可变参数
1
2
3
4
5
6
def test(a,*args,**kwargs):
print(a)
print(args)
print(kwargs)

test(1,2,3,4,Name = 'clocky7')
1
(2, 3, 4)
{'Name': 'clocky7'}

2.6 多参数解包

2.6.1 解包位置传参

1
2
3
4
5
6
#就是反过来在实参中使用*
def test(a,b,c):
print(a,b,c)

x = (1,2,3)
test(*x)
1 2 3
1
2
3
4
5
6
# 实参多于形参
def test(a,b,c):
print(a,b,c)

x = (1,2,3,4)
test(*x)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[68], line 6
    3     print(a,b,c)
    5 x = (1,2,3,4)
----> 6 test(*x)

TypeError: test() takes 3 positional arguments but 4 were given
1
2
3
4
5
# 实参少于形参
def test(a,b,c):
print(a,b,c)
x = (1,2)
test(*x)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[70], line 5
    3     print(a,b,c)
    4 x = (1,2)
----> 5 test(*x)

TypeError: test() missing 1 required positional argument: 'c'

2.6.2 解包关键字传参(字典)

1
2
3
4
def test(a,b,c):
print(a,b,c)
x = {'a':1,'c':3,'b':2}
test(**x)
1 2 3

注意 : * 本身不是参数,只是一个占位符

1
2
3
def test(a,*,b = 3):
print(a,b)
test(1)
1 3
1
2
3
def test(a,*,b = 3):
print(a,b)
test(1,3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[83], line 3
    1 def test(a,*,b = 3):
    2     print(a,b)
----> 3 test(1,3)

TypeError: test() takes 1 positional argument but 2 were given
1
2
3
4
# 意思是*占一个参数位置,其后面的参数若要传参必须以关键字传参
def test(a,*,b = 3):
print(a,b)
test(1,b = 4)
1 4

三.可变与不可变参数

实质上是传入变量中的数据,但指向一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 传不可变数据
def test(a): #这里其实本质是传入10,但x作为一个变量兼对象,所以其实是传入了地址引用
a = 20

x = 10
test(x)
print(x)


# 传可变数据
def test(a):
a[0] = 20

y = [1,2,3]
test(y)
print(y)
10
[20, 2, 3]
1
2
3
4
5
6
# 不可变的元组也一样
def test(a):
a[0] = 20
z = (1,2,3)
test(z)
print(z)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[92], line 5
    3     a[0] = 20
    4 z = (1,2,3)
----> 5 test(z)
    6 print(z)

Cell In[92], line 3, in test(a)
    2 def test(a):
----> 3     a[0] = 20

TypeError: 'tuple' object does not support item assignment

四.匿名函数

没有名字,可以有任意数量的参数,但只能包含一个表达式,并返回该表达式的结果。用lambda定义匿名函数。

  • lambda arguments: expression
1
2
3
4
5
6
7
8
9
10

fn = lambda x:x+1
print(fn(1))

# 用于条件表达式,判断是否为偶数
is_even = lambda x: "Even" if x % 2 == 0 else "Odd"

print(is_even(4)) # 输出: Even
print(is_even(7)) # 输出: Odd

2
Even
Odd

五.内置函数

1
2
3
4
5
6
7
8
9
10
11

# all函数 可迭代对象中的所有元素都为 `True`,返回 `True`,否则返回 `False`
print(all([True, True, False])) # 输出: False
print(all([1, 2, 3])) # 输出: True

print()

# any函数 可迭代对象中只要一个元素为 `True`,返回 `True`,否则返回 `False`
print(any([False, False, False])) # 输出: False
print(any([1, 2, 3])) # 输出: True

False
True

False
True
1
2
3
4

# sum函数,返回可迭代对象中所有元素的总和
print(sum([1,2,3,4,5]))

15
1
2
3
4
5
6
7
8
9
10
11

# sorted 返回一个新列表,其中包含可迭代对象中的元素,按照升序排序(这就是默认排序)
l = (1, 5, 3, 2, 4)
print(sorted(l))

# sorted() 函数还可以接受一个 key 参数,指定一个函数,用这个函数操作的结果来排序(排序的内容仍然是原可迭代对象中的元素)
l = (1, 5, 3, 2, 4)
def my_key(x):
return x % 3
print(sorted(l, key=my_key))

[1, 2, 3, 4, 5]
[3, 1, 4, 5, 2]

这里补充一个小知识:经典的math库中abs()表示取绝对值

1
2
3
4
5
6
7
8
9
10

# reversed() 返回一个反向迭代器
x = reversed([1, 2, 3])

# 将方向迭代器转换为列表
re = list(x)

print(x,type(x))
print(re)

<list_reverseiterator object at 0x0000019B8496A530> <class 'list_reverseiterator'>
[3, 2, 1]
1
2
3
4
5
6
7
8
9

# callable 检查对象是否可以当函数被调用
def fn(x):
return

print(callable(fn))
print(callable(print))
print(callable(123))

True
True
False
1
2
3
4
5
6
7
8
9
10

# zip 将多个可迭代对象中对应元素打包成一个zip对象,常用于并行遍历多个序列,如果超出则丢弃
a = [1, 2, 3]
b = ('name','age')
c = ('clocky','20')

x = zip(a,b,c)
print(x,type(x))
print(list(x))

<zip object at 0x0000019B84F18880> <class 'zip'>
[(1, 'name', 'clocky'), (2, 'age', '20')]
1
2
3
4
5
6
7
8

# exec 执行存储在字符串中的 Python 代码(这个比eval能写的内容更多)
# eval 只能执行一个字符串表达式,exec可以执行多行
code = '''for i in range(3):
print(i)'''

exec(code)

0
1
2
1
2
3
4
5
6

# globals 返回值为一个字典,字典key为当前python环境全局变量的名字

for key in globals():
print(key)

__name__
__doc__
__package__
__loader__
__spec__
__builtin__
__builtins__
........
1
2
3
4
5
6
7
8

# locals 返回一个字典,字典key为当前函数内部或者局部的变量名
def fn(x):
y = x + 1
print(locals())

fn(1)

{'x': 1, 'y': 2}
1
2
3
4
5
6
7
8

# filter(fun,x) 返回一个经过函数筛选的可迭代对象filter,函数操作后实际会给出一个布尔值,若为True则原数据保留,否则丢弃
# filter 又叫过滤器
numbers = [1, 2, 3, 4, 5]
filtered = filter(lambda x: x**2, numbers)
print(filtered,type(filtered))
print(list(filtered))

<filter object at 0x000002A4449C1F60> <class 'filter'>
[1, 2, 3, 4, 5]

5.1 高阶函数

5.1.1 map

1
2
3
4
5
6
7
8
9
10

# 类似filter()函数,返回一个迭代器
x = [1,2,3,4,5,6,7,8,9,10]
def is_even(n):
if n % 2 == 0:
return n

even_numbers = map(is_even, x)
print(list(even_numbers))

[None, 2, None, 4, None, 6, None, 8, None, 10]

5.1.2 reduce

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 这里不同的是用于两个参数的函数,reduce会将函数操作后的返回值作为第一个参数,再取下一个数据
# reduce(fun,arg,init)会返回函数的最后一个返回值,其中init是初始值,可自定义,将其作为第一个参数,多用于累加后再加一个值
from functools import reduce

x = [1, 2, 3, 4, 5]
def fn(a,b):
print(a,b)
return a+b
re = reduce(fn,x,0)

print()
print(re)

0 1
1 2
3 3
6 4
10 5

15

六.变量作用域

  • 指在程序中某个变量的有效范围,也就是在代码的哪个部分可以访问或修改该变量

  • Python中的变量作用域遵循LEGB规则(Local, Enclosing, Global, Built-in),依次搜索变量的定义位置

  • 函数内部可以访问内部变量和外部的全局变量,外部则不能访问内部局部变量

6.1 Local→局部作用域

在函数调用时被创建,在函数调用后自动销毁。

6.2 Enclosing→嵌套作用域

  • 指外层函数中的变量,在内层函数中可访问,但不可修改。

  • 当一个函数嵌套在另一个函数内部时,外层函数的变量属于Enclosing作用域

  • 内层函数变量若与外层变量同名,则优先选择内层

  • 实质上就是在最内层找到变量,若找不到,则继续往外层找,一层一层直到找到为止。

1
2
3
4
5
6
7
8
9
10
11

def fn1():
a = 100

def fn2():
a = 200
print(a)
fn2()

fn1()

200

6.3 全局作用域

  • 指模块级别定义的变量,整个模块都可以访问。

  • 如果想在函数中修改全局变量,需要使用global关键字

6.4 Built-in→内建作用域

  • 包含Python内建的函数、异常和常量,如print(), len(), int, Exception等。

  • 这些变量可以在任何地方使用。

6.5 函数调用问题

1
2
3
4
5

# 特殊调用
a = [1,2,3,lambda x:x+1]
a[3](1)

2
1
2
3
4
5
6
7
8
9
10
11
12
13

# 函数无论在哪里调用,一定是在它定义的作用域下运行
a = 1

def fn():
print(a)

def fn1():
a = 2
fn()

fn1()

1

修改变量的值

1
2
3
4
5
6
7
8
9

# 一个会报错的情况,x的赋值和print顺序不能换,换了甚至取不到外面的值
x = 1
def f1():
print(x)
x = 2

f1()

---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[31], line 7
    4     print(x)
    5     x = 2
----> 7 f1()

Cell In[31], line 4, in f1()
    3 def f1():
----> 4     print(x)
    5     x = 2

UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
1
2
3
4
5
6
7
8
9
10
11
12

# 修改全局变量
a = 1
def f1():

global a
print(a)
a = 2

f1()
print(a)

1
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 修改函数外层变量
a = 1
def foo():
a = 2

def foo1():
nonlocal a
print(a)
a = 3

foo1()
print(a)

foo()
print(a)

2
3
1
1
2
3
4
5
6
7
8

# 变量查找遵从由内向外,全局之后还有官方系统的内建作用域
def f1():
def f2():
print(__name__)
f2()
f1()

__main__

6.6 函数内层分配

1.将函数代码存储到代码区,函数中代码不执行。

2.调用函数时,在内存中开辟空间(栈帧),存储函数内部定义的变量。

3.函数调用后,栈帧立即被释放。

七.递归函数

1
2
3
4
5
6
7
8
9
10
11
12
13

# 先将一个简单的,函数内部的作用域是可以访问定义的函数自身的,但是很容易陷入死循环(函数自调用)
# 这种情况一般和条件语句混合使用,强行退出自调用
a = 1
def fn():
pass

def fn1():
print(a)
print(fn())
print(fn1())
fn1()

1 None
1 None
1 None 
...
1 None
1 None

---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
Cell In[40], line 10
    8     print(fn())
    9     print(fn1())
---> 10 fn1()

Cell In[40], line 9, in fn1()
    7 print(a)
    8 print(fn())
----> 9 print(fn1())

Cell In[40], line 9, in fn1()
    7 print(a)
    8 print(fn())
----> 9 print(fn1())

    [... skipping similar frames: fn1 at line 9 (2972 times)]

Cell In[40], line 9, in fn1()
    7 print(a)
    8 print(fn())
----> 9 print(fn1())

Cell In[40], line 7, in fn1()
    6 def fn1():
----> 7     print(a)
    8     print(fn())
    9     print(fn1())

File c:\Users\Clocky7\anaconda3\envs\start1\Lib\site-packages\ipykernel\iostream.py:664, in OutStream.write(self, string)
    655 def write(self, string: str) -> Optional[int]:  # type:ignore[override]
    656     """Write to current stream after encoding if necessary
    657 
    658     Returns
(...)    662 
    663     """
--> 664     parent = self.parent_header
    666     if not isinstance(string, str):
    667         msg = f"write() argument must be str, not {type(string)}"  # type:ignore[unreachable]

RecursionError: maximum recursion depth exceeded
1
2
3
4
5
6
7
8
9
# 正确用法
a = 1
def fn(x):
if x >= 5:
return 5
else:
return fn(x+1)

fn(a)
5
1
2
3
4
5
6
7
8
9
10
11

# 用于阶乘计算

def jiecheng(a):
if a <= 1:
return 1
else:
return a * jiecheng(a - 1)

print(jiecheng(5))

120