python 一篇就能理解函数基础
一,函数是什么?
函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,具体区别,我们后面会讲,编程中的函数在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子过程或子程序),在Pascal中叫做procedure(过程)和function,在C中只有function,在Java里面叫做method。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
函数定义:函数是指一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需要调用其函数名即可
函数特性:
- 代码重用
- 保持一致性
- 可扩展性
二,函数的创建
2.1格式:
python定义函数使用def关键字,一般格式如下:
def 函数名(参数列表): 函数体
举个例子:
def hello(): print("hello") hello() #调用 # 结果:hello
2.2函数名的命名规则:
- 函数名必须以下划线或字母开头,可以包含任意字母、数字或下划线的组合。不能使用任何的标点符号;
- 函数名是区分大小写的。
- 函数名不能是保留字。
2.3形参和实参
形参:形式参数,不是实际存在,是虚拟变量,在定义函数和函数体的时候使用形参,目的就是在函数调用的时候接收实参(实参个数,类型应与实参一一对应)
实参:实际参数,调用函数时候传给函数的变量,可以是常量,变量,表达式,函数,传给形参
区别:形参是虚拟的,不占用内存空间,形参变量只有在调用时才分配内存单元,实参是一个变量,占用空间,数据传送单向,实参传给形参,不能形参传给实参。
import time times = time.strftime('%Y-%m-%d') def func(time): print("Now time is :%s"%times) func(times) # 结果:Now time is :2018-03-24
下面具体举个例子
def show_shoppingcart(): balance = 100000 shopping_cart = [ ('mac',9000), ('kindle',800), ('tesla',100000), ('python_book',120), ] print("shopping_cart".center(50,'*')) for i,v in enumerate(shopping_cart): print('\033[35;1m %s: %s \033[0m'%(i,v)) expense = 0 for i in shopping_cart: expense +=i[1] print('\n\033[32;1m您的余额为 %s \033[0m'%(balance-expense)) show_shoppingcart() # 结果: # ******************shopping_cart******************* # 0: ('mac', 9000) # 1: ('kindle', 800) # 2: ('tesla', 100000) # 3: ('python_book', 120) # # 您的余额为 -9920
现在我们用一个例子说明函数的三个特性:
def action1(n): print ('starting action1...') with open('日志记录','a') as f: f.write('end action%s\n'%n) def action2(n): print ('starting action2...') with open('日志记录','a') as f: f.write('end action%s\n'%n) def action3(n): print ('starting action3...') with open('日志记录','a') as f: f.write('end action%s\n'%n) action1(1) action2(2) action3(3) ##***************代码重用 def logger(n): with open('日志记录','a') as f: f.write('end action%s\n'%n) def action1(): print ('starting action1...') logger(1) def action2(): print ('starting action2...') logger(2) def action3(): print ('starting action3...') logger(3) action1() action2() action3() ##***************可扩展和保持一致 ##为日志加上时间 import time def logger(n): time_format='%Y-%m-%d %X' time_current=time.strftime(time_format) with open('日志记录','a') as f: f.write('%s end action%s\n'%(time_current,n)) def action1(): print ('starting action1...') logger(1) def action2(): print ('starting action2...') logger(2) def action3(): print ('starting action3...') logger(3) action1() action2() action3() 函数的特性展示
三,函数的参数
- 必备参数
- 关键字参数
- 默认参数
- 不定长参数
3.1必需的参数:
必须参数须以正确的顺序传入函数,调用的数量必须和声明时的一样
def f(name,age): print('I am %s,I am %d'%(name,age)) f('alex',18) f('alvin',16)
3.2关键字参数:
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
def f(name,age): print('I am %s,I am %d'%(name,age)) # f(16,'alvin') #报错 f(age=16,name='alvin')
3.3缺省参数(默认参数):
调用函数时,缺省参数的值如果没有传入,则被认为是默认值。下例会打印默认的age,如果age没有被传入:
def print_info(name,age,sex='male'): print('Name:%s'%name) print('age:%s'%age) print('Sex:%s'%sex) return print_info('alex',18) print_info('铁锤',40,'female')
3.4不定长参数
你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述2种参数不同,声明时不会命名。
# def add(x,y): # return x+y def add(*tuples): sum=0 for v in tuples: sum+=v return sum print(add(1,4,6,9)) print(add(1,4,6,9,5))
加了星号(*)的变量名会存放所有未命名的变量参数。而加(**)的变量名会存放命名的变量参数
def print_info(**kwargs): print(kwargs) for i in kwargs: print('%s:%s'%(i,kwargs[i]))#根据参数可以打印任意相关信息了 return print_info(name='alex',age=18,sex='female',hobby='girl',nationality='Chinese',ability='Python') ###########################位置 def print_info(name,*args,**kwargs):#def print_info(name,**kwargs,*args):报错 print('Name:%s'%name) print('args:',args) print('kwargs:',kwargs) return print_info('alex',18,hobby='girl',nationality='Chinese',ability='Python') # print_info(hobby='girl','alex',18,nationality='Chinese',ability='Python') #报错 #print_info('alex',hobby='girl',18,nationality='Chinese',ability='Python') #报错
注意,还可以这样传参:
def f(*args): print(args) f(*[1,2,5]) def f(**kargs): print(kargs) f(**{'name':'alex'})
3.5高阶函数
至少满足下面一个条件的函数:
- 接受一个或者多个函数作为输入
- 输出一个函数
def add(x,y,f): return f(x) + f(y) res = add(3,-6,abs) print(res) ############### def foo(): x=3 def bar(): return x return bar
四,函数的返回值
要想获取函数的执行结果,就可以用return语句把结果返回
注意:
- 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
- 如果未在函数中指定return,那这个函数的返回值为None
- return多个对象,解释器会把这多个对象组装成一个元组作为一个一个整体结果输出。
五,作用域
5.1 作用域介绍
python中的作用域分4种情况:
- L:local,局部作用域,即函数中定义的变量;
- E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
- G:globa,全局变量,就是模块级别定义的变量;
- B:built-in,系统固定模块里面的变量,比如int, bytearray等。 搜索变量的优先级顺序依次是:作用域局部>外层作用域>当前模块中的全局>python内置作用域,也就是LEGB。
x = int(2.9) # int built-in g_count = 0 # global def outer(): o_count = 1 # enclosing def inner(): i_count = 2 # local print(o_count) # print(i_count) 找不到 inner() outer() # print(o_count) #找不到
当然,local和enclosing是相对的,enclosing变量相对上层来说也是local。
5.2 作用域产生
在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如if、try、for等)是不会引入新的作用域的,如下代码:
if 2>1: x = 1 print(x) # 1
这个是没有问题的,if并没有引入一个新的作用域,x仍处在当前作用域中,后面代码可以使用。
def test(): x = 2 print(x) # NameError: name 'x2' is not defined
def、class、lambda是可以引入新作用域的。
5.3变量的修改
################# x=6 def f2(): print(x) x=5 f2() # 错误的原因在于print(x)时,解释器会在局部作用域找,会找到x=5(函数已经加载到内存), 但x使用在声明前了,所以报错: # local variable 'x' referenced before assignment. #如何证明找到了x=5呢?简单:注释掉x=5,x=6 # 报错为:name 'x' is not defined #同理 x=6 def f2(): x+=1 #local variable 'x' referenced before assignment. f2()
5.4 global关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了,当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下,代码如下:
count = 10 def outer(): global count print(count) count = 100 print(count) outer() #10 #100
5.5 nonlocal关键字
global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量怎么办呢,这时就需要nonlocal关键字了
def outer(): count = 10 def inner(): nonlocal count count = 20 print(count) inner() print(count) outer() #20 #20
5.6小结
(1)变量查找顺序:LEGB,作用域局部>外层作用域>当前模块中的全局>python内置作用域; (2)只有模块、类、及函数才能引入新作用域; (3)对于一个变量,内部作用域先声明就会覆盖外部变量,不声明直接使用,就会使用外部作用域的变量; (4)内部作用域要修改外部作用域变量的值时,全局变量要使用global关键字,嵌套作用域变量要使用 nonlocal关键字。nonlocal是python3新增的关键字,有了这个 关键字,就能完美的实现闭包了。
六,递归函数
定义:在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
实例1(阶乘)
def factorial(n): result=n for i in range(1,n): result*=i return result print(factorial(4)) #**********递归********* def factorial_new(n): if n==1: return 1 return n*factorial_new(n-1) print(factorial_new(3))
实例二(斐波那契数列)
def fibo(n): before=0 after=1 for i in range(n-1): ret=before+after before=after after=ret return ret print(fibo(3)) #**************递归********************* def fibo_new(n):#n可以为零,数列有[0] if n <= 1: return n return(fibo_new(n-1) + fibo_new(n-2)) print(fibo_new(3)) print(fibo_new(30000))#maximum recursion depth exceeded in comparison
递归函数的优点: 是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
递归特性:
- 1. 必须有一个明确的结束条件
- 2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
- 3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返 回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。)
七,内置函数
1 ,filter(function, sequence):
对sequence中的item依次执行function(item),将执行结果为True的item做成一个filter object的迭代器返回。可以看作是过滤函数。
str = ['a', 'b','c', 'd'] def fun1(s): if s != 'a': return s ret = filter(fun1, str) print(list(ret))# ret是一个迭代器对象
2, map(function, sequence)
map()函数,可以将一个函数映射到一个可以枚举类型上面。对sequence中的item依次执行function(item),将执行结果组成一个List返回
另外map也支持多个sequence,当然这也要求function支持相应数量的参数输入。
str = ['a', 'b','c','d'] def fun2(s): return s + "alvin" ret = map(fun2, str) print(ret) # map object的迭代器 print(list(ret))# ['aalvin', 'balvin', 'calvin', 'dalvin']
>>> def cube(x): return x*x*x >>> map(cube, range(1, 11)) [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] >>> def cube(x) : return x + x >>> def add(x, y): return x+y >>> map(add, range(8), range(8)) [0, 2, 4, 6, 8, 10, 12, 14]
对sequence中的item依次执行function(item),将执行结果组成一个map object迭代器返回.
map也支持多个sequence,这就要求function也支持相应数量的参数输入:
def add(x,y): return x+y print (list(map(add, range(10), range(10)))) ##[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
3,reduce(function, sequence, starting_value)
对sequence中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用.
from functools import reduce def add1(x,y): return x + y print (reduce(add1, range(1, 101)))## 4950 (注:1+2+...+99) print (reduce(add1, range(1, 101), 20))## 4970 (注:1+2+...+99+20)
对sequence中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用,例如可以用来对List求和,示例如下:
>>> def add(x,y): return x + y >>> reduce(add, range(1, 11)) (注:1+2+3+4+5+6+7+8+9+10) >>> reduce(add, range(1, 11), 20) (注:1+2+3+4+5+6+7+8+9+10+20)
4,lambda
它允许你快速定义单行的最小函数(其本质就是函数),类似C语言中的宏,可以用在任何需要函数的地方,示例如下:
>>> g = lambda x: x * 2 >>> g(3) 6 >>> (lambda x: x * 2)(3) 6
普通函数与匿名函数的对比:
#普通函数 def add(a,b): return a + b print add(2,3) #匿名函数 add = lambda a,b : a + b print add(2,3) #========输出=========== 5 5
匿名函数的命名规则,用lamdba 关键字标识,冒号(:)左侧表示函数接收的参数(a,b) ,冒号(:)右侧表示函数的返回值(a+b)。
因为lamdba在创建时不需要命名,所以,叫匿名函数
5,int64和int的区别?
int是Python的基本类型,而int64是numpy中引入的一个类,即numpy.int64;
使用numpy模块中的numpy.sum()方法得到的结果是Int64,而使用默认的sum()得到的结果是Int类型。
下面代码中,虽然执行结果a和b的数值都是6,但是类型不同。
1 import numpy as np 2 # a 的类型是int64 3 a = np.sum([1,2,3]) 4 # b 的类型是int 5 b = sum([1,2,3])
int64不会报除0的错误(事实上numpy中的其他类型也不会报错,比如float64等);而int会报除0错误(其他基本类型也会,比如float等)
6,sorted函数
对sequence进行排序,直接看例子:
>>> s = [('a', 3), ('b', 2), ('c', 1)] >>> sorted(s, key=lambda x:x[1]) s = [('a', 3), ('b', 2), ('c', 1)]
更多例子:
>>> l = ['foo', 'bar', 'far'] >>> map(lambda x: x.upper(), l) ['FOO', 'BAR', 'FAR'] >>> filter(lambda x: 'f' in x, l) ['foo', 'far'] >>> map(lambda x: x.upper(), filter(lambda x: 'f' in x, l)) ['FOO', 'FAR'] >>> reduce(lambda a, b: a * b, xrange(1, 5)) # 计算 1*2*3*4 = 24 24
7,序列解包(for x,y in zip(keys,values):)详解
序列解包是一个非常重要和常用的一个功能,使用序列解包可以用非常简洁的方法完成复杂的功能。增强代码的可读性,减少代码量。
zip函数接受多个(包括0个和1个)序列作为参数,返回一个tuple列表。
7.1 使用序列解包对多个变量同时进行赋值
a, b, c = 1, 2, 3 print(a, b, c) test_tuple = (False, 3.5, 'test') d, e, f = test_tuple print(d, e, f) x, y, z = map(str, range(3)) print(x, y, z)
输出结果为:
1 2 3 False 3.5 test 0 1 2
7.2 序列解包也可以用于列表和字典
字典的话默认是对“key” 进行操作,如需对“key”----"value" 进行操作则需要使用字典的items()方法进行操作。“value” 进行操作的话就使用values进行操作。
#列表进行解包 a = [1, 2, 3, 5, 6] b, c, d, f, g = a print(b, c, d, f, g) print('---------------------------') #字典进行解包 test_dicts = {'a': 'x', 'b': 1, 'c': 3} q, w, e = test_dicts r, t, y = test_dicts.items() i, o, p = test_dicts.values() print(q, w, e) print(r, y, t) print(i, o, p)
输出结果:
1 2 3 5 6 --------------------------- a b c ('a', 'x') ('c', 3) ('b', 1) x 1 3
7.3 用序列解包同时遍历多个序列
list_1 = [1, 2, 3, 4] list_2 = ['a', 'b', 'c'] for x, y in zip(list_1, list_2): print(x, y)
结果:
1 a 2 b 3 c
7.4 Python3.x和Python2.x在zip函数的区别
x = [1, 2, 3] y = [4, 5, 6] z = [7, 8, 9] #注意:Python3.x和Python2.x这里的差别 #Python3.x xyz = list(zip(x, y, z)) #Python2.x #xyz = zip(x, y, z) print(xyz) #输出结果:[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
八 函数式编程
学会了上面几个重要的函数后,我们就可以来聊一聊函数式编程到底是个什么鬼
一 概念(函数式编程)
函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,常见的面向对象编程是也是一种命令式编程。
而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式。
函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如y=x*x函数计算x的平方根,只要x的平方,不论什么时候调用,调用几次,值都是不变的。
纯函数式编程语言中的变量也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是不可变的(immutable),也就是说不允许像命令式编程语言中那样多次给一个变量赋值。比如说在命令式编程语言我们写“x = x + 1”,这依赖可变状态的事实,拿给程序员看说是对的,但拿给数学家看,却被认为这个等式为假。
函数式语言的如条件语句,循环语句也不是命令式编程语言中的控制语句,而是函数的语法糖,比如在Scala语言中,if else不是语句而是三元运算符,是有返回值的。
严格意义上的函数式编程意味着不使用可变的变量,赋值,循环和其他命令式控制结构进行编程。
函数式编程关心数据的映射,命令式编程关心解决问题的步骤,这也是为什么“函数式编程”叫做“函数式编程”。
二 实例
假如,现在你来到 baidu面试,面试官让你把number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8]中的正数的平均值,你肯定可以写出:#计算数组中正整数的平均值 number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8] count = 0 sum = 0 for i in range(len(number)): if number[i]>0: count += 1 sum += number[i] print sum,count if count>0: average = sum/count print average #========输出=========== 30 6 5
首先循环列表中的值,累计次数,并对大于0的数进行累加,最后求取平均值。
这就是命令式编程——你要做什么事情,你得把达到目的的步骤详细的描述出来,然后交给机器去运行。
这也正是命令式编程的理论模型——图灵机的特点。一条写满数据的纸带,一条根据纸带内容运动的机器,机器每动一步都需要纸带上写着如何达到。
那么,不用这种方式如何做到呢?
number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8] positive = filter(lambda x: x>0, number) average = reduce(lambda x,y: x+y, positive)/len(positive) print average #========输出=========== 5
这段代码最终达到的目的同样是求取正数平均值,但是它得到结果的方式和 之前有着本质的差别:通过描述一个列表->正数平均值 的映射,而不是描述“从列表得到正数平均值应该怎样做”来达到目的。
再比如,求阶乘
通过Reduce函数加lambda表达式式实现阶乘是如何简单:
from functools import reduce print (reduce(lambda x,y: x*y, range(1,6)))
又比如,map()函数加上lambda表达式(匿名函数)可以实现更强大的功能:
squares = map(lambda x : x*x ,range(9)) print (squares)# <map object at 0x10115f7f0>迭代器 print (list(squares))#[0, 1, 4, 9, 16, 25, 36, 49, 64]
三 函数式编程有什么好处呢?
1)代码简洁,易懂。
2)无副作用
由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是引用透明(Referential transparency)的和没有副作用(No Side Effect)。
练习:
1,简述普通参数,指定参数,默认参数,动态参数的区别
普通参数:以正确的顺序传入函数,调用时数量必须和声明的一样 指定参数:参数和函数调用关系密切,函数调用使用关键字参数来确定传入的参数值,参数 允许函数调用时参数的顺序和声明时不一致 默认参数:函数进行调用时,如果没有新的参数传入则默认的情况下,就调用默认参数 动态参数:个别函数能处理比当初声明时更多的参数,这些参数就动态参数
2,写函数,计算传入的字符串中数字,字母,空格,以及其他的个数
def func(s): al_num =0 space_num = 0 digit_num = 0 others_num = 0 for i in s: if i.isdigit(): digit_num +=1 elif i.isspace(): space_num +=1 elif i.isalpha(): al_num +=1 else: others_num +=1 return (al_num,space_num,digit_num,others_num) result = func("asdsadjlk1212jdjakdk2 d d d d323233223下") print(result) result = func(" d d d d323233223下") print(result)
3,写函数,判断用户传入的对象(字符串,列表,元组)长度是否大于5
def func(s,lis,tup): zifuchuan = len(s) liebiao = len(lis) yuanzu = len(tup) if zifuchuan>5: print("大于5") else: print("小于5") if liebiao >5: print("大于5") else: print("小于5") if yuanzu >5: print("大于5") else: print("小于5") return (zifuchuan,liebiao,yuanzu) func('dadadad','[1,2,3]',{1,2,3})
4,写函数监测用户传入的对象(字符,列表,元组)的每一个元素是否有空内容
def func(n): for i in a: i = str(i) if '' in i: return ('空格: ',i) else: return ('没空格') a = ('dasdsd dasd','ds') res = func(a) print(res)
5,写函数,检查传入列表的长度,如果大于2,那么仅仅保留前两个长度的内容,并将新内容返回给调用者
def func(li): len_li = len(li) if len_li>2: print("列表长度大于2") new_li = li[0:2] return (new_li) res = func([12,12,45,78,32,12]) print(res)
6,写函数,检查获取传入列表或元组的所有奇数位索引对应的元素,并将其作为新列表返回给调用者
def func(li,tup): li = [] tup = [] for i in range(len(li)): if i %2 ==1: li.append(li[i]) print(li) for j in range(len(tup)): if j %2 ==1: tup.append(tup[j]) print(tup) return (li,tup) res = func([1,2,3,4,5,6,7,8,9],(1,2,3,11,21,4,5,6,7)) print(res)
7,写函数,检查传入字典的每一个value的长度,如果大于2,那么仅仅保存前两个长度的内容,并将新内容返回给调用者
8,写函数,计算传入字符串中的【数字】、【字母】、【空格】和【其他】的个数
# 8,写函数,计算传入字符串中的【数字】、【字母】、【空格】和【其他】的个数 def func(strr): digit_number = 0 space_number = 0 alpha_number = 0 else_number = 0 for i in strr: if i.isdigit(): digit_number +=1 elif i.isspace(): space_number +=1 elif i.isalpha(): alpha_number +=1 else: else_number +=1 return ("数字,空格,字母,其他内容分别有:",(digit_number,space_number,alpha_number,else_number)) res = func('sda!@#$%^&1234567dfghj da da ') print(res) # ('数字,空格,字母,其他内容分别有:', (7, 3, 12, 7))
此文内容参考http://www.cnblogs.com/yuanchenqi/articles/5828233.html