【Chapter 3.2】Python 的函数
3.2 函数
Functions是python中很重要的概念。可以用def来定义;
def my_function(x, y, z=1.5):
if z > 1:
return z * (x + y)
else:
return z / (x + y)
1 Namespaces, Scope, and Local Functions(命名空间,作用范围,局部函数)
scope分两种,global and local (全局和局部)。namespace用来描述变量的作用范围。当调用一个函数的时候,会自动创建一个局部命名空间,用来存放函数的参数,一旦函数结束,局部命名空间会被自动废弃掉。考虑下面的例子:
def func():
a = []
for i in range(5):
a.append(i)
当func()被调用,会创建一个空list a,然后5个元素赋给a。函数结束后,a也会被废弃。假设有下面的定义:
a = []
def func():
for i in range(5):
a.append(i)
给函数范围外的变量赋值是可行的,但是这些变量必须通过global关键字来声明:
a = None
def bind_a_variable():
global a
a = []
bind_a_variable()
In [170]: print(a)
[]
注意:
这里我们不推荐使用global关键字。因为这个全局变量通常用于存储系统状态(state in a system),如果你用了很多全局变量,或许你该考虑使用class。
2 Returning Multiple Values(返回多个值)
Python 中函数可以返回多个值。下面是一个简单的例子:
def f():
a = 5
b = 6
c = 7
return a, b, c
a, b, c = f()
在数据分析和其他科学计算应用中,你会发现自己常常这么干。
原理:其实函数还是返回了一个object,即tuple,然后这个tuple被解压给了result variables. 比如:
return_value = f()
这样的话,return_value就是一个3个返回值的三元元组 。
此外,还有一种非常具有吸引力的多值返回方式——返回字典:
def f():
a = 5
b = 6
c = 7
return {'a' : a, 'b' : b, 'c' : c}
3. 函数也是对象
因为函数是对象,所以很多构造能轻易表达出来。比如我们要对下面的string做一些数据清洗:
In [176]: states = [' Alabama ', 'Georgia!', 'Georgia', 'georgia',
...: 'FlOrIda', 'south carolina##', 'West virginia?']
...:
要想让这些string统一,我们要做几件事:去除空格,删去标点符号,标准化大小写。
做法一 :是利用内建函数和re模块(正则表达式):
import re
def clean_strings(strings):
result = []
for value in strings:
value = value.strip()
value = re.sub('[!#?]', '', value)
value = value.title()
result.append(value)
return result
clean_string(states)
['Alabama',
'Georgia',
'Georgia',
'Georgia',
'Florida',
'South Carolina',
'West Virginia']
还有一种做法,把一系列操作放在一个list里:
def remove_punctuation(value):
return re.sub('[!#?]', '', value)
clean_ops = [str.strip, remove_punctuation, str.title]
def clean_strings(strings, ops):
result = []
for value in strings:
for function in ops:
value = function(value)
result.append(value)
return result
clean_strings(states, clean_ops)
一个更函数化的方式能让你方便得在一个高等级上转变string。可以把函数当做其他函数的参数,比如用内建的map函数,这个map函数能把一个函数用于一个序列上:
for x in map(remove_punctuation, states):
print(x)
Alabama
Georgia
Georgia
georgia
FlOrIda
south carolina
West virginia
4.匿名(lambda)函数
这种函数只有一行,结果能返回值。下面两个函数是一样的效果:
def short_function(x):
return x * 2
equiv_anon = lambda x: x * 2
之后我们只称其为lambda函数。这种函数在数据分析方面非常有用,就因为方便。比如下面的例子:
def apply_to_list(some_list, f):
return [f(x) for x in some_list]
ints = [4, 0, 1, 5, 6]
apply_to_list(ints, lambda x: x * 2)
假设你想按不同字母的数量给一组string排序:
In [178]: strings.sort(key=lambda x: len(set(list(x))))
In [179]: strings
Out[179]: ['aaaa', 'foo', 'abab', 'bar', 'card']
5. 柯里化:局部参数应用
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
简单的说,通过局部参数应用,在一个原有函数的基础上,构造一个新的函数。比如,我们想要一个加法的函数:
def add_number(x, y):
return x + y
通过上面这个函数,我们可以衍生出一个只需要一个参数的新方程,add_five,即把5加到参数上:
add_five = lambda y: add_number(5, y)
其中第二个参数y叫做被柯里化了。这其实没什么特别的,我们做的其实就是用一个已有的函数定义了一个新函数。而内建的functools模块里的 partial函数能简化这个操作:
from functools import partial
add_five = partial(add_number, 5)
6. Generators(生成器)
这个东西在python里很重要,能用来迭代序列。比如,迭代一个字典:
some_dict = {'a': 1, 'b': 2, 'c': 3}
for key in some_dict:
print(key)
当我们写for key in some_dict
的时候,python解释器就新建了一个iterator:
dict_iterator = iter(some_dict)
dict_iterator
一个生成器能产生object给python 解释器,当遇到for loop这样的情景时。大部分方法,除了list之类的object,都能接受迭代器。比如内建的函数min, max, sum,或是类型构造器 list, tuple:
list(dict_iterator)
['a', 'b', 'c']
生成器是用于构造迭代对象的简洁方式。不像其他函数一口气执行完,返回一个结果,生成器是多次返回一个序列,每请求一次,才会返回一个。用 yield 可以构建一个生成器:
def squares(n=10):
print('Generating squares from 1 to {0}'.format(n**2))
for i in range(1,n+1):
yield i**2
当你实际调用一个生成器的时候,不会有代码立刻执行:
gen = squares()
gen
Out[198]:
知道我们发出请求,生成器才会执行代码:
for x in gen:
print(x, end=' ')
Generating squares from 1 to 100
1 4 9 16 25 36 49 64 81 100
Generator expresssions (生成器表达式)
另一个构造生成器的方式是利用生成器表达式。写法就像列表表达式一样,只不过使用括号:
gen = (x **2 for x in range(100))
gen
Out[201]: at 0x0000018B5DDD37D8>
上面的代码和下面冗长的代码是等效的:
def _make_gen():
for x in range(100):
yield x ** 2
gen = _make_gen()
生成器表达式也可以取代列表推导式,作为函数参数:
dict((i, i**2) for i in range(5))
itertools模块
itertools模块有很多常用算法的生成器。比如groupby能取任意序列当做函数。
import itertools
first_letter = lambda x: x[0]
names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
for letter,group in itertools.groupby(names,first_letter):
print(letter,list(group))
A ['Alan', 'Adam']
W ['Wes', 'Will']
A ['Albert']
S ['Steven']
itertools.groupby(iterable[, key]) 返回一个产生按照key进行分组后的值集合的迭代器.
如果iterable在多次连续迭代中生成了同一项,则会定义一个组,如果将此函数应用一个分类列表,那么分组将定义该列表中的所有唯一项,key(如果已提供)是一个函数,应用于每一项,如果此函数存在返回值,该值将用于后续项而不是该项本身进行比较,此函数返回的迭代器生成元素(key, group),其中key是分组的键值,group是迭代器,生成组成该组的所有项。
即:按照keyfunc函数对序列每个元素执行后的结果分组(每个分组是一个迭代器), 返回这些分组的迭代器
itertools.groupby(names, first_letter)
,把names中的每个字符串放入到first_letter这个函数中,得到了首字母作为key,赋给了letter。但后面那个group的结果我没看懂,Albert也是A开头的,为什么没有归到第一组里呢? 答案由github用户RookieDay提供,具体内容如下:
itertools.groupby(iterable[, key])
根据文档里面的这段解释
The operation of groupby() is similar to the uniq filter in Unix. It generates a break or new group every time the value of the key function changes (which is why it is usually necessary to have sorted the data using the same key function).
可以发现,这里groupby的使用类似于Unix里面的uniq(uniq可检查文本文件中重复出现的行 --> 重复行是指连续出现的重复行)命令,key函数值的改变会中断或者生成新的组(这就是为什么使用相同key函数对数据排序的原因),所以这里groupby 可以理解为把迭代器中相邻的重复元素挑出来放在一起(参考廖雪峰groupby)
groups = []
uniquekeys = []
data = sorted(data, key=keyfunc)
for k, g in groupby(data, keyfunc):
groups.append(list(g)) # Store group iterator as a list
uniquekeys.append(k)
所以如果想要让Albert与其他两个A开头的名字分到一组的话,可以先对names排序,让三个A开头的名字相邻,然后再用groupby:
表3-2中列出了一些我经常用到的itertools函数。建议参阅Python官方文档,进一步学习。
7. 错误和异常处理
在数据分析应用中,许多函数只能用于特定的输入。比如float能把string变为浮点数,但如果有不正确的输入的话会报错:
捕捉多个不同的异常:
def attempt_float(x):
try:
return float(x)
except (TypeError, ValueError):
return x
在某些情况下,你不想抑制任何异常,但你想希望执行某些代码,不论try里的代码成功执行与否。这样的话,需要用的finally:
f = open(path, 'w')
try:
write_to_file(y)
finally:
f.close()
这样的处理会始终会让f关闭。同样的,你可以在try里的代码成功执行后,让某些代码执行:
f = open(path, 'w')
try:
write_to_file(f)
except:
print('Failed')
else:
print('Succeeded')
finally:
f.close()
Exceptions in IPython (IPython中的异常)
当使用%run执行代码片段,引发异常后,IPython中默认打印出所有的调用信息(traceback)
%run example/ipython_bug.py
ERROR:root:File `'example/ipython_bug.py'` not found.