Python(6)——Python中的函数与装饰器

关注微信公众号IT小组,获取更多知识干货~


目录:

函数的基本概念

递归函数

局部变量、全局变量、函数多个return语句

匿名函数

高阶函数

装饰器


一:函数的基本概念

任何编程语言中,函数是经常被使用到的,在Python中也不例外。
例如我们第一次接触到打印语句print时,print是Python中的函数。在使用print函数时,只需要给出要打印的数据,无论是变量,表达式还是字符串。print函数便都能将其打印出来。
函数是对语句的一种封装。
因为我们将很多语句写在函数中,当需要使用的时候,通过调用这个函数来使用函数内部的语句代码。
在Python中使用“def”关键字来定义函数,例子如下:

def hello():   # Python中以def来作为定义函数的关键字
   print ( 'hello' )
   hello()   # 调用函数的方式是函数名(),如果有参数则需要传递参数,下面会介绍
# 打印结果如下:
hello

从上面我们知道了,函数定义后,并不会自动执行,只有调用了函数,函数才会执行。函数是调用一次执行一次。
函数的命名规则和之前的变量命名规则一致,不过函数名的规范一般是动词加名词。

(在Python中,变量名,函数名,类名,模块名,都可以称为标识符,它们的命名是有一定规则的。Python的标识符命名规则和绝大多数高级编程语言一样,都是以数字和字母下划线构成,但是不能使用关键字和以数字开头并且区分大小写。——Python(1)——初识Python

上面自定义的hello函数,在调用时没有传递任何的参数。但是很多函数都会要求传入参数,比如前面章节中我们调用len函数的时候,要给定一个可迭代对象作为参数,如下:

print ( len( 'hello' ) )
# 打印结果如下:
5 

如果想让之前定义的hello函数接收参数,根据参数来打印结果。可以给函数增加形参,(在函数( )里面的变量称为形参,调用函数时传入的参数称为实参)。
代码如下:

def hello(name,place):   # 在函数( )里面的name,place变量我们称为形参
   print ( 'hello,'+ name )
   print ( 'welcom to ' + place )
   
hello( 'xiaoming' , 'shanghai' ) # 在调用函数时传递的参数,我们称为实参
# 打印结果如下:
hello,xiaoming
welcom to shanghai

# 我们还可以定义如下的函数,add(),让两个数相加,如下:
def add( a , b ):
    print ( a + b )

add ( 2 ,3 )
add ( 'nihao' , 'xiaoming' )
# 打印结果如下:
5
nihaoxiaoming 

函数中参数传递方式和前面字符串方法format参数的传递方式一致(Python(5)——Python中字符串的format方法)分为了位置参数,关键字参数等等,代码如下:

def add ( a , b ): # 自定义add函数和两个形参a,b
    print ( a + b )

c = add ( 1, 1 ) # 这是位置传参,参数按照顺序来传递
d = add ( a=1,b=2 ) # 这是关键字参数
e = add ( 2, b=1 ) # 位置参数和关键字参数混合使用,但是位置参数要在关键字参数前

函数的返回值:
自定义的add函数中,虽然可以通过函数内部的print函数语句来进行数据的处理和输出,看似add函数是有返回值的,但其实add函数并没有返回值,代码如下:

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

a = add ( 2 , 3 ) # 通过变量a来获取add的返回值
print ( a )
# 打印结果如下:
5           # 这是add函数中print函数语句的打印出来的
None        # 这才是自定义函数add的返回值,前面我们提到过,当函数没有返回值时会返回None

自定义add函数没有返回值,意味着我们对add函数处理后的数据操作有限,比如我们想调用add函数后再对add函数输出的数据的进行操作时,会非常的麻烦。要想对此数据进行处理则需要修改add函数才可以,显然这样对于简单的函数可以,但是当函数内部的结构过于庞大的时候,这样就太过于麻烦。为此,我们可以使用return语句给函数增加返回值,通过return语句来控制函数的返回值,代码如下:

def add( a , b ):
  return ( a + b ) # return语句是用来返回值的
  
a = add ( 1 ,3 )  # 因为add函数有return语句,会将return返回的值存储到变量a里
print ( a ) 
# 打印结果如下:
4

函数默认参数,代码例子如下:

def hello( name,place='shanghai' ): # place给定了默认参数shanghai
    print('{},welcom to {}'.format(name, place))
    
hello('xiaowang') # 只给了name参数,place参数未给,上面place参数默认为shanghai
# 打印结果如下:
xiaoming,welcom to shanghai

函数还可以传入可变参数,上面函数中的参数个数都是固定的,而Python使用*args来存储多余的参数并以元组保存,如下:

def hello( name,place='shanghai', *args ): 
    print('{},welcom to {},args的参数是{}'.format(name, place,args))
    
hello( 'xiaohong','beijing','xiaoming','xiaolan' ) # 超出了函数形参的个数
# 打印结果如下:
xiaohong,welcom to beijing,args的参数是('xiaoming', 'xiaolan')

通过*args可以对传入多余的参数进行保存,但是对函数已有的形参不能修改,如果想对函数已有的形参进行修改,可以使用**kwargs变量,**kwargs变量中的数据以字典形式保存,代码例子如下:

def hello( name,place,**kwargs ): 
    print('{},welcom to {},新增变量是{}'.format(name, place,kwargs))

hello( 'xiaohong' , 'shanghai' , age = 18 )

# 打印结果如下:
xiaoming,welcom to shanghai,新增变量是{'age': 19}
二:递归函数

在自定义函数并调用函数的时候,代码例子如下:

def add( a , b ) :
    print ( a + b ) # 调用了print函数,并且传入了参数
add() # 调用add函数

上述自定义了一个函数add,用于两个数值的相加,在add 函数中,我们发现了add函数内部有print函数(调用了print函数),这也就意味着函数内部可以调用其他的函数。
如果一个函数内部调用自己,我们称为递归函数,代码例子如下:

def hello():
    print ( 'hello,world' )
    hello() # 调用自己
    
hello() 
# 打印结果如下:
hello,world
hello,world
hello,world
hello,world
hello,world
hello,world
....
# 在hello函数内部自己调用自己,会不停的调用下去,直到内存溢出,产生错误
# 在上面hello函数之所以会不停的递归下去,是因为它的内部没有条件使得递归条件为False

在递归函数内部,要留一个条件,使得递归满足条件才能执行,我们称为出口。递归函数不利于理解,但是对于某些特定的问题,使用递归会更加的简洁,如下:

# 求1+2+3+4一直加到n的和,如果不使用递归也可以用for in语句等完成,但是使用递归会更加的简洁,如下:
def add( n ):
   if n == 0:
      return 0
   return  n + add( n - 1 ) 
 
a = add( 5 ) 
print ( a )
# 打印结果如下:
15

上面自定义add函数是递归函数,出口是n0的时候,如果n!=0,则会一直调用自己,并把n作为参数传递进去,直到n0条件成立并返回值为0时结束。详细解释如下:

#  add(5)将5传递到add里面的形参n,n=5,在判断n是否等于0,很明显
# 是否,然后,执行return n + add(n-1),意味着:
# add(5) = 5+add(5-1)
# add(4) = 4+add(4-1)
# add(3) = 3+add(3-1)
# add(2) = 2+add(2-1)
# add(1) = 1+add(1-1)
# add(0) = if n==0 : return 0
# 即add(5)=5+4+3+2+1

我们还可以使用递归实现最经典的斐波那契数列{1,1,2,3,5,8,13,21,34,55…},代码如下:

def fib( n ):
    if n == 1:
       return 1
    if n == 2:
       return 1
    return fib(n-1) + fib(n-2)

print(fib(10))
# 打印结果如下:
55

递归虽然使得代码更简洁,但是却不利于阅读,在开发过程中,使用循环也同样能达到递归的效果,如下,使用循环来实现斐波那契数列:

# 1,1,2,3,5,8,13,21,34,55...
def fibonacci( n ):
    num1 = num2 = 1
    i = 0
    while i < n - 1:
        num1,num2 = num2,num1 + num2  
        i += 1
     return num1

print ( fibonacci( 10 ) )
# 打印结果如下:
55
三:局部变量、全局变量、函数的多个返回值
全局变量和局部变量:

全局变量:定义在函数外部的变量称为全局变量,它在整个文件中都能被访问。
局部变量:在函数中定义的变量称为局部变量,只能在函数内部访问。
代码示例如下:

a = 100 
def test():
    a = 10
    print ( 'test中的的a值是{}'.format( a ) )
    
test()
print ( '全局变量a的值是{}'.format ( a ) ) 
# 打印结果如下:
test中的的a值是10
全局变量a的值是100

上面可以知道,局部变量和全局变量不会干扰,如果想在函数中修改全局变量,需要使用global关键字。代码如下:

a = 100 
def test():
   global  a = 10 # 通过global关键字修改了全局变量a的值
   print ( 'test中的的a值是{}'.format( a ) )
    
test()
print ( '全局变量a的值是{}'.format ( a ) ) 
# 打印结果如下:
test中的的a值是10
全局变量a的值是10

localsglobals是Python中的内置函数locals用来获取函数的所有的局部变量,globals用来获取所有的全局变量,数据都以字典的形式保存。代码如下:

a = 100
b = 12
c = 34
def a():
    d = 10
    e = 43
    print ( locals() ) # 用locals内置函数用来打印局部变量

a() # 调用a函数来获取所有的局部变量
print ( globals( ) )
# 打印结果如下:
{'d': 10, 'e': 43} # a函数里面的局部变量
{ 'a': <function a at 0x0000000001D0C1E0>, 'b': 12, 'c': 34} # 全局变量
# 上面a对应的值是内存中分配给函数a的地址空间,(函数也是一种特殊的全局变量)

(在Python中只有函数可以分割作用域,而循环、选择语句中都不能分割作用域。作用域是一个可访问的资源范围)

函数的多个返回值:

函数是用return语句来返回值的。

def add( a , b ):
    return  a,b,a+b  # 一般返回是一个元组,此时可以不用加()

print( add( 1 , 3 ))
# 打印结果如下:
(1, 3, 4)

函数中有多个return语句时,但是一般情况下,只会执行一个,代码如下:

def  a(num):
    if num > 60:
        return '你考及格了'
    else:
        return '你没有考及格'
        
print ( a (89) ) 
# 打印结果如下:
你考及格了
四:匿名函数

在Python中有匿名函数,匿名函数使用lambda关键字来标识,匿名函数是当函数的功能非常简单的时候,可以考虑使用匿名函数,代码如下:

a = lambda name,age: 'hello,{},you age is {}'.format( name,age ) # 变量a被赋值成为一个匿名函数lambda
# lambda格式:lambda 变量,变量N : 代码语句
print ( a ( 'lisi' , 23 ) )
# 打印结果如下:
hello,lisi,you age is 23

# 函数也可以当成参数传递:
def add(a, b,fn):
    return fn( a , b)
print (add ( 1,3,lambda a,b: a*b ) ) # 使用匿名函数还可以重写之前的函数
# 打印结果如下:
3
五:高阶函数

函数中可能会包含了其他的函数, 如果想对函数中的函数(局部函数)进行调用可以使用函数( )( ),代码如下:

def  test2()
    print ( 'test2函数运行了' )

def test():
    print ( 'test函数运行了' )
    return test2

a = test()  # a获取test函数的返回值,这里test函数返回值是test2函数
a() # 因为test函数返回test2给变量a,而a()其实就是test2()
# 打印结果如下:
test函数运行了
test2函数运行了

# 在函数内部的函数(局部函数),如下:
def a():
    x = 100 # 局部变量
    print ( 'a函数运行了' )
    def b(): # 局部函数
        # x += 1  内部函数不能直接修改外部函数的变量,需要在外部函数的变量中声明为nonlocal
        print ( 'b函数运行了' )
    return b

# 在上面a函数中又包含了一个b函数,b函数只能通过a函数内部调用,如果想在外部访问,如下;
a()() # a()函数返回的是b函数,后面在加一个()意味着又调用了b函数
# 打印结果如下:
a函数运行了
b函数运行了

(如果想要在局部函数中访问上一层函数中的变量,则需要在变量前使用nonlocal关键字)

六:装饰器

装饰器是Python中独有的。
装饰器在Python中是非常重要的,利用装饰器可以将代码变的更加灵活和更加具有可扩展性。
Python中使用 “ @ ” 作为装饰器的关键字。
在了解装饰器之前,先考虑一个项目需求场景:“要求利用自定义函数算出从0-99的和。”
这是很简单的,代码如下:

def sum():
    x = 0
    for i in range( 100 ): # range类自动生成指定范围的数值
        x += i
    print( x )

sum()
# 打印结果如下:
4950

上面自定义了一个函数sum,在这个函数内部使用了for…in循环来求0-99的和,这是非常正确的。但是如果此时需求改变了:“在求0-99和之前先打印’ hello '字符串,并且不能修改sum函数,只允许手动调用sum函数来实现这个新增的需求。”

如何在不修改sum函数的基础上,增加新的功能呢?

此时装饰器的强大就体现出来了,装饰器既可以不修改原有代码,又能给原有代码增加新的需求实现,代码如下:

def hello(fn):
    def active():
        print( 'hello' )
        fn() # fn存储了之前的sum函数,调用fn意味着调用sum函数
    return  active # 返回active函数,此时会执行active函数

@hello   # 用hello装饰器装饰了sum函数
def sum():
    x = 0
    for i in range( 100 ):
        x += i
    print( x )
    
sum()
# 打印结果如下:
hello
4950

如上面代码所示,sum函数前面增加了一个 “ @hello ” 装饰器。

“@hello”本质上是一个函数,在调用sum函数的时候,会先调用装饰器,也就是hello函数,把sum函数当成参数传递到装饰器hello中,存放在fn中,并返回active函数。也就意味着sum函数被装饰成为了active函数,调用sum函数就是在调用active函数。如果想要调用sum函数,则需要调用fn。

此时可以看到,在未更改原有sum函数代码的情况下,增加了一个新的功能。

有参数的函数被装饰时:
之前的函数被装饰的时候,都没有参数,要是有参数的函数被装饰,代码如下:

def time(fn):
    def active(a,b):
        print('你的名字是{},你所在地点是{}'.format(a,b))
        fn(a,b) # fn中存储之前的hello函数,需要注意此时fn中的形参要和active中的形参名称保持一致
    return active

@time # 使用time装饰器装饰hello函数
def hello(name,place):
    print ( '{}你好,欢迎来到{}'.format(name,place))

hello('lisi','shanghai') # 调用hello函数,
# 打印结果如下:
你的名字是lisi,你所在地点是shanghai
lisi你好,欢迎来到shanghai

如上面代码所示,hello函数传递了两个参数,所以装饰器time的内部函数active里面也要拥有两个形参用来接收,并且在调用hello函数时候,也要将参数传递过去。
装饰器本身也可以带参数,代码示例如下:

def time(n):
    def hi(fn): # hi函数中的fn存储了hello函数
        def active(a,b): 
            if n > 2017: # 如果time传递的实参大于2017则执行下面的代码
                print('time的n参数是{}'.format(n))
                print('你的名字是{},你所在地点是{}'.format(a,b))
                fn(a,b) # fn则存储了hello函数,fn中的形参要和active形参名称保持一致
        return active # 返回active函数
    return hi

@time(2018) # 带参数的装饰器,2018这个实参被传递到time里的形参n中,而hello函数则传到了hi中的fn里
def hello(name,place):
    print ('{}你好,欢迎来到{}'.format(name,place))

hello('lisi','shanghai')
# 打印结果如下:
time的n参数是2018
你的名字是lisi,你所在地点是shanghai
lisi你好,欢迎来到shanghai

装饰器time传递了一个实参过去,用装饰器time函数中的n来保存,而被装饰的hello函数则被传递到了hi函数的fn中来保存,并且返回了active函数,也就意味着下面调用hello函数就是调用hi函数中内置active函数,而调用fn则就是调用了hello函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值