python基础—初识函数(一)

1.python中函数定义

函数是逻辑结构化和过程结构化的一种编程方法。

(1)、python中函数定义方法:

def test(x):

  '''The function definitions'''

  x+=1

  return x

说明:

  def :定义函数的关键字

  test :函数名

  ():可定义形参

  '''''':文档描述,非必要

  x+=1: 泛指代码块或程序处理逻辑

  return: 定义返回值

2.为什么要使用函数

背景摘要:现在需要写一个监控程序,当CPU,MEMORY,DISK等指标的使用量超过阈值时发送报警邮件

(1)、传统方式实现:

while1:
    if cpu>90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭链接
    
    if memory >90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭链接
    
    if disk >90%
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭链接
    

(2)、使用函数实现:

def 发送邮件(内容):
  #发送邮件提醒
  连接邮箱服务器
  发送邮件
  关闭链接

while 1:
    if cpu>90%:
        发送邮件('cpu报警'if memory>90%:
        发送邮件('memory报警'if disk>90%:
        发送邮件('disk报警'

总结使用函数的好处:

  1.代码重用

  2.保持一致性,容易维护

  3.可扩展性

3.函数参数

(1)、形参

形参变量只有在调用时候才分配内存单元,在调用结束时,即刻释放所调用的内存单元。因此,形参只在函数内部有效,函数调用结束返回主调用函数后则不能在使用该形参变量。

(2)、实参

实参可以是常量,变量,表达式,函数等,无论实参是何种类型的量,在进行函数调用时,它们必须有确定的值,以便把这些值传给形参。因此应预先用赋值,输入等办法使参数获得确定值。

def  calc(x,y):   #x,y为形参
    res=x**y
    return res

calc(a,b)   #a,b为实参
print(calc(a,b))

(3)、位置参数和关键字

 标准调用:实参和形参一一对应

 关键字调用:位置无需固定

def test(x,y,z):
    print(x)
    print(y)
    print(z)

test(1,2,3)  #位置标准调用
test(x=1,y=3,z=2)#关键字调用

 注意:如果混合使用,位置参数一定要在关键字参数的左边

(4)、默认参数

def handle(x,type='mysql'):
    print(x,type)

handle('hello')  #type默认值为'mysql'   
handle('hello',1) #重新赋值,覆盖原本默认参数

>> hello mysql
   hello 1

 (5)、可变长参数

①、*args 

能够接收列表或元祖

为了能让一个函数接受任意数量的位置参数。

def avg(first, *args):
    return (first + sum(rest)) / (1 + len(args))


avg(1, 2) # 1.5
avg(1, 2, 3, 4) # 2.5

在这个例子中,args是由所有其他位置参数组成的元组。然后我们在代码中把它当成了一个序列来进行后续的计算。

②、**kwargs

为了接受任意数量的关键字参数

def test(**kwargs)
    print(kwargs)

test(a=1, b=2) >>{'a': 1, 'b': 2}

如果希望某个函数能同时接受任意数量的位置参数和关键字参数,可以同时使用*和**。

def anyargs(*args, **kwargs):
    print(args) # A tuple
    print(kwargs) # A dict

使用这个函数时,所有位置参数会被放到args元组中,所有关键字参数会被放到字典kwargs中。

注意:

一个*参数只能出现在函数定义中最后一个位置参数后面,而 **参数只能出现在最后一个参数。 有一点要注意的是,在*参数后面仍然可以定义其他参数。

def a(x, *args, y):  #此时y必须是关键字参数
    pass

def b(x, *args, y, **kwargs):   # 此时y必须是关键字参数
    pass

这种参数就是我们所说的强制关键字参数。

扩展:只接受关键字参数的函数

希望函数的某些参数强制使用关键字参数传递,只需将强制关键字参数放到某个*参数或者单个*后面就能达到这种效果。

def recv(maxsize, *, block):
    'Receives a message'
    pass

recv(1024, True) # TypeError
recv(1024, block=True) # Ok

利用这种技术,我们还能在接受任意多个位置参数的函数中指定关键字参数。比如:

def minimum(*values, clip=None):
    m = min(values)
    if clip is not None:
        m = clip if clip > m else m
    return m

minimum(1, 5, 2, -5, 10) # Returns -5  
minimum(1, 5, 2, -5, 10, clip=0) # Returns 0

很多情况下,使用强制关键字参数会比使用位置参数表意更加清晰,程序也更加具有可读性。 例如,考虑下如下一个函数调用:

msg = recv(1024, False)

如果调用者对recv函数并不是很熟悉,那他肯定不明白那个False参数到底来干嘛用的。 但是,如果代码变成下面这样子的话就清楚多了:

msg = recv(1024, block=False)

另外,使用强制关键字参数也会比使用**kwargs参数更好,因为在使用函数help的时候输出也会更容易理解:

>>> help(recv)
Help on function recv in module __main__:
recv(maxsize, *, block)
    Receives a message

 (6)、局部变量和全局变量

 规则:全局变量名大写,局部变量名小写

 在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。

   全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序。
   
 当全局变量与局部变量同名时:
   在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用。
 
  如果函数的内容无global关键字,优先读取局部变量,对全局变量只能读取,无法对全局变量重新赋值,对于可变对象,可以对内部元素进行操作;
  如果函数中有global关键字,变量本质就是全局那个变量,可读取,可赋值。

 如果函数内无global关键字

#有声明局部变量,函数调用会调用局部变量
HOBBY = ['上网', '打游戏']
def test():
    HOBBY = '泡妞'
    print('我的爱好:', HOBBY)

test()   >>我的爱好:泡妞
#无声明局部变量,函数会调用全局变量
HOBBY = ['上网', '打游戏']
def test():
    print('我的爱好:',HOBBY)

test()  >>我的爱好:['上网', '打游戏']

如果函数中有global关键字

a = 1
def test():
    global a
    a += 1    # 改变全局变量a的值
    print('函数中的a:', a) 
    return a

test()
print(a)

(7)、返回值

  要想获取函数的执行结果,就可以用return语句把结果返回

  注意:

  1. 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,所以也可以理解为 return 语句代表着函数的结束
  2. 如果未在函数中指定return,那这个函数的返回值为None 

4、嵌套函数 

def negan():
    name = 'Negan'
    print(name)
    def alex():
        name = 'Alex'
        print(name)
        def egon():
            name = 'egon'
            print(name)
        print(name)
        egon()
    alex()
    print(name)

说明:函数只有在被调用的时候才会被执行

5、递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

(1)、使用递归计算n的阶乘

计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:

fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n

所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。

于是,fact(n)用递归的方式写出来就是:

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

假如我们要计算5的阶乘,则:

>>fact(5)
>>5 * fact(4)
>>5 * 4 * fact(3)
>>5 * 4 * 3 * fact(2)
>>5 * 4 * 3 * 2 * fact(1)
>>5 * 4 * 3 * 2 * 1

(2)、递归问路

import time
person_list = ['Alex','Bob','Christian']
def ask_way(person_list):
    print('_'*60)
    if len(person_list) == 0:
        print('没有人知道')
        return '没有人知道'
    person = person_list.pop(0)
    if person == 'Christian':
        return '%s说:我知道,路在脚下' %person
    print('Hi,%s敢问路在何方?' %person)
    print('%s说:我不知道,我可以帮你去问问%s' %(person,person_list))
    time.sleep(1)
    res = ask_way(person_list)
    print('%s问的结果是:%res' %(person, res))
    return res

res = ask_way(person_list)
print(res)
____________________________________________________________
Hi,Alex敢问路在何方?
Alex说:我不知道,我可以帮你去问问['Bob', 'Christian']
____________________________________________________________
Hi,Bob敢问路在何方?
Bob说:我不知道,我可以帮你去问问['Christian']
____________________________________________________________
Bob问的结果是:'Christian说:我知道,路在脚下'es
Alex问的结果是:'Christian说:我知道,路在脚下'es
Christian说:我知道,路在脚下

(3)、递归计算列表中所有元素的和

item = [1,2,3,4,5,6,7]
def sum(item)
    head, *tail = item    
    return head + sum(tail) if tail else head

递归特性:

a. 必须有一个明确的结束条件

b. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少

c. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,

 每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,

 所以,递归调用的次数过多,会导致栈溢出,Python3中默认栈的大小是998)

6、匿名函数

Python中可以不用def关键字创建函数,使用lambda即可创建匿名函数,语法格式如下:

lambda = param1,param2,...,paramN:expression

匿名函数也是函数,与普通函数一样,参数也是可选的。

# 普通函数
def calc(x):
    return x*x

# 改成匿名函数
lambda x : x*x

扩展:

>>> x = 10
>>> a = lambda y: x + y
>>> x = 20
>>> b = lambda y: x + y

a(10)和b(10)返回的结果是什么?如果你认为结果是20和30,那么你就错了。

这其中的奥妙在于lambda表达式中的x是一个自由变量, 在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。 因此,在调用这个lambda表达式的时候,x的值是执行时的值。例如:

>>> x = 15
>>> a(10)
25
>>> x = 3
>>> a(10)
13

如果你想让某个匿名函数在定义时就捕获到值,可以将那个参数值定义成默认参数即可,就像下面这样:

>>> x = 10
>>> a = lambda y, x=x: x + y
>>> x = 20
>>> b = lambda y, x=x: x + y
>>> a(10)
20
>>> b(10)
30

在这里列出来的问题是新手很容易犯的错误,有些新手可能会不恰当的使用lambda表达式。 比如,通过在一个循环或列表推导中创建一个lambda表达式列表,并期望函数能在定义时就记住每次的迭代值。例如:

>>> funcs = [lambda x: x+n for n in range(5)]
>>> for f in funcs:
... print(f(0))
...
4
4
4
4
4
>>>

但是实际效果是运行是n的值为迭代的最后一个值。现在我们用另一种方式修改一下:

>>> funcs = [lambda x, n=n: x+n for n in range(5)]
>>> for f in funcs:
... print(f(0))
...
0
1
2
3
4
>>>

7、高阶函数

满足下面两个条件即为高阶函数:

(1)、函数传入的参数是一个函数名

(2)、函数的返回值是一个函数

(1)、把函数当作参数传给另一个函数

def foo(n):
    print(n)
def bar(name):
    print('My name is %s'%name)

foo(bar('alex'))   

>>My name is alex
     None

(2)、返回值中包含函数

def foo():
    print('from foo')
    return bar

def bar():
    print('from bar')


foo()()   

>> from foo
   from bar

(3)、尾调用

  在函数的最后一步调用另外一个函数(最后一行不一定是函数的最后一步)

  尾调用的关键在于函数的最后一步调用别的函数,根据函数即变量的定义,定义a函数,a函数内调用b函数,b函数内调用函数c,在内存中形成一个调用记录,又称为调用栈,用于保存调用位置以及变量信息,即a->b->c,直到c返回结果给b,c的调用记录才会消失,b返回结果给a,b的调用结果消失,a返回结果,a的调用记录消失,所有的调用记录都是“”先进后出”,形成一个‘’调用栈‘’。

(4)、map()函数

map方法接收两个参数,一个是待执行的方法,另一个是可迭代对象。
 
处理序列中每个元素,得到的结果是一个“列表”,该“列表”元素个数及位置与原来一样。
 
map()实现原理:
num = [1,2,3,4]

def add(x):
    return x +1 

def map_test(func, array)
    ret = []
    for i in array:
        ret.append(func(i))
    return ret

print(map_test(add, num))  >> [2,3,4,5]   # 使用普通函数

print(map_test(lambda x:x+1, num)) >> [2,3,4,5] # 配合匿名函数实现

使用map函数实现上面的功能

注意:调用map时候返回的是map类的实例,此时func的方法并没有执行,根据需要返回的类型,在map实例中调用list、tuple、set、dict等方法触发回调函数。

num = [1,2,3,4]

print(list(map(lambad x: x+1, num)))   

(5)、filter()函数

遍历序列中每个元素,判断每个元素得到一个布尔值,如果是True则留下来。

data = list(range(10))

def func(item):
    if item % 2 == 0:
        return item

def filter_test(func, array):
    ret = [0]
    for i in array:
        if func(i):
            ret.append(func(i))
    return ret

filter_test(func, data) >> [0,2,3,6,8]

使用filter()函数实现

list(filter(lambda x:x%2==0, list(range(10))))

(6)、reduce()函数

reduct方法有三个参数:

reduce(function, sequence, initial=None)

function:待执行的方法

sequence:序列

initial:初始值

在没有初始值的情况下,首次执行会从序列中取出来两个值,传入function得到一个结果,然后从序列中按照顺序取出下一个值,和该结果一起传入function,直到序列中把所有

的元素取完。
 
若设置了初始值,首次执行则会从序列中取出一个元素,并和初始值一起传入function,同样将结果和序列中的下一个值继续传入function,直到序列中的元素被取完。
 
num = [1,2,3,10]

def multi(x,y):
    return x*y

def reduce_test(func,array,init=None):
    if init is None:
        res = array.pop(0)
    else:
        res = init
    for i in array:
        res = func(res,i)
    return res
print(reduce_test(multi,num,10))

使用reduce()实现

from functools import reduce

num = [1,2,3,10]

reduce(lambda x,y: x*y, num)

带默认参数

from functools import reduce

num = [1,2,3,10]

reduce(lambda x,y: x*y, num) >>120

 

 
posted @ 2018-04-10 19:00  李大鹅  阅读(204)  评论(0编辑  收藏  举报