函数
定义函数
函数也是一个对象
- 对象是内存中专门用来存储数据的一块区域
- 函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用
- 函数中保存的代码不会立即执行,需要调用函数代码才会执行
创建函数:
def 函数名([形参1,形参2,...形参n]) :
代码块
- 举个例子:
def times(x):
return x * x
# return 后边跟什么值,函数就会返回什么值
# return 后边可以跟任意的对象,返回值甚至可以是一个函数
def fn():
# return 'Hello'
# return [1,2,3]
# return {'k':'v'}
def fn2() :
print('hello')
return fn2 # 返回值也可以是一个函数
r = fn() # 这个函数的执行结果就是它的返回值
# r()
# print(fn())
# print(r)
# 如果仅仅写一个return 或者 不写return,则相当于return None
def fn2() :
a = 10
return
# 在函数中,return后的代码都不会执行,return 一旦执行函数自动结束
def fn3():
print('hello')
return
print('abc')
函数还可以返回多个值:
def move(x, y):
return nx * 2, ny * 3
- 但其实这只是一种假象,Python函数返回的仍然是单一值
- 它实际上是一个tuple,但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
空函数
- 如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
- pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。
- pass还可以用在其他语句里,比如:
if age >= 18:
pass
深入理解def
关于返回值:
- 请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回
- 如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。return None可以简写为return。
def是一个可执行语句:
- 正因为def是一个语句,所以它可以出现在任何地方。但是def语句通常在模块文件中编写,并在模块导入时运行
# 定义
if test:
def func():
.....
else:
def func():
.....
# 调用
func()
- def创建了一个对象并将其赋值给某一变量名
- 当python运行到def语句时,它将会生成一个新的函数对象并将其赋值给这个函数名。
- 此时,函数名变成某一个函数的引用
- (注意,python中所有的语句都是实时执行的,没有独立编译这样的流程)
- 比如上面,当python运行到def语句时,它将会创建一个新的函数对象,封装这个函数的代码并赋值给变量名times。
- 函数并不存在,直到python运行了def之后才存在,def在运行时才评估,def中的代码在函数调用之后才评估
- 当python运行到def语句时,它将会生成一个新的函数对象并将其赋值给这个函数名。
调用函数
def fn5():
return 10
# fn5 和 fn5()的区别
print(fn5) # fn5是函数对象,打印fn5实际是在打印函数对象 <function fn5 at 0x05771BB8>
print(fn5()) # fn5()是在调用函数,打印fn5()实际上是在打印fn5()函数的返回值 10
使用位置参数调用函数名
在调用函数时传递的实参与定义函数时的形参顺序一致,这是调用 函数的基本形式。
使用关键字参数调用函数
在调用函数时可以采用“关键字=实参”的形式,其中,关键字的名称就是定义函数时形参的名称。
def add(a=0, b=0, c=0):
return a + b + c
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
# 传递参数时可以不按照设定的顺序进行传递
print(add(c=50, a=100, b=200))
如何正确理解函数名
- 变量名(函数)名可以赋值给另一个对象:
- 除了调用外,函数允许任意的属性附加到记录信息以供随后使用
def func(): ...
func() # call
func.attr = value #附加属性
多态:函数的作用取决于传递给它的对象类型
函数在调用时,解析器不会检查实参的类型
实参可以传递任意类型的对象
def times(x):
return x * x
print(times(2, 4)) #8:乘法
print(times('NA', 2)) #NANA:赋值
- 函数的作用取决于传递给它的对象类型。
- python将对某一对象在某种语法的合理性交给对象自身来判断。
- 这种依赖类型的的行为叫做多态:一个操作的意义取决于被操作对象的类型
- 只要对象支持所预期的接口,那么函数就能处理。
- 如果传递的对象不支持这种预期的接口,python将会在*表达式运行时检测到错误,并自动抛出一个异常。因此编写代码错误进行检测是没有意义的。
- 实际上,这种做法只会限制函数的功能,让函数限制在测试过的那些类型上才有效
- 这也意味着必须测试代码区检测错误,而不是依赖编译器来检测
函数类型
- Python中的任意一个函数都有数据类型,这种数据类型是function, 被称为函数类型。
- 不同的函数类型的区分点主要在参数列表,有两个参数的函数和有一个参数的函数是不同的函数类型
- 函数类型的数据与其他类型的数据是一样的,任意类型的数据都可 以作为函数返回值使用,还可以作为函数参数使用。因此,一个函数可 以作为另一个函数返回值使用,也可以作为另一个函数参数使用
函数的参数
关键字参数
- 关键字参数,可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数
fn(b=1 , c=2 , a=3)
print('hello' , end='')
- 位置参数和关键字参数可以混合使用
- 混合使用关键字和位置参数时,必须将位置参数写到前面
def fn2(a):
print('a =',a)
不定长参数(可变参数)
- 在定义函数时,可以在形参前边加上一个*,这样这个形参将会获取到所有的实参
- 它将会将所有的实参保存到一个元组中
- a,b,*c = (1,2,3,4,5,6)
# *a会接受所有的位置实参,并且会将这些实参统一保存到一个元组中(装包)
def fn(*a):
print("a =",a,type(a))
# fn(1,2,3,4,5)
- 带星号的形参只能有一个
- 带星号的参数,可以和其他参数配合使用
# 第一个参数给a,第二个参数给b,剩下的都保存到c的元组中
# def fn2(a,b,*c):
# print('a =',a)
# print('b =',b)
# print('c =',c)
- 可变参数不是必须写在最后,但是注意,带*的参数后的所有参数,必须以关键字参数的形式传递
# 第一个参数给a,剩下的位置参数给b的元组,c必须使用关键字参数
def fn2(a,*b,c):
print('a =',a)
print('b =',b)
print('c =',c)
# 所有的位置参数都给a,b和c必须使用关键字参数
def fn2(*a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
# 如果在形参的开头直接写一个*,则要求我们的所有的参数必须以关键字参数的形式传递
def fn2(*,a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
# fn2(a=3,b=4,c=5)
- *形参只能接收位置参数,而不能接收关键字参数
- **形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中
- 字典的key就是参数的名字,字典的value就是参数的值
# **形参只能有一个,并且必须写在所有参数的最后
def fn3(b,c,**a) :
print('a =',a,type(a))
print('b =',b)
print('c =',c)
# fn3(b=1,d=2,c=3,e=10,f=20)
命名关键字参数
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
def person(name, age, *, city, job):
print(name, age, city, job)
和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
看个例子
# cls 参数只能通过关键字参数指定,它一定不会捕获未命名的定位参数。
def tag(name, *content, cls=None, **attrs):
if cls is not None:
attrs['class'] = cls
if attrs:
attr_str = ''.join('[attrs] %s ="%s" ' % (attr, value)
for attr, value in sorted(attrs.items()))
else:
attr_str = ''
#------返回值--------
if content:
return '\n'.join('<%s(%s)>%s</%s>' %
(name, attr_str, c, name) for c in content)
else:
return '<%s%s />' % (name, attr_str)
# 传入单个定位参数,生成一个指定名称的空标签。
print(tag('br')) #name = br
"""
<br />
"""
# 第一个参数后面的任意个参数会被 *content 捕获,存入一个元组。
print(tag('p', 'oceanstar')) #content = ('oceanstar')
"""
<p()>oceanstar</p>
"""
print(tag('p', 'hello', 'world')) #content = ('hello', 'world')
"""
<p()>hello</p>
<p()>world</p>
"""
# tag 函数签名中没有明确指定名称的关键字参数会被 **attrs 捕获,存入一个字典
print( tag('p', 'hello', id=33)) # context = ('hello'), attrs[id] = 33
# cls 参数只能作为关键字参数传入。
print(tag('p', 'hello', 'world', cls='sidebar')) #content = ('hello', 'world') cls='sidebar'
#调用 tag 函数时,即便第一个定位参数也能作为关键字参数传入
print( tag(content='testing', name="img")) # name="img", content=('testing')
# 在 my_tag 前面加上 **,字典中的所有元素作为单个参数传入,同名键会绑定到对应的 具名参数上,余下的则被 **attrs 捕获
my_tag = {'name': 'img', 'title': 'Sunset Boulevard', 'src': 'sunset.jpg', 'cls': 'framed'}
print(tag(**my_tag) )
"""
<img[attrs] class ="framed" [attrs] src ="sunset.jpg" [attrs] title ="Sunset Boulevard" />
"""
参数的解包(拆包)
# 参数的解包(拆包)
def fn4(a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
# 创建一个元组
t = (10,20,30)
# 传递实参时,也可以在序列类型的参数前添加星号,这样他会自动将序列中的元素依次作为参数传递
# 这里要求序列中元素的个数必须和形参的个数的一致
# fn4(*t)
# 创建一个字典
d = {'a':100,'b':200,'c':300}
# 通过 **来对一个字典进行解包操作
fn4(**d)
传参与引用
共享传参
python唯一支持的参数传递模式就是共享传参。
共享传参指的是函数的各个形参获得实参中各个引用的副本。
def f(a, b):
a += b
return a
# 数字 x 没变
x = 1
y = 1
print(f(x, y)) #2
print(x, y) # 1 1
# 列表 a 变了
a = [1, 2]
b = [3, 4]
print(f(a, b)) # [1, 2, 3, 4]
print(a, b) # [3, 4]
# 元组 t 没变
t = (10, 20)
u = (30, 40)
print( f(t, u))
print(t, u)
防御可变参数
除非这个方法确实想修改通过参数传入的对象,否则在类中直接把参数赋值给实例变量之前一定要三思,因为这样会为参数对象创建别名。如果不确定,那就创建副本。
不推荐的写法
class TwilightBus:
def __init__(self, passengers=None):
if passengers is None:
self.passengers = []
else:
self.passengers = passengers #别名
推荐的写法
def __init__(self, passengers=None):
if passengers is None:
self.passengers = []
else:
self.passengers = list(passengers)
提取函数参数
def clip(text, max_len = 80):
"""
在max_len前面或者后面的第一个空格处截断文本
:param text:
:param max_len:
:return:
"""
end = None
if len(text) > max_len:
space_before = text.rfind(' ', 0, max_len)
if space_before >= 0:
end = space_before
else:
space_after = text.rfind(' ', max_len)
if space_after >= 0:
end = space_after
if end is None: #没有找到空格
end = len(text)
return text[:end].rstrip()
提取关于函数参数的信息
- 函数对象有个 __defaults__ 属性,它的值是一个元组,里面保存着定位参数和关键字参数的默认值。仅限关键字参数的默认值在 __kwdefaults__ 属性中。
print(clip.__defaults__)
"""
(80,)
"""
- 参数的名称在__code__ 属性中,它的值是一个 code 对象引用,自身也有很多属性。
- 这里不包含前缀为 * 或 ** 的变长参数。参数的默认值只能通过它们在__defaults__ 元组中的位置确定
print(clip.__code__) # <code object clip at 0x00000272E9C2D790, file "C:\Users\oceanstar\PycharmProjects\pythonProject\main.py", line 1>
# 参数名称在 __code__.co_varnames 中,不过里面还有函数定义体中创建的局部变量。
print(clip.__code__.co_varnames) #('text', 'max_len', 'end', 'space_before', 'space_after')
# 参数名称是前 N 个字符串,N 的值由__code__.co_argcount 确定。
print(clip.__code__.co_argcount) #2
推荐使用inspect模块来提取,而不是上面的方法
提取函数的签名
- inspect.signature 函数返回一个 inspect.Signature 对象:
- 它有一个 parameters属性,这是一个有序映射,把参数名和 inspect.Parameter 对象对应起来。
- 各个 Parameter 属性也有自己的属性,例如 name、default 、 kind、annotation(注解)
- name:函数名
- default:默认值
- kind 属性的值是 _ParameterKind 类中的 5 个值之一
- POSITIONAL_OR_KEYWORD:可以通过定位参数和关键字参数传入的形参(多数 Python 函数的参数属于此类)
- VAR_POSITIONAL:定位参数元组
- VAR_KEYWORD:关键字参数字典
- KEYWORD_ONLY:仅限关键字参数
- POSITIONAL_ONLY:仅限定位参数;
- 特殊的 inspect._empty 值表示没有默认值,
from inspect import signature
# 参数为函数对象,它能够捕获关于这个函数的签名信息
sig = signature(clip)
print(sig)
print(str(sig))
"""
(text, max_len=80)
(text, max_len=80)
"""
for name, param in sig.parameters.items():
print(param.kind, ':', name, '=', param.default)
"""
POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
POSITIONAL_OR_KEYWORD : max_len = 80
"""
使用inspect.signature不仅可以帮忙查看函数的参数信息,还可以帮忙检查一组参数是否符合函数的输入。
(1)使用signature的bind方法,如果参数符合,返回BoundArguments对象,否则抛出TypeError错误。
import inspect
def hello(greet : str, flag=True):
print("hello", greet)
sig = inspect.signature(hello)
print(sig.bind("hello", True))
print(sig.bind())
- out:
<BoundArguments (greet='hello', flag=True)>
Traceback (most recent call last):
File "C:\Users\oceanstar\PycharmProjects\pythonProject\main.py", line 9, in <module>
print(sig.bind())
File "C:\Users\oceanstar\AppData\Local\Programs\Python\Python310\lib\inspect.py", line 3179, in bind
return self._bind(args, kwargs)
File "C:\Users\oceanstar\AppData\Local\Programs\Python\Python310\lib\inspect.py", line 3094, in _bind
raise TypeError(msg) from None
TypeError: missing a required argument: 'greet'
Process finished with exit code 1
(2)如果不想检查所有参数,只想检查部分参数,可以使用bind_partial
import inspect
def hello(greet : str, flag=True):
print("hello", greet)
sig = inspect.signature(hello)
print(sig.bind("hello", True))
print(sig.bind_partial()) # 检查部分参数,不会抛出错误
- out:
<BoundArguments (greet='hello', flag=True)>
<BoundArguments ()>
help
# help()是Python中的内置函数
# 通过help()函数可以查询python中的函数的用法
# 语法:help(函数对象)
# help(print) # 获取print()函数的使用说明
# 文档字符串(doc str)
# 在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明
# 当我们编写了文档字符串时,就可以通过help()函数来查看函数的说明
# 文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串
def fn(a:int,b:bool,c:str='hello') -> int:
'''
这是一个文档字符串的示例
函数的作用:。。。。。
函数的参数:
a,作用,类型,默认值。。。。
b,作用,类型,默认值。。。。
c,作用,类型,默认值。。。。
'''
return 10
help(fn)