python基础知识——函数

一.函数是什么

    函数在数学中表示一种“映射关系”,在中学中学生们会接触到各种各样的函数,例如:三角函数(sin, cos, tan),幂函数,对数函数。而在编程中,函数表示一段可以被重复使用的代码?

    重复使用?那为什么不 ctrl + c, ctrl + v呢?这样不是更方便吗?

1.复制代码,必须在细节上进行调整,多次尝试,在数据不同时尤为明显。

2.一旦复制的代码需要调整,复制了几次就要调整几次,很容易漏掉。

    因此,尽量不要复制代码,而是做好代码“重用”。

    我们引入一个代码案例,用于代码求和。

# 1. 求 1-100 的和
theSum = 0
for i in range(1,101):
    theSum += i


print(theSum)

# 2. 求 300-400 的和
theSum = 0
for i in range(300, 401):
    theSum += i

print(theSum)

# 3. 求 1-1000 的和
theSum = 0
for i in range(300, 401):
    theSum += i

print(theSum)

# 定义一个求和函数
def calcSum(beg, end):
    theSum = 0
    for i in range(beg, end + 1):
        theSum += i
    print(theSum)
    
    
#调用函数
#求 1-100 的和
calcSum(1, 100)
#求 300-400 的和
calcSum(300, 400)
#求 1-1000 的和
calcSum(1, 1000)

    从中我们不难看出,如果是复制代码我们需要反复修改参数,而定义一个函数只需要传入参数即可,后续我们修改函数,所有的调用也会同步修改。

二.函数的定义和调用

1.函数的定义(分配任务)

格式:

def  函数名(形参列表)

       函数体

       return 返回值

def:define(定义)

形参列表:形参列表中,可以有多个形参(形式参数),多个形参之间可以使用 逗号 分隔。

函数体:函数体要带有一级缩进~(带有缩进的代码,才是函数内部的语句)

返回值:函数执行到 return 就意味着执行完了,return后面的值,就是函数的返回值~ return 语句                并不是必须的,可以有也可以没有。

2.函数的调用(开始完成任务

函数名(实参列表)

实际参数:简称实参。此处的实参的个数要和形参的个数相匹配。

返回值 = 函数名(实参列表)

# 如果是定义,而不去调用,则函数体里面的代码就不会执行
def test():
    print('hello')
    print('hello')
    print('hello')


# 函数调用才会真正执行函数体里面的代码
# 函数经过一次定义之后,可以被调用多次!
test()
test()
test()
test()

三.函数的参数

举个例子:

    我有一个朋友 (函数的形参

    高中的时候,是一个学霸,同桌是一个非常漂亮也很可爱的女生,他们的关系非常好,但是因为一些原因,最终没能够走到最后。

   这个朋友,就是XXX(此处为人名,也就是函数的实参

   函数的调用是可以有很多次的,同时每次调用的实参,也可以是可以不同的。函数的实参,就是在函数调用的时候,赋值给形参~

关于传递参数的几个问题:

1)少传递参数:

def test(a, b, c):
    print(a, b, c)


test(10)

 

 形参有三个,传递参数时就要传递三个进去,否则就会报错。

2)函数中可以传递各种参数类型,只要满足函数体里卖弄的操作就行。

def add(x, y):
    return x + y


print(add(10, 20))
print(add(1.5, 2.5))
print(add('hello', 'world'))

四.函数的返回值

1.什么是返回值  

  要理解函数的返回值,我们首先要理解输入输出的概念,广义上的输入输出并不仅仅是从键盘输入,也不是从显示器输出。

    举个工厂制造手机的例子:

        原材料:芯片,电池,摄像头,外壳......[输入]

        产品:功能完备的手机。                        [输出]

    也就是吃进去的是草(输入),挤出来的是奶(输出)。

2.耦合

我们引入两段代码:

def calcSum(beg, end):
    theSum = 0
    for i in range(beg, end + 1):
        theSum += i
    print(theSum)


calcSum(1, 100)

 

def calcSum(beg, end):
    theSum = 0
    for i in range(beg, end + 1):
        theSum += i
    return theSum


result = calcSum(1, 100)
print(result)

    改进之后的代码,在 calcSum 函数内部,只是进行了计算,而把打印的逻辑放到了函数的外面,calcSum 把计算结果当作返回值,返回给“函数调用者”,即把函数里面计算好的 5050 赋值给了 result 变量。 

    在实际开发中,一般更倾向于第二种写法!

    一个通用的编程规则:一个函数只做一件事!

        第一个版本,既做了计算,又做了打印(和用户交互)

        第二个版本,只是做了计算,不关心如何和用户交互!

    一旦后续需要改变和用户交互的方式,第二种写法就更有优势(不必修改代码),让 逻辑和交互/界面分离-->进一步的好处:解耦合。

    一个稍微复杂一些的程序中,经常会涉及多个模块,模块之间可能要进行交互,交互就会带来耦合,当然希望通过良好的设计让耦合尽量降低。 

举个例子:

    我和我女朋友,我们的耦合就比较强,一方产生变动,对另一方,影响就比较大。

    我和我的女神,耦合几乎为 0,最多是点赞之交。

    如果女神生病了,对我来说没啥影响,如果我女朋友生病了,那对我的影响就大了。

3.多个返回值

     一个函数可以有多个返回值。

def test():
    return 1
    return 2

# 一般多个 return 语句是搭配 分支语句 / 循环语句的

 

def isOdd(num):
    '''
    用来判定 num 是不是奇数! 如果是奇数就返回 True, 不是就返回 False
    :param num: 要判定的整数
    :return: 返回 True False 表示是不是奇数
    '''
    if num % 2 == 0:
        return False
    return True


def isOdd(num):
    '''
    用来判定 num 是不是奇数! 如果是奇数就返回 True, 不是就返回 False
    :param num: 要判定的整数
    :return: 返回 True False 表示是不是奇数
    '''
    if num % 2 == 0:
        return False
    else:
        return True


print(isOdd(10))
print(isOdd(19))

     这两段代码逻辑是等价的!当函数执行到 return 的时候就不再继续执行下去了,而是回到了调用位置~~(函数结束了)

    把 return True 写到 if 外面,意味着 条件是否满足 都会执行 return True 但是再仔细观察,就发现 条件满足之后,if 里面有一个 return 了。一旦条件满足后,是没有机会执行到 return True。

    Python 中的一个函数可以返回多个值!这是一个非常香的特性,能馋哭C++和Java。对于他俩而言调用一个函数一次只能返回一个值,C++要想返回多个值,可以通过输出型参数(指针/引用),Java中要想返回多个值,需要把多个值给包装成一个对象,返回这个对象。

def getPoint():
    x = 10
    y = 20
    return x, y


_, b = getPoint()
print(_, b)

    虽然现在返回了多个值,但是我只想用其中的一部分,不关注其他的~~,可以使用 _ 来进行占位!

    不要 x 了,只要 y ,把 y 赋值给 b 即可!

五.函数的作用域

1.作用域 

def getPoint():
    x = 10
    y = 20
    return x, y


x, y = getPoint()
print(x, y)

    函数里面的 x y 和函数外面 x y 是同一组变量吗?

    他们是不同的变量,只不过名字恰好相同!

        就像我管我女朋友叫“宝贝”,我的朋友也管他的女朋友叫做“宝贝”。

    这就叫做变量的作用域,一个变量名的有效范围是一定的!只在一个固定的区域内生效。

    所以函数内部的变量名,这能在函数内部生效,出了函数,就无效了。函数内部和函数外部是可以使用同名变量的,虽然变量名相同,但是是不同的变量。

2.局部变量和全局变量

x = 10


def test():
    x = 20
    print(f'函数内部:{x}')
    

test()
print(f'函数外部:{x}')

    上述代码的 x = 10 为全局变量,是在整个程序中都是有效的。

    而 x = 20 是局部变量,只是在函数内部有效的。

    函数里,也是可以使用全局变量的。在函数里尝试读取全局变量,是可以的!当函数中尝试访问某个变量时,会先尝试在局部变量中查找,如果找到,就直接访问,如果没找到,就会往上一级作用域中进行查找,test 再往上一级作用域,就是全局了~~

x = 10


def test():
    print(f'x:{x}')


test()

 

    global 关键字可以帮助我们在函数中修改全局变量的值。

x = 10


def test():
    global x
    x = 20


test()
print(f'x = {x}')

    没有 global ,此时就会把 x = 20 当作是在函数内部创建一个局部变量 x 而实际上是要修改全局变量 x ,为了让 函数里面 知道 x 是个全局变量,就是用 global 关键字先声明一下。

3.其他代码块中的作用域

     if, else, while, for 这些关键字也会引入“代码块”,但是这些代码块不会对变量的作用域产生影响。在上述语句代码块内部定义的变量,可以在外面被访问。

for i in range(1,5):
    print(i)
print('---------------')
print(i)

if True:
    x = 10

print(x)

 

六.函数的执行过程

def test():
    print('执行函数体代码')
    print('执行函数体代码')
    print('执行函数体代码')


print('111111')
test()
print('222222')
test()
print('333333')

 def test():

         print('执行函数体代码')

         print('执行函数体代码')

         print('执行函数体代码')

    执行这几段代码,只是在定义函数,不会执行函数体的内容。

    执行到函数调用的时候,就会跳转到函数体内部来进行执行,当函数内部执行完毕(运行完了/遇到 return),就回到之前调用的位置,继续往下执行。

    为了更好的观察函数的执行过程,我们可以画图,也可以使用调试器。调试执行,相比于正常的运行,最大的区别,可以随时停下来,方便程序员观察程序的中间过程。

    先给第一个“print”这里加一个断点,然后进行调试执行,在断点处,程序就暂停了~就可以让代码“单步执行”。

    单机左侧的行数即可打上断点。 

    上述图片蓝色高亮部分,为函数的调用栈,描述了当前代码是怎么跳转过去的(进一步的也就是函数之间的调用关系)。 

七.函数的链式调用和嵌套调用

1.链式调用

def isOdd(x):
    x = x


def add(a, b):
    return a + b


print(isOdd(10))
print(isOdd(add(5, 5)))

    用一个函数的返回值,作为另一个函数的参数!

    链式调用中,是先执行()里面的函数,后执行外面的函数。换句话说,调用一个函数,就需要先对他的参数求值。 

2.嵌套调用

def a():
    print('函数 a')


def b():
    print('函数 b')
    a()


def c():
    print('函数 c')
    b()


def d():
    print('函数 d')
    c()


d()

     上述代码为函数的嵌套调用,就是在函数体的内部调用其他的函数。

七.局部变量和栈帧

def a():
    num = 10
    print('函数 a')


def b():
    num = 20
    a()
    print('函数 b')


def c():
    num = 30
    b()
    print('函数 c')


def d():
    num = 40
    c()
    print('函数 d')


d()

 

    

    调试器的左下角,能够看到函数之间的“调用栈”,“调用栈”里面描述了当前这个代码的函数之间的调用关系是啥。每一层这个调用关系就称为“函数的栈帧”,每个函数的局部变量就在这个栈帧中体现的。 

   每一层栈帧,你选中之后,都能看到里面的局部变量,每个函数的局部变量就保存在对应的栈帧中。调用函数,则生成对应的栈帧,函数结束,则对应的栈帧消亡(里面的局部变量也就没了)。

    上述函数中每个变量都同名,但是是不同变量的,属于不同的函数作用域。每个变量也是保存在各自栈帧中的(每个栈帧也是保存在内存上的)。 变量的本质就是一块内存空间。

八.函数递归

1.什么是递归    

    函数递归,就是一个函数,自己调用自己。

从前有座山

山里有座庙

庙里有个老和尚

老和尚给小和尚讲故事:从前有座山

                                        山里有座庙

                                       庙里有个老和尚

                                       老和尚给小和尚讲故事:从前有座山

                                                                               山里有座庙

                                                                               庙里有个老和尚

                                                                               老和尚给小和尚讲故事:.......

我们写一个函数,来求 n 的阶乘。

def factor1(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result


def factor2(n):
    if n == 1:
        return 1
    return n * factor2(n - 1)


print(factor1(5))
print(factor2(5))

    第二个函数就使用了函数的递归,函数中的 n 虽然都是 n ,但是 n 是函数形参,形参相当于函数的局部变量,局部变量在函数自己的栈帧上,虽然两个函数的局部变量名相同,但是不同的栈帧是不同的内存空间,也就是不同的变量了。(另一方面,看起来是同一个函数,但是这里的两次调用,其实是两个栈帧)。 

    递归的代码,不会无限的执行下去,会在每一次递归的时候,逐渐逼近递归的结束条件(if n == 1)。

    递归的代码,虽然很多时候看起来写法很简单,但是执行过程可能会非常复杂!!在分析递归代码的时候,光用脑子想,太困难。所以可以画图,可以调试。

    递归的代码要有两个要素:

    1).递归结束条件

    2).递归的递推公式

2.递归的缺点

1)执行的过程非常复杂,难以理解。

2)递归代码容易出现“栈溢出”的情况(代码不小心写错了,导致每次递归,参数不能正确的接近         递归结束条件,出现无限递归的情况)。

3)递归代码一般都是可以转换成等价的循环代码的。并且,循环的版本通常运行速度要比递归的       版本有优势。(函数的调用也是有开销的)。

3.递归的优点

    代码非常简洁,尤其处理一些本身就需要通过递归方式来处理的问题(数据结构二叉树)。

九.函数形参的默认值

def add(x, y):
    print(f'x = {x},y = {y}')
    return x + y


result = add(10, 20)
print(result)

    在函数内部加上打印信息,方便我们进行调试。但是,像这种调试信息,希望在正式发布的时候不要有,只是在调试阶段才有。

def add(x, y, debug = False):
    if debug:
        print(f'x = {x},y = {y}')
    return x + y


result = add(10, 20, True)
print(result)

    形参的默认值

    带有默认值的形参,就可以在调用函数的时候不必传参,通过这样的设计,就可以在函数的设计更灵活。(像默认值这样的语法,在编程界存在争议(C++支持,Java不支持))

    同时,要求带有默认值的形参,得在形参列表的后面,而不能在前面/中间。

    def add(x, debug = False, y): 这样的写法并不正确。多个带有默认参数的形参,这些都得放在后面!!

十.函数的关键字传参

1.位置传参:add(10, 20),按照先后顺序来传参,这种传参风格,称为“位置参数”。这是各个编程语言中最普遍的方式。

2.关键字传参:按照形参的名字来进行传参!

    test(x = 10, y = 20),非常明显的告诉程序猿,你的参数要传给谁。另外可以无视形参和实参的顺序!

    位置参数和关键字参数,还能混着用,只不过混着用的时候要求位置参数在前,关键字参数在后。关键字参数,一般也就是搭配默认参数来使用的。

    一个函数,可以提供很多的参数,来实现对这个函数的内部功能做出一些调整设定,为了降低调用者的使用成本,就可以把大部分参数设定出默认值。当调用者需要调整其中的一部分参数时候,就可以搭配关键字参数来进行操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值