17.python自定义函数

  什么是函数,函数说白了就是将一系列代码封装起来,实现代码的重用。

  什么是代码重用?

  假设我有这样的需求:

  但是我还是觉得太麻烦了,每次想吃饭的时候都要重复这样的步骤。此时,我希望有这样的机器:

  

    将重复的工作封装到一起,我们只要向机器里放入东西,就能得到我们想要的。

  这也就是所谓的代码重用。


 

自定义函数

  知道了函数是干什么用的之后,我们就开始学习自定义函数,也就是动手来造这个神奇的机器。

  看代码示例:

def dfb(a):
    '''一系列操作'''
    return '一碗%s饭' %a

a = dfb('')
b = dfb('')
print a
print b

 

  这样我们就得到了两碗饭,真是方便快捷。

  现在来解释里面都有什么:

  1. def 是python的关键字,是专门用来自定义函数的。

  2. dfb是函数名,用来以后调用的。

  3.(a)中的a为函数的参数,为函数里面的操作提供数据的。

  4.return用来返回一个对象,这个对象可以是函数的处理结果,也可以是函数的处理状态等等。


1.def

  没什么好解释的,语法规定。

 

2.函数名

  函数名就类似于变量名。

  例如我写了一个函数,但我不调用它,那会怎么样。

def dfb(a):
    '''一系列操作'''
    return '一碗%s饭' %a

 

  什么也没有输出,那是不是意味着函数不存在呢?

  我们看看内存里有没有:

print id(dfb)

 

  很明显,函数在内存中,能够找得到。

  所以,当我们定义一个函数的时候,python就会将函数加载到内存中,只不过不调用的时候,函数内部的代码就不执行。

  很明显,和变量赋值原理差不多,所以要注意一个问题:如果函数名和变量名冲突了,相当于重新赋值。而python解释是从上到下的,也就是说此时谁在下面谁占用这个变量名。剩下的那个就只能在内存中等待垃圾回收了。

def dfb(a):
    '''一系列操作'''
    return '一碗%s饭' %a
dfb = 1
print dfb

 

  

print dfb()

 

  

  那么函数名的命名有什么要求吗?

  函数名的要求比变量名严格一点,除了遵守变量名的要求之外,还不能够使用数字

  所以函数名只能使用字母和下划线(_),同时还要避开python的关键字

  另外,在pep8标准中,函数名提倡都要小写


2.函数的作用域

  参数函数的一大重难点,很多人不明白所谓的形参和实参到底是怎么回事。

  要明白其中的区别,首先还解释函数的作用域是什么回事。

  所谓的函数作用域,就是这样。

  python在执行函数里面的代码的时候,会将其放在一个新的环境中。这个新环境就像一个虚拟机,虚拟机能够访问和修改本机中数据,前提是该数据是可修改的,但是python在函数调用完毕之后会自动销毁这个环境。但是,如果我们在函数中试图修改一个不可变的数据,也就是进行重新赋值的行为的话。

DEBUG = True
a = []
b = 123

def test():
    if DEBUG:
        a.append(1)
        b = 321  #重新赋值,先进行对象创建在进行引用更新。

test()
print a
print b

 

 

  就像这样,我们在函数内可以访问到全局变量,也可以对可修改类型进行修改,但是当我们试图为变量b重新赋值时,却发现没用效果。

  因为函数在运行完毕之后就被销毁了,而在函数里面新建的对象也跟着被销毁了。所以我们为b重新赋值,打算新建一个对象来改变其引用时,却发现该对象在出了函数以后就消失了,这样b岂不是没有引用了?python不会让这样的事情发生,所以就引入了作用域,函数内部创建的对象和外部是隔离的,互不影响的。

  所以作用域问题单纯只是针对对象新建问题。

  也就是在函数内新建的对象会放到一个盒子中,这些对象只在函数内有效,且与外层对象隔离,所以做到了就算变量名冲突,在函数内的赋值也不会影响到外面的变量。

  函数内的变量就是局部变量,外部的就是全局变量所以在函数中定义的变量,也就是局部变量,只在函数内部有效。


 

2.形参和实参

  形参,字面意思,指的是形式上的。

  实参,则是实际上的。

  看下面这个例子。

a = 123

def dfb(a):
    '''一系列操作'''
    return '一碗%s饭' %a

b = ''
print dfb(b)

  我们传参的时候,相当于在函数内部进行了 a = b 的操作。

  因为函数是有作用域的,虽然外部的a=123,但是函数内部是相对于a=b='米',是一个局部变量。

  而局部变量在函数执行完毕就销毁了,所以它是形式上的,仅仅只是函数内部处理时用的,更像是一个标记,所以称为形参。而当我们调用函数时,实际给的参数,如这里的 dfb(b) 中的b这个实际的参数,是实际在内存中有的,所以就称为实参。


 2.global

  如果我要强制在函数里面进行全局变量的声明怎么办?

a = 123

def dfb(a):
    '''一系列操作'''
    b = 1
    return '一碗%s饭' %a

dfb('')
print b

 

  

  此时可以使用global关键字:

a = 123

def dfb(a):
    '''一系列操作'''
    global b  #我先声明我要创建一个全局变量b
    b = 1   #然后我再为b赋值
    return '一碗%s饭' %a

dfb('')
print b

 

  这样就可以在函数内创建全局变量了。

  此时有机智的同学就要问了,那我在函数内部声明的全局变量和之前的冲突,是不是会重新赋值呢?

  答案:是的。

  但要注意一个问题:

a = 123

def dfb(a):
    '''一系列操作'''
    global a    #我声明全局变量a,想要覆盖之前的赋值,而传参的时候,相对于进行了a=b的操作
    return '一碗%s饭' %a

b = ''
print dfb(b)

 

  然而报错了:

  说明我们不能将形式参数声明为全局的。

  那我们改一下形式参数的名称:

a = 123

def dfb(c):
    '''一系列操作'''
    global a    #我声明全局变量a,想要覆盖之前的赋值
    a = c
    return '一碗%s饭' %a

b = ''
dfb(b)
print a

  可以了,说明我们的思路是正确的,在函数内声明全局变量确实会覆盖已经有的变量。


 

 

3.传参

  传参是函数的又一大重点和难点。

  传参是为了能使函数适用更多的情况,我们在上面的示例中都写列带参数的函数,是不是说函数一定要参数才行呢?

def test():
    print '然而我并没有参数'

test()

 

  可以看出没有参数也是可以的,只是没有参数的时候,无论怎么调用得到的结果都是一样的,灵活性太低,所以为了提高灵活性,就有了参数存在的必要。

  而参数又分为普通参数,默认参数,动态参数,下面逐一说明。

1.普通参数

def test(a,b,c):
    print a
    print b
    print c
test(1,2,3)

 

  可以看出参数是按照顺序传递进去的,这种写法就叫普通函数。

  但是这里有一个问题:

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

 

  本来要传3个参数的,但是我仅传了2个,这样就报错了。但我不想这样,我希望当我不传参数的时候,参数有一个默认的值,此时就需要默认参数了。

 

2.默认参数

def test(a,b,c=3):
    print a
    print b
    print c
test(1,2)

 

  传参就相当于为形参赋上实参的值,你给了参数,我就按顺序执行 a = 1 ,b = 2,但此时c已经赋值为3了,所以就算不传,参数的数量也够了。

  当然也可以将参数传够, text(1,2,3) 就相当于为c重新赋值了。这样就能达到你传了参数就按照传的参数来处理,而没传就按默认的值处理

  所以我们可以为全部的参数都设置默认值。

  但是可能会出现这样的情况:

def test(a=1,b,c=3):
    print a
    print b
    print c
test(1,2)

 

  并不允许这样的写法,因为这样没有默认值的参数很难处理,所以当默认参数和普通参数同时存在时,要将普通参数放在前面:

def test(b,a=1,c=3):
    print a
    print b
    print c
test(1,2)

 

  之所以要规定这样写,是因为要配合传参的方法:我们可以显式地规定哪个值是传给哪个参数的:

def test(b,a=1,c=3):
    print a
    print b
    print c
test(a=1,b=2,c=3)

 

  如果是这样的显式传参的话,传参的顺序是任意的。也就是 text(a=1,c=3,b=2) 也可以,反正最后赋值的结果是一样的。

  但是,如果是普通传参和显式传参配合使用时,就必须将默认传参放在开头: text(2,a=1,c=3,) 普通的按顺序传,显式可以不按顺序传。这也就是我们在写函数的时候要求普通的参数都放在前面,默认的参数都放在后面,就是为了和这里对应。

 

3.动态参数

  因为我们的函数最后可能并不是只有自己用,而是给用户或其他人调用,但是其他人不一定了解我这里接受多少个参数。

  传少了我们可以使用默认参数来解决,但是传多了怎么办,一旦传多了就报错用户体验就不好了,为了提高函数的适应能力,就出现了动态参数。

def test(a,*args,**kwargs):
    print a
test(1,2,c=3)

 

  这样函数的适应性就更高了,那么这里的 *args 和 **kwargs 分别是什么意思。

  我们先来看去是什么类型:

def test(a,*args,**kwargs):
    print type(args),type(kwargs)

test(1,2,c=3)

 

  它们是元祖和字典,这里注意前面的*号只是表示这个是什么类型的,*表示元祖,**代表字典,而真正的变量名是*后面的。这里并没有规定一定要用args命名元祖,kwargs命名字典。但是这是个规范的写法,为的是让别的程序员能一眼看出这是个什么东西。

  接下来我们看看里面存的是什么:

def test(a,*args,**kwargs):
   print args
   print kwargs

test(1,2,c=3)

 

  它将默认传参方式多传的值放在一个元祖里,而用显式传参多传的用其参数名为键,参数值为值,组成了字典。

  你可以无视它们,也可以将处理它们,赋值操作也好,循环也好,成员判断也好,各种需要看个人。

 


4.return

  当函数遇到return语句时,表示函数执行完毕,此时返回一个对象,然后销毁函数的执行环境。

  但是你在上面的示例中看到也有这样的写法:

def test():
    print '我并没写return'
    
test()

 

  发现没有return语句还是能执行的,在调用的时候输出了东西,执行一遍后也停止了。

  注意,在函数内没有写return语句的时候,默认return的是一个空对象。也就是就算没写,python内部也做了处理。

  此时,有部分人分不清函数的输出和返回值的区别。

  这样说吧,在函数里print之类的操作能够输出内容,是因为虽然函数的执行环境是独立的,但代码还是有效的。外部能进行的操作,函数内部也可以。但是并不是所有的函数在执行完毕后都有如此明显的输出效果,此时我们需要查看函数是否成功,或者说我放了米进去,你操作一番之后总要把饭给我拿出来吧。

  这就是函数中return的意义。返回一个对象。这个对象可以是对执行状态的说明,也可以是处理后的结果等等。

def test():
    '''一系列操作'''
    return '搞定了'

test()

 

  但运行还是没看到东西。

  那是因为函数虽然返回了对象,但是这个对象还在内存中,你并没有把它拿出了,当然什么也看不到。

print test()

 

  这样就看到了,当然你可以进行变量的赋值,如 a = test() ,之后调用变量a就行了。当然返回结果多用True和False代表成功和失败,然后可以和条件控制语句配合使用。

def test():
    '''一系列操作'''
    
print test()

  当然不写就默认返回空对象None了。

 

def test(a,b):
    c = a + b
    return c

print test(1,2)

 

  返回处理后的结果也是可以的,反正看个人需求。

 

  最后,虽说遇到return代表函数结束,但并意味着一个函数里面只有一个return。

def test(a,b):
    if a != 0:
        return a + b
    return 'a不能为0'

print test(1,2)
print test(0,2)

 

  可以配合条件控制语句实现不同情况返回不同的对象,这样就函数就能处理更多的情况了。

 


匿名函数:

  有些情况下,我只想用函数处理一下很简单的业务,这个时候完整地写一个函数感觉会增大代码量,此时可以使用匿名函数进行处理。

  python 使用 lambda 来创建匿名函数:

  1.lambda只是一个表达式,函数体比def简单很多。

  2.lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。

  3.lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。

  4.虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。

 

语法:

lambda [arg1 [,arg2,.....argn]]:expression

 

示例:

# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2
 
# 调用sum函数
print "相加后的值为 : ", sum( 10, 20 )
print "相加后的值为 : ", sum( 20, 20 )

 

  虽说是匿名函数,但是还是需要将其赋给一个变量,否则无法调用。而匿名函数里面的变量也是局部变量,和完整的函数也没有什么区别。

  匿名函数建议只在处理简单功能的时候使用,太过复杂的还是使用完整的函数吧。


pass语句

  Python pass是空语句,是为了保持程序结构的完整性。pass 不做任何事情,一般用做占位语句。

def test():
    pass    #我想要用这个函数处理某些事,但我暂时没想好怎么写,就先占个坑

 

  当然,不仅函数可以使用,流程控制中也可以使用。

if a <= 10:
    pass
else:
    print 'a大于10'

 

  还是那句话,仅起占位的作用。但对于某些语法结构来说,必须要有子语句的存在,所以pass在这个时候就很有用了。

  另外,pass也表示什么都不做。

 


  关于自定义函数就先说到这里,如有什么错误和需要补充的后面会相应修改。

 

posted @ 2016-05-30 23:51  scolia  阅读(12183)  评论(2编辑  收藏  举报