##一、函数
要注意的是,python函数的特点, python中一切都是对象,函数也是对象。这个点要从传统的C,JAVA转过来,一个函数可以像一个对象一样被引用,被赋值,作为参数传递给另一个函数,做返回值,还可以在字典,列表等里面使用。同样的,定义的一个类也是这样。
###1、定义函数
1.1 基本定义
和循环一样,用:和缩进替代{},假设定义一个绝对值函数
def my_abs(x):
if x >= 0:
return x
else:
return -x
如果在一个语句块中想什么也不做,使用pass,eg:
if a > 0:
pass
如果不加pass的话会报错
返回语句,由于Python无类型,所以不用声明返回值得类型。因此,更进一步的,返回值得个数也可以不加限定了,直接逗号分隔返回多个返回值,python会打包成tuple类型。
1.2 引用函数
你已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名(不含.py扩展名);
1.3 参数类型检查
由于Python是无类型的,但是方法可能传入任何类型的参数进来,所以方法的定义中需要的地方必须手动检查参数类型,否则就会出错。
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
用内置函数 isinstance(x, (int, float,…))检查
2 函数的参数
函数也可以赋值给一个变量,
Python的函数参数较为灵活和复杂,有基本参数,默认值参数,可变长参数, 关键字参数, 命名关键字参数。
2.1 基本参数 位置参数
注意一般参数也可以像命名参数一样指定参数名字,但不是必须,命名参数必须指定名字。
2.2 默认值参数
默认参数就是参数含有默认值,调用方法时可以对该参数传递值,也可不对改参数传递值。要求必须把必选参数放在参数序列前面,把默认参数放在参数序列的后面,这样便于编译器处理,也便于使用理解。
默认参数还可以在传参时指定传给谁,在前面加上参数名=xxx即可,表示将实参传递给哪个具体的形参,这样就可以不按定义的默认参数的顺序传递默认参数了。
eg:
print("xcxc", end ="")
就是这种调用
使用默认参数可以达到增加函数扩展性和简洁性的效果,比如要对原来的函数增加一个参数,又不影响以前的调用代码,使用默认参数即可。
Python默认值参数的坑 默认参数中的默认值是可变的!!!
假如入定义方法时对默认参数赋的值是一个可变对象,比如
def add_end(L=[]):
L.append('END')
return L
Python内部的处理应该是,在程序初始化是时候,创建一个对象X,并将它复制给L, 然后返回L。但是当你再次使用该函数时,内部不会再创建一个对象,而是使用最开始创建的那个对象X,所以当你第二次调用add_end()时,会有两个’END’,第n次调用时有n个’END’,为避免这种问题,所以默认参数使用的默认值必须使用不可变对象。
2.3 可变长参数 list
可变参数,和C,Java里面的一样,就是在方法中声明一个参数,此参数可以代表0-n个参数。
直接称列表参数或许更好
def test(a, *args):
xxxx
可变参数在函数调用时会自动组装成一个tuple。
这时可以传递0-N个参数进去.
还可以把集合类型传递给这个参数,方式如下
l=set[“aaa”,“bbb”, ‘ccc’]
method(*l)
即可
2.4 关键字参数 dict
1、其实前面的可变长参数相当于传递一个列表到函数中,这里的关键字参数就相当于一个dict-map到函数中,直接叫dict参数更好吧应该,如下:
def person(name, age, **kw):
print(name, age, kw)
person("小明", 5, country = "china", gender = "男")
m = {"country" : "china", "gender" : "男"}
person("小明", 5, **m)
可以看出,直接传递key = value列表的形式,或者传递组装好的map(前面加上 **)进去都行,直接传递的形式更为简洁
2.5 命名关键字参数 不常用应该 知道即可
还有一种叫做命名关键字参数,就是指定关键字参数的key列表
def person(name, age, *, city, job):
print(name, age, city, job)
语法上就是原来的参数列表后面,加一个间隔符*
,然后后面加上可选的关键字列表,也即key的序列,这个也可用一个可变长参数也可代替
命名参数也可以有自己的默认值,就像默认参数一样,不同的是,命名参数传值时必须指明传给那个命名关键字,而默认参数不一定需要,并且所以的不含默认值得命名参数都需要有只,所以命名参数相对于默认参数的优点是,操作更为明确。
2.6 参数的组合
由于有多种参数,他们之间的组合就比较讲究了,不然使用起来很容易混乱和出错。
参数定义的顺序必须是:一般参数、默认参数、可变参数、命名关键字参数和关键字参数。
虽然后面几种可以改变顺序,但是最好不要那样做,不然很容易混乱。
另外, 虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。
另外,函数还有这样的语法:
定义
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
然后调用
>>> args = (1, 2, 3, 4)
>>> f1(*args)
如果在list的元素个数足够的情况下了,解释器会逐一取出list的项填入到函数参数中,(不够会报错),对于关键字参数,则使用dict有一样的语法。
2.7 使用小技巧
对于默认值参数,可以使用 解dict语法,直接传递所以参数,不用一个个传例如
def mm(a = 1, b= 2):
…
调用
dd= {‘a’ = 2, ‘b’= 4)
调用
mm(**dd)
一个小点,传递参数时,一般参数也可以指定参数名,进行传值。
3.4 生成器
用于生成有限或无限个元素,并不是一次生成,而是调用一次,生成一次。可以使用next(g)方法,让它生成下一个元素。也可以在for循环中使用。
创建生成器的方法:
(1)直接使用 g = (x * x for x in range(10))
这种生成式,把生成列表时用的[]变成()括号,即可创建一个生成器,它不再是一个列表,不能使用列表相关的方法。而是按生成器的来使用。
(2)使用函数对象创建:
比如
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
调用next()时,遇到yield就返回一次值,然后函数暂停在这里,下次再调用next()函数从这里开始执行,直到遇到下一个yield,然后暂停,返回值。显然,对于循环中的next(),会循环的遇到yield。
含有yield的函数可以称作生成器函数,调用它之后返回的是一个生成器,并不是普通的返回值。比如
m = fib(6);
next(m);
或者
m = fib(b).__next__
#其中的__next_是生成器函数拥有的一个属性
m()
这两种方法让生成器进行下一次执行
可以直接对列表进行相加拼接 如 l[0:2] + s[8:]
这里可看出,Python的函数是能记录状态的,这和C中的函数是不一样的。在Python中它的实质和对象是没有区别的
重点内容
##二、函数式编程
函数式编程接近与数学计算,抽象程度很高,相应的和计算机底层结构差异越大,效率不如C语言等高。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
###1、高阶函数
函数 == 对象
函数式编程中,函数本身像一个对象一样,只是没有内部数据状态而,而函数名和变量名一样,指向某个函数对象的地址。
a、如f = abs
此时f就代表abs函数,能计算绝对值了。而将abs看做一个变量,它也是可变的!! 注意赋值是不带括号和参数的如果带了,就是执行了。
b、如果执行abs = 10,ads将不再能计算绝对值了,它将变成10这个数字。abs函数丢失了!,此时想要重新可用,需要重启Python交互式环境。
c、函数可以作为参数,返回值,容器元素等,被传递,存储等几乎任何变量使用的地方。
1.1 map/reduce
map就是映射,就想数学中的映射函数一样,是现实世界中一类十分常见的操作处理。map就是对一个序列中所有元素做某种变换,比如路径字符串替换前面的盘符。使用方式如下,先定义一个处理的函数f,然后放到map(f, Iterable)中,最后返回一个lterator惰性计算的序列。
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce:
reduce 和map的不同点是函数参数个数必须为2,即f(x1, x2),且操作序列的方式不同,它会将函数前一次计算的结果作为参数x1传入,然后取下一个数做x2传入,所以reduce的Iterable的元素个数至少为2,最后计算出一个结果。即
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
1.2 filter
如名,filter对列表中的元素进行过滤,传入一个返回boolean的函数即可。
1.3 sort
如名,对列表中的元素进行排序,使用方式
sorted([36, 5, -12, 9, -21])
直接排序
sorted([36, 5, -12, 9, -21], key=abs)
添加排序函数,此排序函数返回一个值,然后根据返回值排序
sorted([36, 5, -12, 9, -21], key=abs, reverse=True)
反向
以上元素能方便的转换成key的,如果有多级参数排序这种不好转换成key的,在Python2里面可以传入cmp函数,但是在Python3里面去掉了,搞了一个很复杂的操作。
2、返回函数
即在函数中创建另一个函数,然后作为返回值返回,此函数并不会立即执行,需要在后面进行显示的的调用之后才会执行。此函数可以访问外部函数的参数和局部变量,本质上是持有它们的引用, 可以访问这个引用,以及改变它所指向的对象内部变量的值,但是不能改变这个引用所指向的地址及对象,只能外部函数改变,这和Java的函数里面的匿名内部类相似,只是没有想Java里面一样final化,Python里面没有这个机制,所有会有后面一个问题q:,比如
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum(1, 3, 5, 7, 9)
print(f())
特点:
1、每次调用外部返回的函数都是不一样的。
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False
问题q:返回的函数是持有内部局部变量的引用,并不是直接拷贝一个,所以如果在下次执行之前,局部变量发生改变,函数持有的数据也是改变了的。比如下面这种:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
>>> f1()
9
>>> f2()
9
>>> f3()
9
#因为在循环中定义的函数,等到后面某个时间执行时,内部函数(闭包)指向的变量的值已经改变了,变成了3。所以输出结果都是9
对于循环内的变量,如果一定要引用,方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
再看看结果:
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
因为传参时会创建一个新的引用,count函数对i的改变,不可能再影响f的j参数了。缺点是代码较长,可利用lambda函数缩短代码。
2、lambda表达式——匿名函数
lambda表达式的格式:lambda 参数列表:一个表达式
参数列表可以有多种参数,当然,它还可以像一个普通的函数一样,可以赋值传递等。
使用lambda可以简化函数的编写,更加快捷
lambda表达式可用来编写跳转表(jump table),就是行为的列表或字典。例如:
L = [(lambda x: x**2),
(lambda x: x**3),
(lambda x: x**4)]
** 3、函数装饰器 **
顾名思义,对函数加上一层包装,增加其功能。
例如
def log(func):
@functools.wraps(func)
def wrapper(x):
print('start' + func.__name__)
ret = func(x)
print('end' + func.__name__)
return ret
return wrapper
@log
def abs(x):
if x > 0:
return x
else:
return -x
print(abs(-11))
Python里面的函数名可以通过赋值改变其指向,所以其中的@log就相当于依据 abs = log(abs) 此时abs就相当于wapper函数,可在wrapper中接收相同的参数,返回函数值等。
使用装饰器之后,原函数的函数名会改变,Python提供了一个funtools函数进行处理 如上面的@functools.wraps(func)就是把wrapper的函数名称变回去,有些依赖签名的工具需要这个。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')
执行结果如下:
>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。这样就能往第三层的函数里面传入相同的参数,并且可以使用第一层的属于Derector的参数了。巧妙的利用内部函数可以访问外部函数参数和局部变量的特性。Python里面函数基本也相当于对象了,外部函数的参数、局部变量被函数对象持有就不会消失。
4、functools提供的一些功能
functools.partial(f, a); 根据已有的函数f,自动传入部分参数,然后以此创建一个新的函数。编码中,通常需要创建某个函数的重载函数,原来函数所有参数都需要调用时传递,重载的只需传递部分的参数,另一部分在重载函数内传递默认值即可。functools.partial就是用来简化这种操作的。