一、函数(function)的简介

函数也是一个对象。对象是内存中专门用来存储数据的一块区域。
函数用来保存一些可执行的代码,并且可以在需要的时候对这些语句多次调用。

函数名是函数的对象,函数名+()是调用函数执行。

'''
函数简介:
函数也是一个对象;
对象是内存中专门用来存储数据的一块区域。
函数用来保存一些可执行的代码,并且可以在需要的时候对这些语句多次调用。

语法:
def 函数名([形参1,形参2,形参3...]):
    代码块
'''

# 函数名是一个对象,跟变量一样,用来存储数据的地址,fn()调用函数

def fn():
    print('这是一个函数')

print(fn) #打印地址变量fn的内存地址
#返回:<function fn at 0x000002879FB7C9D8>

f=fn #在这里fn是函数对象
f()#调用fn函数,并执行代码

二、函数的参数

在定义函数的时候,可以在函数名后面的括号里面定义数量不等的形参;多个形参我们用逗号隔开;

形参(形式参数)定义形参就是相当于在函数内部定义了变量,但是并没有赋值;

实参(实际参数)函数定义时指定了形参,那么调用函数的时候也必须传递实参;实参将会赋值给对应的形参,有几个形参就有几个实参。

'''
函数的参数:
在定义函数的时候,可以在函数名后面的括号里面定义数量不等的形参;
多个形参我们用逗号隔开;
形参(形式参数)定义形参就是相当于在函数内部定义了变量,但是并没有赋值;
实参(实际参数)函数定义时指定了形参,那么调用函数的时候也必须传递实参;
实参将会赋值给对应的形参,有几个形参就有几个实参。
'''
def fn2(a,b):#a,b为形参
	print('a=',a)
	print('b=',b)

fn2(10,11) #实参,实参个数与形参个数相同

三、函数的传递方式

1、定义形参的时候我们可以为形参指定一个默认值,指定了默认值以后,如果用户传递了参数,默认值不会有任何作用;如果用户没有传递,默认值才会生效。

2、位置传参数:
位置传参就是将对应位置的实参传递给对应位置的形参;

3、关键字传参:
关键字传参可以不按照关键字定义的顺序的传参数,而是根据参数名来传递参数;

4、位置传参和关键字传参可以混合使用
混合使用的时候,必须将位置参数写到关键字参数前面

'''
函数的传递方式:
1、定义形参的时候我们可以为形参指定一个默认值,
指定了默认值以后,如果用户传递了参数,默认值不会有任何作用;
如果用户没有传递,默认值才会生效。

2、位置传参数:
位置传参就是将对应位置的实参传递给对应位置的形参;

3、关键字传参:
关键字传参可以不按照关键字定义的顺序的传参数,而是根据参数名来传递参数;

4、位置传参和关键字传参可以混合使用
混合使用的时候,必须将位置参数写到关键字参数前面
'''
def fn(a=10,b=50,c=4):
    print(a,b,c)

#位置传参
fn(1,2,3)

fn(1,2)

#关键字传参
fn(b=3,c=6,a=1)

#位置传参和关键字传参可以混合使用
#混合使用的时候,必须将位置参数写到关键字参数前面
fn(1,c=6,b=1)

四、实参的类型

函数在调用的时候,解释器不会检查实参的类型;实参的类型可以使任意的对象。

def fn(a=10,b=50,c=4):
    print(a,b,c)

'''
实参的类型:
函数在调用的时候,解释器不会检查实参的类型;
实参的类型可以使任意的对象
'''
def fn2(a):
    print('a=',a)

b=fn

fn2(b) #a= <function fn at 0x0000020F047BCB88>

def fn3(a):
    #在函数中对形参重新赋值不会影响其他的变量
    a=30 #形参变量的地址跟实参的地址不一样
    print('a=',a,'id(a)=',id(a))

c=10
#两个变量的地址不一样
fn3(c) #a= 30 id(a)= 140715752072496
print('c=',c,'id(c)=',id(c))#c= 10 id(c)= 140715752071856


def fn4(a):
    #如果形参在执行的时候,当我们通过形参去修改对象的值时,
    #会影响到指向该对象的变量
    a[0]=30
    print('a=',a,'id(a)=',id(a))

c=[1,2,3]
#两个变量的地址是一样的
fn4(c) #a= [30, 2, 3] id(a)= 2263493661128
print('c=',c,'id(c)=',id(c))#c= [30, 2, 3] id(c)= 2263493661128

五、不定长参数

1、在定义函数的时候,可以在形参的前面加上一个*,这样这个形参将会获取所有的实参,它将所有的实参保存到一个元组当中。

2、**形参可以接收其他的关键字参数,他会将这些参数统一保存到一个字典当中。

'''
不定长参数:
定义一个函数来实现任意数的和

1、在定义函数的时候,可以在形参的前面加上一个*,
这样这个形参将会获取所有的实参,它将所有的实参保存到一个元组当中。

2、**形参可以接收其他的关键字参数,他会将这些参数统一保存到一个字典当中。

3、总结
一个*解决位置参数;
两个*解决关键字参数。
'''
def s(*a):
    print('a=',a,type(a))#返回a= (1, 1, 2, 3, 4, 5, 6, 7, 9) <class 'tuple'>
    #定义一个变量来保存结果
    r=0
    #遍历元组,将元组中的元素取出来并进行累加
    for n in a:
        r+=n
    print(r)

s(1,1,2,3,4,5,6,7,9)

#可变长参数不是必须要写到最后的,
#但是注意,带*参数后面的参数,必须以关键字的形式传参
def s(b,*a,c=10):
    print('a=',a,type(a))
    #定义一个变量来保存结果
    r=0
    #遍历元组,将元组中的元素取出来并进行累加
    for n in a:
        r+=n
    print(r,'c=',c)

s(1,1,2,3,4,5,6,7,9)#返回a= (1, 2, 3, 4, 5, 6, 7, 9) <class 'tuple'>,37 c= 10,c值没有更改,还是用默认值

s(1,1,2,3,4,5,6,7,9,c=100)#返回a= (1, 2, 3, 4, 5, 6, 7, 9,c=100) <class 'tuple'>,37 c= 100,c值发生了更改


#**形参可以接收其他的关键字参数,他会将这些参数统一保存到一个字典当中。
#字典当中的key是参数的名字,字典当中的value是参数的值
#**形参只能有一个,必须写在最后
def ss(b,c,**a):
    print('a=',a,type(a))

    print('b=', b)
    print('c=', c)

ss(b=1,d=2,c=3,e=4,f=5)
'''
返回值:
a= {'d': 2, 'e': 4, 'f': 5} <class 'dict'>
b= 1
c= 3
'''

六、参数解包

序列都可以用一个来解包;字典不是序列,字典的解包用两个

'''
参数解包
序列都可以用一个*来解包;
字典不是序列,字典的解包用两个*。

'''
def fns(a,b,c):
    print('a=', a)

    print('b=', b)
    print('c=', c)


t=(5,6,7)
print(*t)#参数的解包,返回:5 6 7
fns(*t)#序列中的元素必须与形参的长度保持一致

tta={'a':1,'b':2}
print(**tta) #解包,返回字典的键:a= 5 b= 6 c= 7

七、函数的返回值

返回值就是函数执行后返回的结果。

用return来指定函数的返回值,return后面可以跟任意对象,甚至可以是一个函数。
如果仅仅只写一个return或者不写return,则相当于return None。
在函数中,return后面的代码不会执行,return一旦执行,函数自动结束。

我们可以直接使用函数的返回值,也可以通过变量来接收函数的返回值。

'''
求任意数的和
'''

def s(*num):
    #定义一个变量来保存结果

    result=0

    #遍历元组,并将元组当中的元素进行累加

    for n in num:
        result+=n

    print(result)

s(1,2,3,4,5,6)

def fn():
    #用return来指定函数的返回值
    #return后面可以跟任意对象,甚至可以是一个函数。
    return 100

#我们可以直接使用函数的返回值,也可以通过变量来接收函数的返回值。
r=fn() #这个函数执行的结果就是返回值的结果。
print(r)

def fn():

    def fn2():
        print('hello')

    return fn2 #返回这个函数时不需要加括号。调用函数才需要加括号

r=fn()
print(r)#返回<function fn.<locals>.fn2 at 0x000002B67F44CC18>;r在此处是一个函数

r() #返回hello
def fn3():
    #如果仅仅只写一个return或者不写return,则相当于return None。
    print('python')
    print('java')

r=fn3()

print(r) #因为没有写return的返回值,
'''
返回值:
python
java
None
'''
def fn3():
    #如果仅仅只写一个return或者不写return,则相当于return None。
    #在函数中,return后面的代码不会执行,return一旦执行,函数自动结束。
    print('python')
    return
    print('java')

r=fn3()

print(r) ##在函数中,return后面的代码不会执行,return一旦执行,函数自动结束。
'''
返回值:
python
None
'''
def fn4():
    for i in range(10):
        if i==3:
            break #执行到i=3时,循环中没有return函数时,可以打印后面的‘循环执行完毕’
        if i==4:
            continue #跳过本次循环,后面的继续运行

        if i==5:
            return #return可以用来结束函数,后面的循环不在执行,也不会打印‘循环执行完毕’
        print(i)

    print('循环执行完毕')

fn4()
def fn5():
    return 1

print(fn5)   #返回<function fn5 at 0x0000015D30A3CF78>;是函数对象
print(fn5()) #返回1,fn5()是调用函数,打印的是函数的返回值

八、文档字符串

'''
文档字符串
help()函数,可以查询其他函数的用法;
语法:help(函数对象)

'''

help(print)
'''
返回值:
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.

'''

def fn(a,b,c):
    '''
    :param a: 类型 int 默认值 ...
    :param b: 类型 str 默认值...
    :param c: 类型 bool 默认值...
    :return: int
    '''

    return 1

help(fn)
'''
返回值:
Help on function fn in module __main__:

fn(a, b, c)
    :param a: 类型 int 默认值 ...
    :param b: 类型 str 默认值...
    :param c: 类型 bool 默认值...
    :return: int
'''

def fn2(a:int,b:str,c:bool)->int:#很少使用
    '''
    :param a: 类型 int 默认值 ...
    :param b: 类型 str 默认值...
    :param c: 类型 bool 默认值...
    :return: int
    '''

    return 1

help(fn2)
'''
返回值:
Help on function fn2 in module __main__:

fn2(a: int, b: str, c: bool) -> int
    :param a: 类型 int 默认值 ...
    :param b: 类型 str 默认值...
    :param c: 类型 bool 默认值...
    :return: int
'''

九、函数的作用域

作用域指的就是变量生效的区域。

全局作用域:在程序执行时创建,在程序执行结束时销毁;所有函数以外的部分都是全局作用域,在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置访问。

函数作用域:在函数调用时创建,在调用结束后销毁,函数每调用一次就会产出一个新的作用域;在函数作用域中定义的变量都是局部变量,它只能在函数内部被访问到。

'''
作用域指的就是变量生效的区域。
'''
b=20
def fn():
    a=10#a定义在函数内部,所以它的作用域是函数内部,函数外部是访问不到的
    print('函数内部:','a=',a)
    print('函数内部:', 'b=', b)


fn()

print('函数外部:','b=',b)
'''
返回值:
函数内部: a= 10
函数内部: b= 20
函数外部: b= 20
'''

def fn2():
    
    a=30

    def fn3():#fn3中可以访问到fn2中的变量,fn2的变量相当于fn3的全局变量
        print('fn3中:','a=',a)

    fn3()

fn2()

python中fn函数 fn python_作用域

'''
在函数内部修改全局变量的方法:global
'''
a=20
def fn3():
    #如果希望在函数内部修改全局变量,则需要使用global关键字,来声明
    global a #声明在函数内部使用的a是全局变量,再去修改a,是修改的全局变量a
    a=10
    print('函数内部:','a=',a)

fn3()
print('函数外部:','a=',a)
'''
返回值:
函数内部: a= 10
函数外部: a= 10
'''

十、命名空间

命名空间实际上就是一个字典,是一个专门用来存储变量的字典。

locals()用来获取当前作用域的命名空间,如果在全局作用域中调用locals(),则获取的是全局命名空间,如果在函数作用域中调用locals(),则获取的是函数的命名空间,返回的是一个字典。

s=locals() #当前的命名空间

s['c']=100 #向字典中添加key-value,就相当于在全局中创建了一个变量
print(c)#返回:100
print(s)#返回:{...,'s': {...}, 'c': 100}

def fn4():

    s=locals()#在函数内部调用locals()会获取到函数的命名空间

    s['c']=20

    print(s) #返回:{'c': 20}

fn4()
'''
在函数中获取全局的命名空间。
'''
s=locals() #当前的命名空间

s['c']=100 #向字典中添加key-value,就相当于在全局中创建了一个变量,c=100

def fn4():
    a=10
    #global()函数可以用来在任意位置获取全局命名空间

    global_space=globals()

    print(global_space)#返回{..., 's': {...}, 'c': 100, 'fn4': <function fn4 at 0x000002932F7BC9D8>}

fn4()

十一、递归式函数

递归简单的理解就是自己调用自己;
递归式函数就是在函数中自己调用自己。

递归式函数有两个条件:
1、基线条件:
问题可以被分解为最小的问题,当满足基线条件的时候,递归就不再执行了。
2、递归条件:
将问题可以分解的条件。

'''
举例:求10的阶乘。
'''
#方法一:循环方法
def func(n):
    result=n
    for i in range(1,n):
        result*=i
    return result

print(func(10))

# #无穷递归,类似于死循环
# def fn():
#     fn()
# fn()

#方法二:递归方法
def fn(n):
    #基线条件,判断n是否是1,如果是1不能再继续递归。
    if n==1:
        return 1
    #递归条件
    return n*fn(n-1)

print(fn(10))

递归的练习:

'''
练习一:创建一个函数,来为任意数字做幂运算,n**i。
'''
def fn1(n,i):
    #参数 n:做幂运算的数字,i做幂运算的次数

    #基线条件:
    if i==1:
        #求一次幂运算
        return n
    #递归条件:
    return n*fn1(n,i-1)

print(fn1(3,2))

'''
练习二:创建一个函数,用来检测任意字符串是否是回文字符串,
如果是返回True,如果不是返回False

什么是回文字符串?
回文字符串指的是字符串从后往前读和从前往后读是一样的,例如abcba
'''
def fn2(string):
    #充分利用字符串的首尾...
    #基线条件
    if len(string)<2:
        return True
    elif string[0] != string[-1]:
        return False
    #递归条件
    return fn2(string[1:-1])

print(fn2('avava'))

十二、高阶函数

1、定义

高阶函数:满足以下2个条件中的任意一个都是高阶函数。

(1)接收函数作为参数的函数;
(2)将函数作为返回值的函数;

'''
高阶函数:满足以下2个条件中的任意一个都是高阶函数。

1、接收函数作为参数的函数;
2、将函数作为返回值的函数;

高阶函数的好处:
当我们使用一个函数作为参数时,实际上,我们就是将指定的代码传递给了目标函数。
'''

l=[1,2,3,4,5,6,7,8,9,10]

#定义一个函数,可以将制定的列表的所有的偶数保存到一个新的列表并返回。

def fn(lst):

    #创建一个新的列表
    new_lst=[]

    for n in lst:

        #判断奇数和偶数
        if n%2==0:
            new_lst.append(n)

    return new_lst

print(fn(l))

'''
函数内部定义函数
'''
def fn1(lst):

    #函数中再定义一个函数:用来检测任意数的偶数;
    def fn2():

        if n%2==0:
            return True

    #函数中再定义一个函数:用来检测指定数字是否大于5;
    def fn3():

        if n > 5:
            return True
    #创建一个新的列表
    new_lst=[]

    for n in lst:

        #判断奇数和偶数
        if not fn3():
            new_lst.append(n)

    return new_lst

print(fn1(l))



'''
把规则移除函数外,函数名作为函数的形参
'''
# 定义一个函数:用来检测任意数的偶数;
def fn2(n):
    if n % 2 == 0:
        return True

# 定义一个函数:用来检测指定数字是否大于5;
def fn3(n):
    if n > 5:
        return True

def fn1(func,lst):
    #创建一个新的列表
    new_lst=[]

    for n in lst:
        #判断奇数和偶数
        if not func(n):
            new_lst.append(n)

    return new_lst

print(fn1(fn2,l))

2、匿名函数
匿名函数:lambda表达式,只会调用一次,执行完后,它从内存中消失。
lambda函数表达式是用来创建一些简单的函数,它是函数创建的另外一种方式。
语法:lambda 参数列表:返回值

#filter()可以从序列中过滤出符合条件的元素,保存到一个新的序列中。
#参数:1、函数,根据该函数来过滤序列(可迭代结构)
#     2、需要过滤的序列(可迭代结构)
#返回值:过滤后新的序列

l=[1,2,3,4,5,6,7,8,9,10]
# 定义一个函数:用来检测任意数的偶数;
def fn2(n):
    return  n % 2 == 0 #返回值为满足n % 2 == 0的n

# 函数中再定义一个函数:用来检测指定数字是否大于5;
def fn3(n):
    if n > 5:
        return True

print(filter(fn2,l))#<filter object at 0x000002743091DE48>
print(list(filter(fn2,l)))#[2, 4, 6, 8, 10]

'''
匿名函数:lambda表达式,只会调用一次,执行完后,它从内存中消失。
lambda函数表达式是用来创建一些简单的函数,它是函数创建的另外一种方式。
语法:lambda 参数列表:返回值
'''
#定义一个函数,实现两个数的和
def fn5(a,b):
    return a+b
print(fn5(4,5))

print(lambda a,b:a+b)#<function <lambda> at 0x000001E3D135CDC8>

print((lambda a,b:a+b)(1,2))

fun=lambda a,b:a+b
print(fun(4,5))


'''
map()函数可以对可迭代对象中所有元素做指定的操作,
然后将其添加到一个新的对象中返回。
'''
l=[1,2,3,4,5,6,7,8,9,10]

# lambda i:i+1,l#将l各元素加1,然后以新列表返回
r=map(lambda i:i+1,l)
print(list(r))

'''
sort()函数可以对列表当中的元素进行排序。
sort()函数中有一个关键字,需要一个函数名作为参数,
'''
lst=['as','dgfa','gah']
lst.sort() #排的是字符串的大小
print(lst)#['as', 'dgfa', 'gah']

lst=['as','dgfa','gah']
lst.sort(key=len) #排的是字符串的长度,key=len使用的就是高阶函数
print(lst)#['as', 'gah', 'dgfa']

3、闭包
闭包:
将函数作为返回值返回,也是一种高阶函数。

通过闭包可以创建一些只有当前函数才能访问的对象,
还可以将一些私有的数据藏到闭包当中。

形参闭包的三个必须条件:
1、函数嵌套;
2、将内部函数名作为返回值返回;
3、内部函数必须使用的外部函数的变量。

什么时候用闭包?
当有些数据不希望别人访问或修改的时候,就用闭包,这样可以保证一个数据的安全性。

'''
闭包:
将函数作为返回值返回,也是一种高阶函数。

通过闭包可以创建一些只有当前函数才能访问的对象,
还可以将一些私有的数据藏到闭包当中。

形参闭包的三个必须条件:
1、函数嵌套;
2、将内部函数名作为返回值返回;
3、内部函数必须使用的外部函数的变量。

什么时候用闭包?
当有些数据不希望别人访问或修改的时候,就用闭包,这样可以保证一个数据的安全性。
'''

def fn():

    #函数内部再定义一个函数
    def fn2():
        a=10
        print('fn2',a)

    #在内部函数将fn2内存地址作为返回值返回
    return fn2

r=fn()

r()#fn2 10

#求多个数的平均值
nums=[]#列表在全局作用域中是不安全的,容易被修改和覆盖
def avg(n):
    nums.append(n)
    return sum(nums)/len(nums)

print(avg(10))
print(avg(21))

#通过闭包的方式扭转变量在全局作用域中不安全的特性,闭包必然有函数的嵌套。
def new_avg():
    nums = []  # 列表在全局作用域中是不安全的,容易被修改和覆盖
    def avg(n):
        nums.append(n)
        return sum(nums)/len(nums)

    return avg

a=new_avg()
print(a(21))
print(a(10))

4、装饰器
装饰器:
通过装饰器,可以在不修改原函数的情况下对函数进行扩展;
在开发当中,都是通过装饰器来扩展函数的功能的。

'''
我们可以直接修改函数中的代码,会产生一些问题:
1、修改的函数很多;
2、不方便后期的维护;
3、会违反开闭原则(OCP)程序设计:
    OCP原则就是要求对程序的扩展,但是要关闭对程序的修改。
——-->>>>>>
装饰器:
通过装饰器,可以在不修改原函数的情况下对函数进行扩展;
在开发当中,都是通过装饰器来扩展函数的功能的。

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

def mul(a,b):
    return a*b

def fn():
    print('fn')

#创建一个新的函数,来对原函数进行扩展
def fn2():
    print('start')

    fn()

    print('end')

fn2()

def new_add(a,b):#用新函数扩展已有的函数-->>装饰器的引入
    print('start')

    r=add(a,b)

    print('end')

    return r

print(new_add(1,2))

'''
装饰器的使用
'''
#一、无形参的函数扩展
def start_end(old):

    #参数old:要扩展的函数对象

    #用来对其他函数进行扩展

    #创建一个新函数
    def new_function():
        print('start')

        old()

        print('end')

    #返回新函数
    return new_function

f=start_end(fn)
f()

#二、有形参的函数扩展
def start_end(old):

    #参数old:要扩展的函数对象

    #用来对其他函数进行扩展

    #创建一个新函数
    def new_function(a,b):
        print('start')

        result=old(a,b)

        print('end')

        #返回函数执行的结果
        return result

    #返回新函数
    return new_function

f=start_end(add)
print(f(1,2))

#三、采用不定长参数的形式扩展函数,实现通用形式的函数扩展
def start_end(old):#类似这种函数,在原有函数上增加新的内容,其实就是一个装饰器

    #参数old:要扩展的函数对象

    #用来对其他函数进行扩展

    #创建一个新函数
    def new_function(*a,**b):#*a,**b在此处是装包,变成元组和字典的形式
        print('start')

        result=old(*a,**b)#*a,**b在此处是解包,

        print('end')

        #返回函数执行的结果
        return result

    #返回新函数
    return new_function

f=start_end(fn)
f()

f=start_end(add)
r=f(1,2)
print(r)

@start_end #用start_end函数,对say_baybay进行装饰
def say_baybay():
    print('new function')

say_baybay()