python:函数

函数

定义函数

函数也是一个对象

  • 对象是内存中专门用来存储数据的一块区域
  • 函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用
  • 函数中保存的代码不会立即执行,需要调用函数代码才会执行

创建函数:

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中的代码在函数调用之后才评估

调用函数

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)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值