函数基础-(引子、定义函数、调用函数、函数返回值、函数参数)

函数基础

  • 引子

  • 定义函数

  • 调用函数

  • 函数返回值

  • 函数参数

一、引子

1.不用函数会引发什么问题?

  代码的组织结构不清晰,可读性差;

  遇到重复的功能只能重复编写实现代码,代码冗余;

  功能需要扩展的时候,需要找出所有实现该功能的地方修改,无法统一管理且维护难度极大;

  举个例子:

  比如说,len方法突然不能直接用了,然后现在有一个需求,让你计算'hello world'的长度,你怎么计算?

  这个需求对于现在的你其实不难,我们一起来写一下。

s1 = "hello world"
length = 0
for i in s1:
    length = length+1

print(length)

  好了,功能实现了,非常完美。然后现在又有了一个需求,要计算另外一个字符串的长度,"hello eva".

  于是,这个时候你的代码就变成了这样:

s1 = "hello world"
length = 0
for i in s1:
    length = length+1

print(length)

s2 = "hello eva"
length = 0
for i in s2:
    length = length+1

print(length)

  这样确实可以实现len方法的效果,但是总感觉不是那么完美?为什么呢?

  首先,之前只要我们执行len方法就可以直接拿到一个字符串的长度了,现在为了实现相同的功能我们把相同的代码写了好多遍 —— 代码冗余

  我们就想啊,要是我们能像使用len一样使用我们这一大段“计算长度”的代码就好了。这种感觉有点像给这段代码起了一个名字,等我们用到的时候直接喊名字就能执行这段代码似的。要是能这样,是不是很完美啊?这时候就需要我们的函数出场啦!

  

2.函数是什么?

  函数的定义:函数就是对功能的封装;

  想象生活中的例子,修理工需要事先准备好工具箱里面放好锤子、扳手、钳子等工具,然后遇到锤钉子的时候,拿上锤子用就可以,而无需临时再造一把;

  修理工 -------> 程序员

  具备某功能的工具 --------> 函数

  要想使用工具,需要事先准备好,然后拿来就用且可以重复使用

  想要用函数,需要先定义,再使用;.

  那么我们如何用函数方式解决上面问题呢?

  现在就教大家一个既能,让你们把代码装起来。

def mylen():
    s1 = "hello world"
    length = 0
    for i in s1:
        length = length+1
    print(length)

  这样我们就定义了一个函数,怎么使用呢,只需要mylen()就可以实现函数调用。

3.函数分类

  a.内置函数

  为了方便我们的开发,针对一些简单的功能,python解释器已经为我们定义好了的函数即内置函数,对于内置函数,我们可以拿来就用而无需事先定义,另一篇文章里专门有内置函数的讲解;

  b.自定义函数

  毕竟是内置函数,所能提供的功能是有限的,这样就需要我们自己根据需求,事先定义好我们自己的函数来实现某功能,以后在遇到应用场景时,调用自定义的函数即可。

二、定义函数

1.如何定义函数,函数的结构体。

## 语法
def  函数名(形参1,形参2,形参3,.):
       '''注释'''
       函数体
       return  返回的值

## 函数名要有意义

##注意:
#1.def关键字开头,空格之后接函数名称和圆括号(),最后还有一个“:”;
#2.def是固定的,不能变,必须是连续的def三个字母,不能分开;
#3.空格,为了将def关键字和函数名分开,必须空开,也可以使用2个或多个空格,正常人都是一个空格;
#4.函数名,函数名只能包含字符串,下划线和数字且不能以数字开头,要想变量一样的去定义;
#5.括号,是必须加的,不要问为什么,加上就对了;
#6.每一个函数都应该对功能和参数进行相应的说明,应该写在函数下面的第一行,以增强代码的可读性;
#7.调用,就是函数名(),一定要记得加上括号,不加括号获取的就是函数的内存地址了。

  例如:

def auth(user,password):
    '''
    auth function
    user:用户名
    password:密码
    return:认证结果      
    '''
    if user == "zjk" and password == "123":
         return 1

user = input(user:).strip()
password = input(pws:).strip()
res = auth(user,password)
print(res)
函数定义实例

2.函数的使用原则:先定义,再调用;

  函数即“变量”,变量必须先定义后饮用,未定义而直接引用函数就相当于在引用一个不存在的变量;用例子来说明:

#测试1.
>>> def foo():
...     print("from foo")
...     bar()
... 
>>> foo()
from foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in foo
NameError: name 'bar' is not defined

#报错,提示bar这个函数没有定义;

#测试2.
>>> def bar():
...     print("from bar")
... 
>>> def foo():
...     print("from foo")
...     bar()
... 
>>> foo()
from foo
from bar

##正常执行;

#测试3
>>> def foo():
...     print("from foo")
...     bar()
... 
>>> def bar():
...     print("from bar")
... 
>>> foo()
from foo
from bar

#说明在代用函数时,函数是不分前后的,但需要注意的时,函数的使用,必须遵循原则,先定义,后使用;不然的话代码调用函数的时候,函数却还没有加载到内存;所以,我们在使用函数时,一定要明确地区分定义阶段和调用阶段。

#定义阶段
>>> def foo():
...     print("from foo")
...     bar()
... 
>>> def bar():
...     print("from bar")
... 
>>> foo()
#调用阶段
>>> foo()
from foo
from bar

3.函数在定义阶段都干了啥子事情?

  只检测语法,不执行代码;

  也就是说,语法错误在函数定义阶段就 会检测出来,而代码的逻辑错误只有在执行的时候才知道;

4.定义函数的三种形式;

  无参:应用场景仅仅只是执行一些操作,比如与用户交互,打印;

  有参:需要根据外部传来的参数,才能执行相应的逻辑,比如统计长度,求最大值最小值;

  空函数:设计代码结构;

  例如:

#定义阶段
def tell_tag(tag,n): #有参数
    print(tag*n)

def tell_msg(): #无参数
    print('hello world')

#调用阶段
tell_tag('*',12)
tell_msg()
tell_tag('*',12)

'''
************
hello world
************
'''

#结论:
#1、定义时无参,意味着调用时也无需传入参数
#2、定义时有参,意味着调用时则必须传入参数
无参、有参
def auth(user,password):                             
    '''                                                           
    auth function                                                 
    :param user: 用户名                                              
    :param password: 密码                                           
    :return: 认证结果                                                 
    '''                                                           
    pass                                                          
                                                                  
def get(filename):                                                
    '''                                                           
    :param filename:                                              
    :return:                                                      
    '''                                                           
    pass                                                          
                                                                  
def put(filename):                                                
    '''                                                           
    :param filename:                                              
    :return:                                                      
    '''                                                           
def ls(dirname):                                                  
    '''                                                           
    :param dirname:                                               
    :return:                                                      
    '''                                                           
    pass                                                          

#程序的体系结构立见           
空函数

三、调用函数

1.调用函数

  函数的调用:函数名加括号,func()

  a.先找到名字

  b.根据名字调用代码

2.函数返回值

  return 是一个关键字,这个词翻译过来就是“返回”,所以我们管写在return后面的值叫“返回值",一旦遇到return,结束整个函数;

  三种类型,无返回值,返回1个值,返回多个值;

  无返回值:函数体中没有return,或者return后没有跟任何值,这时此函数会返回一个None;

  返回1个值:return 后跟一个值;

  返回多个值:return后跟多个值,多个值之间用逗号分隔,此时return返回的是一个元组类型;

  那么,什么时候该有返回值呢?

    调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值;

    通常有参函数需要有返回值,输入参数,经过计算,得到一个最终的结果;

  什么时候不需要有返回值?

    调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值;

    通常无参函数不需要有返回值;

3.函数调用的三种形式

  a.语句形式:foo()

  b.表达式形式:3 * len("hello")

  c.当中另外一个函数的参数:range(len("hello"))

4.函数结束的方式:

  a.遇到return结束整个函数;

  b.函数体正常执行完毕,整个函数也会结束;

四、函数的参数

  我们已经把函数返回值相关的事情研究清楚了,我们自己已经完成了一个可以返回字符串长度的函数,但是现在这个函数还是不完美,之前我们使用len函数的时候得是length = len("hello world"),这样我可以想计算谁就计算谁的长度。但是现在我们写的这个函数,只能计算一个“hello world”的长度,换一个字符串好像就是不行了。这可怎么办?

#函数定义
def mylen(s1):
    """计算s1的长度"""
    length = 0
    for i in s1:
        length = length+1
    return length

#函数调用
str_len = mylen("hello world")
print('str_len : %s'%str_len)

带参数的函数
带参数的函数

  我们告诉mylen函数要计算的字符串是谁,这个过程就叫做 传递参数,简称传参,我们调用函数时传递的这个“hello world”和定义函数时的s1就是参数

1.实参和形参:

  我们调用函数时传递的这个“hello world”被称为实际参数,因为这个是实际的要交给函数的内容,简称实参。

  定义函数时的s1,只是一个变量的名字,被称为形式参数,因为在定义函数的时候它只是一个形式,表示这里有一个参数,简称形参

2.传递多个参数:

  参数可以传递多个,多个参数之间用逗号分割。

def mymax(x,y):
    the_max = x if x > y else y
    return the_max

ma = mymax(10,20)
print(ma)
传递多个参数

  也正是因为需要传递多个参数、可以传递多个参数,才会有了后面这一系列参数相关的故事。。。

3.根据实参和形参角度出来的不同参数类型:

1)位置参数:

  站在实参角度:

  a.按照位置传值:  位置形参:必选参数;位置实参:按照位置给形参传值

def mymax(x,y):
    #此时x=10,y=20
    the_max = x if x > y else y
    return the_max

ma = mymax(10,20)
print(ma)

按照位置传参
按照位置传值

  b.按照关键字传值: 无需按照位置为形参传值;

  注意的问题:关键字实参必须在位置实参右面;对同一个形参不能重复传值;并且关键字传值就相当于变量赋值,所以实参中的关键字名称一定要符合变量命名的定义;

def mymax(x,y):
    #此时x = 20,y = 10
    print(x,y)
    the_max = x if x > y else y
    return the_max

ma = mymax(y = 10,x = 20)
print(ma)

按照关键字传参
按照关键字传参

  c.位置、关键字形式混合使用

def mymax(x,y):
    #此时x = 10,y = 20
    print(x,y)
    the_max = x if x > y else y
    return the_max

ma = mymax(10,y = 20)
print(ma)

位置、关键字混用传参
位置、关键字混用传参

  正确用法:

  问题一:位置参数必须在关键字参数的前面;

  问题二:对于一个形参只能赋值一次;

  站在形参角度:

  位置参数必须传值;

def mymax(x,y):
    #此时x = 10,y = 20
    print(x,y)
    the_max = x if x > y else y
    return the_max

#调用mymax不传递参数
ma = mymax()
print(ma)

#结果
TypeError: mymax() missing 2 required positional arguments: 'x' and 'y'

位置参数必须传参
位置参数必须传参

2)默认参数:

  形参在定义时就已经为其赋值;可以传值也可以不传值,经常需要变得参数定义成位置形参,变化较小的参数定义成默认参数(形参);

  注意的问题:

  只在定义时赋值一次;默认参数的定义应该在位置形参右面;默认参数通常应该定义成不可变类型;

def stu_info(name,sex = "male"):
    """打印学生信息函数,由于班中大部分学生都是男生,
        所以设置默认参数sex的默认值为'male'
    """
    print(name,sex)


stu_info('alex')
stu_info('eva','female')

默认参数
默认参数的定义

  参数陷阱:默认参数是一个可变数据类型;

def defult_param(a,l = []):
    l.append(a)
    print(l)

defult_param('alex')
defult_param('egon')

3)动态参数(可变长参数):

  可变长指的是实参值的个数不固定,而实参有按位置和按关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整地存放它们,分别是*args,**kwargs;

  按位置传值多余的参数都由args统一接收,保存成一个元组的形式;

  *args   : 接收的是按照位置传参的值,组织成一个元组;

  **kwargs: 接受的是按照关键字传参的值,组织成一个字典;

  args必须在kwargs之前;

  动态传参的另一种方式:

  站在形参的角度上,给变量加上*,就是组合所有传来的值形成一个元组;

  站在实参的角度上,给一个序列加上*,就是将这个序列按照顺序打散,形成一个个参数传递进去;

-------------- 给形参加* ------------------------------------------
>>> def func(*args):
...     print(args)
... 
>>> func(1,2,3,4,5)
(1, 2, 3, 4, 5)


>>> def func1(**kwargs):
...     print(kwargs)
... 
>>> func1(a="A",b="b",c="C")
{'a': 'A', 'b': 'b', 'c': 'C'}


-----------------------给实参加*------------------------------------
>>> def func(a,b,c,d):
...     print(a,b,c,d)
... 
>>> func(*[1,2,3,4])
1 2 3 4
>>> func(**{"a":"A","b":"B","c":"C","d":"D"})
A B C D

4.关于函数的默认参数隐藏的巨坑

  我们先来看一个例子:

#定义一个函数并调用这个函数
def qqxing(l = []):
    l.append("zjk")
    print(l)

qqxing()
qqxing()
qqxing()
qqxing()
-------------打印结果------------------------------
['zjk']
['zjk', 'zjk']
['zjk', 'zjk', 'zjk']
['zjk', 'zjk', 'zjk', 'zjk']

#我们让第二次调用时添加一个参数[];
def qqxing(l = []):
    l.append("zjk")
    print(l)

qqxing()
qqxing([])
qqxing()
qqxing()
-------------打印结果-------------------------------
['zjk']
['zjk']
['zjk', 'zjk']
['zjk', 'zjk', 'zjk']

##那么请问,不加参数调用时和加参数调用时,每次的列表的id是否一致?

##我们再进行一次加参数和不加参数的打印,这次我们带上id
def qqxing(l = []):
    l.append("zjk")
    print(id(l))

qqxing()
qqxing()
qqxing()
qqxing()
----------------打印结果--------------------------------
38680776
38680776
38680776
38680776

##------------加参数[]-----------------------------------------
38811848
36969224
38811848
38811848

##这次大家看出结果了吧!
##说明:如果默认参数的值是一个可变数据类型,那么每一次调用函数的时候,如果不传值就公用这个数据类型的资源。
默认参数隐藏的坑

5.关于形参中各参数的顺序:

  对于形参:

  位置参数,*args,默认参数,**kwargs

  对于实参:

  位置参数,关键字参数

  

参数总结图:

  

  

  

原文地址:https://www.cnblogs.com/zhangjunkang/p/9440866.html