《利用Python进行数据分析·第2版》【Chapter 3.2】Python 的函数

【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

<dict_keyiterator at 0x104c92ef8>

一个生成器能产生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]: <generator object squares at 0x0000018B5DD88BA0>

知道我们发出请求,生成器才会执行代码:

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]: <generator object <genexpr> 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.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容