Python全栈之路---day04(函数初识)

函数存在的意义?

之前我们都已经熟悉的使用过python自带的len()方法用来获取字符串的长度,但设想如果某一天这个方法突然不能用了,我们应该怎么办?以计算字符串hello world的长度为例,想实现这一需求并不麻烦:

s = 'hello world'
def my_len():
    i = 0
    for k in s:
        i += 1
    return i


length = my_len()
print(length)  # 11

由此改功能得以实现,但是倘若我们想计算字符串“I love you”的长度,不得不修改S的值再进行类似的操作,虽然也能够达到目的,但总体感觉上来说总显得不够完美。为什么呢?

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

其次,之前我们只写两句话读起来也很简单,一看就知道这两句代码是在计算长度,但是刚刚的代码却不那么容易读懂 —— 可读性差

于是,我们也想定义一个类似于len()的方法用来实现我们需要经常使用的操作,等到我们需要使用的时候直接调用函数名就能执行特定的代码一般。

函数定义与调用

先附上可以实现上述需求的一个示例函数的代码:

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

函数定义:

     def 关键词开头,空格之后接函数名称和圆括号(),最后还有一个":"。

   def 是固定的,不能变,必须是连续的def三个字母,不能分开。。。它们要相亲相爱的在一起。

   空格 为了将def关键字和函数名分开,必须空(四声),当然你可以空2格、3格或者你想空多少都行,但正常人还是空1格。

   函数名:函数名只能包含字符串、下划线和数字且不能以数字开头。虽然函数名可以随便起,但我们给函数起名字还是要尽量简短,并能表达函数功能

   括号:是必须加的,先别问为啥要有括号,总之加上括号就对了!

函数调用:

      就是 函数名()     ps:要记得加上括号

注意:另外函数也有专属于他自己的注释风格

# 函数的注释
def func():
    '''
    这个函数实现了什么功能
    参数1:
    参数2:
    :return:是字符串或者列表的长度
    '''
    pass

函数的返回值

  return关键字:该词翻译过来是“返回”的意思,故我们管其后的值为“返回值”。要想弄清楚返回值,我们得先知道返回值的几种情况:没有放回值、返回一个值、返回多个值

没有返回值:

  不写return和只写return后面不写其他内容的情况下,函数都会返回一个None。你可能会因此产生疑问:既然没有要返回的值,为何嗨呀写一个return呢?这里要强调一下return的其他用法:即一旦你遇上return,就会结束整个函数,return后面的语句将不会再执行。

返回一个值:

  这种情况只需在return后面加上要返回的值即可。

    注意:return可以返回任意数据类型的值,但是return和返回值之间要留有空格。

返回多个值:

  return后面也可以返回任意多个、任意数据类型的值

def ret_demo1():
    '''返回多个值''' 
    return 1,2,3,4


def ret_demo2():
    '''返回多个任意类型的值'''
    return 1,['a','b'],3,4

ret1 = ret_demo1()
print(ret1)
ret2 = ret_demo2()
print(ret2)

  返回的多个值会被组织成元组被返回,也可以用多个值来接收

def ret_demo2():
    return 1,['a','b'],3,4

#返回多个值,用一个变量接收
ret2 = ret_demo2()
print(ret2)

#返回多个值,用多个变量接收
a,b,c,d = ret_demo2()
print(a,b,c,d)

#用多个值接收返回值:返回几个值,就用几个变量接收
a,b,c,d = ret_demo2()
print(a,b,c,d)

  PS:在python中,用逗号分割的多个值就被认为是一个元祖。

函数的参数

   本篇开始就说到如果有需求想要计算另外字符串的长度,但又不想重复编写复杂的代码的时候,我们应该怎么做?其实只要定义一个带有参数的模板,将要计算的字符串赋给这个参数传入函数进行计算即可。这个过程就被称为传递参数,简称传参

形参和实参

  传参的过程种又有实参和形参的区别:

  实参,全称为实际参数,简单的说就是我们想要进行处理的值,也是后续过程实际要交给函数的内容。

  形参,全称为形式参数,只是一个变量名,在定义函数的时候它只是一个形式,表示这里有一个参数。

传递多个参数

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

  正因为多个参数的存在,才有这后续的一系列操作。。。

位置参数

站在实参角度

1、按照位置传值

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) #20

2、按照关键字传值

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) #20

3、位置、关键字形式混合传值

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)    #20

     注意:在位置和关键字两种形式混合使用的情况下,必须遵循“一个形参赋值一次,位置参数必须在关键字参数前面”的原则。

默认参数

由来:传参过程中㝉出现某些值重复出现的情况,此时将变化比较小的值设置成默认参数。

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


stu_info('凝宝')  #凝宝 male
stu_info('柳柳','female') #柳柳 female

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

#如果默认参数的值是一个可变数据类型,
# 那么每一次调用函数的时候,如果不传值就共用这个数据类型的资源
def qqxing(l=[]):
    l.append(1)
    print(l)

qqxing()#[1]
qqxing([])#[1]
qqxing()#[1, 1]
qqxing()#[1, 1, 1]

动态参数

可以接受任意多个参数
两种:
*args:接收的是按照位置传参的值,组织成一个元组
**kwargs:接收的是按照关键字传参的值,组织成一个字典
args必须在kwargs之前
def sum(*args):
    n = 0
    for i in args:
        n += i
    return n


print(sum(1, 2))  # 3
print(sum(1, 2, 3))  # 6
print(sum(1, 2, 3, 4))  # 10
def func(**kwargs):
    print(kwargs)


func(a=1, b=2, c=3)  # {'a': 1, 'b': 2, 'c': 3}
func(a=1, b=2)  # {'a': 1, 'b': 2}
func(a=1)  # {'a': 1}
def func1(*args, **kwargs):
    print(args, kwargs)


func1(1, 2, 3, 4, 5, a='adsd', b='cda')  # (1, 2, 3, 4, 5) {'a': 'adsd', 'b': 'cda'}
总顺序:位置参数,*args,默认参数,**kwargs

命名空间和作用域

命名空间

命名空间的本质:存放名字与值的绑定关系

命名空间一共分为三种:

内置命名空间--python解释器

   就是python解释器一启动就可以使用的名字存储在内置明明空间中
内置的名字在启动解释器的时候加载进内存

全局命名空间--我们写的代码但不是函数中的代码

   程序在执行过程中依次加载进内存
放置了我们设置的所有变量名和函数名

局部命名空间--函数

   就是函数内部定义的名字
当调用函数的时候才会产生这个名称空间,随着函数执行的结束,这个明明空间就又消失了

三种命名空间之间的加载与取值顺序:

加载顺序:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)
取值:在局部调用:局部命名空间->全局命名空间->内置命名空间

   在全局调用:全局命名空间->内置命名空间
#在局部:可以使用全局、内置命名空间中的名字
#在全局:可以使用内置命名空间的名字,但是不能使用局部的
#在内置:不能使用局部和全局的名字的
#范围:内置>全局>局部(小范围可以使用大范围)

#在正常情况下,直接使用内置的名字
#当我们在全局中定义了与内置同名的名字时会使用全局的名字
#多个函数应该拥有多个独立的局部名字空间,不互相共享 

作用域

全局作用域-->作用在全局:内置和全局名字空间中的名字都属于全局作用域
局部作用域-->作用在局部:函数(局部命名空间中的名字都属于局部作用域) locals()查看

对于不可变数据类型:在局部可以查看全局作用域中的变量,但是不能直接修改。如果想要修改,需要在程序的一开始添加global声明
如果在一个局部(函数)内声明了一个global变量,那么这个变量在局部的所有操作将对全局的变量有效
a=1
def fun():
    global a
    a+=1

fun()
print(a)    #2
a=1
b=2
def fun():
    x='aaa'
    y='bbb'
    print(locals())#{'x': 'aaa', 'y': 'bbb'}
fun()
print(globals())#{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000016EB4011CC0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/Python/day07/函数进阶.py', '__cached__': None, 'func1': <function func1 at 0x0000016EB531A9D8>, 'a': 1, 'b': 2, 'fun': <function fun at 0x0000016EB531AA60>}
print(locals())#本地的
#globals永远打印全局的名字
#locals 输出什么 根据locals所在的位置
 函数的嵌套和作用域链
 函数的嵌套调用
def max(a,b):
    return a if a>b else b

def my_max(x,y,z):
    c=max(x,y)
    return max(c,z)

print(my_max(1,2,3))    #3
函数的嵌套定义
def f1():
    print("in f1")
    def f2():
        print("in f2")

    f2()
f1()
函数的嵌套定义(一)

def f1():
    def f2():
        def f3():
            print("in f3")
        print("in f2")
        f3()
    print("in f1")
    f2()
    
f1()
函数的嵌套定义(二)

 

  函数的作用域链

def f1():
    a = 1
    def f2():
        print(a)
    f2()

f1()
作用域链(一)

 

def f1():
    a = 1
    def f2():
        def f3():
            print(a)
        f3()
    f2()

f1()
作用域链(二)
def f1():
    a = 1
    def f2():
        a = 2
    f2()
    print('a in f1 : ',a)

f1()
作用域链(三)

 

nonlocal:只能用于局部变量,找上一层中离当前函数最近一层的局部变量  -->对全局无效,对局部也只是对最近的一层有影响
声明了nonlocal的内部函数的变量修改会影响到离当前函数最近一层的局部变量
a=1
def outer():
    a=1
    def inner():
        a=2
        print(a)
        print("inner")
        def inner2():
            nonlocal a #声明了一个上面第一层局部变量
            a+=1
            print('inner2')
        inner2()
        print("##a##",a)
    inner()
    print("**a**:",a)
outer()
print("**a**:",a)

函数名的本质

函数名本质上就是函数的内存地址

1、可以被引用

def func():
    print('in func')

f = func
print(f) #<function func at 0x000001DBF88930D0>

2、可以被当做容器类型的元素

def f1():
    print('f1')


def f2():
    print('f2')


def f3():
    print('f3')

l = [f1,f2,f3]
d = {'f1':f1,'f2':f2,'f3':f3}
#调用
l[0]()
d['f2']()

3、可以当做函数的参数和返回值

def func():
    print(123)

def wahaha(f):
    f()
    return f        #函数名可以作为函数的返回值

qqxing=wahaha(func)    #函数名可以作为函数的参数   123
qqxing()

 

闭包

闭包函数:

内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数
#函数内部定义的函数称为内部函数

def outer():
    a=1
    def inner():
        print(a)
    print(inner.__closure__) #(<cell at 0x0000029E98BC7678: int object at 0x00007FFADB639340>,)
outer()

 如上述代码中,inner函数即为闭包函数,可用__closure__方法进行判断

闭包函数常用方法

def outer():
    a=1
    def inner():
        print(a)
    return inner
inn=outer()
inn()  

闭包的嵌套:

def wrapper():
    money = 1000
    def func():
        name = '凝宝'
        def inner():
            print(name,money)
        return inner
    return func

f = wrapper()
i = f()
i()

 

猜你喜欢

转载自www.cnblogs.com/zhuoning/p/11368084.html
今日推荐