您的位置:首页 > 编程语言 > Python开发

Python数据分析 | (3)Python函数和异常处理

2018-12-22 16:17 519 查看

本篇博客所有示例使用Jupyter NoteBook演示。

Python数据分析系列笔记基于:利用Python进行数据分析(第2版)  

目录

1.Python函数

2.生成器

3.itertools模块

4.错误和异常处理

1.Python函数

函数是Python中最重要的也是最主要的代码组织和复用手段。作为最重要的原则,如果你要重复使用相同或类似的代码,就需要写一个函数。通过给函数起一个名字,可以提高代码的可读性。

  • 函数的定义

用def关键字声明,用return关键字返回值:

[code]def my_function(x,y,z=1.5):
if z>1:
return z*(x+y)
else:
return z/(x+y)

可以有多条return语句,如果达到函数末尾没有遇到任何一条return语句,则返回None。

函数可以有一些位置参数和一些关键字参数。关键字参数通常用于指定默认值或可选参数,如上例中的z。而x和y是位置参数。可以用以下几种方式调用上例中的函数:

[code]my_function(5,6,z=0.7)
my_function(3.14,7,3.5)
my_function(10,20)  #不传z,则使用默认值1.5

函数参数的限制在于:关键字参数必须在位置参数(如果有的话)之后。可以用任何顺序指定关键字参数,不用记住函数参数的顺序,只要记住名字就好了。也可以用关键字传递位置参数:

[code]my_function(x=5,y=6,z=7)
my_function(y=6,x=5,z=7)

此时位置参数的顺序可以是任意顺序,记住参数的名字就可以了,这种写法可以提高可读性。

  • 命名空间、作用域和局部函数

函数可以访问两种不同作用域中的变量:全局(global)和局部(local)。

Python用命名空间描述变量作用域的名称。任何函数中被赋值的变量默认分配到局部命名空间中,局部命名空间在函数被调用时创建,函数参数会立即填入该命名空间,函数执行完毕后,局部命名空间就会被销毁(有一些例外情况,之后还会介绍)。例如:

[code]def func():
a = []
for i in range(5):
a.append(i)
func()
print(a)

调用func()后,首先创建空列表a,然后添加5个元素,最后a会在函数退出的时候被销毁,print(a)为空:

可以在函数退出之前把a返回:

[code]def func():
a = []
for i in range(5):
a.append(i)
return a
a = func()
print(a)

可以在函数中对全局变量进行修改/赋值等操作:

[code]a = []
def func():
for i in range(5):
a.append(i)
func()
print(a)

如果函数中有与全局变量同名的局部变量,就近原则,函数对局部变量进行操作:

[code]a = []
def func():
a = []
for i in range(5):
a.append(i)
func()
print(a)

此时,如果想在函数中对全局变量进行修改/赋值,需要在函数中局部变量赋值之前,把a声明为global:

[code]a = []
def func():
global a
a = []
for i in range(5):
a.append(i)
func()
print(a)

再看一个例子:

[code]a = 5
def A():
a = -3
a += 2
A()
print(a)

[code]a = 5
def A():
global a
a = -3
a += 2
A()
print(a)

注意:不要频繁使用global关键字。因为全局变量一般用于存放系统的某些状态。如果发现自己用了很多,那可能要来点面向对象编程了(使用类)。

  • 返回多个值

和C++和Java不同,Python的函数可以返回多个值,非常强大:

[code]def f():
a = 5
b = 6
c = 7
return a,b,c
a,b,c = f()
a = f()[0] #如果需要其中某一个返回值 可以用索引

这个函数其实返回了一个对象,也就是一个元组,最后该元组会被拆分到各个结果变量中:、

[code]return_value = f()

此时return_value是一个包含3个返回值的3元元组。

还有一种非常具有吸引力的多值返回方式---返回字典:

[code]def f():
a = 5
b = 6
c = 7
return {'a':a,'b':b,'c':c}
  • 函数也是对象

Python中的函数也是对象。假设有下面这个字符串列表,希望对其进行一些数据清理工作,执行一系列转换:

[code]states = ['   Alabama ','Georgia!','Georgia','georgia','south  carolina##','West virginia?']

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

print(clean_strings(states))

还有一种办法是把对字符串处理的所有函数对象,放在一个列表中:

[code]states = ['   Alabama ','Georgia!','Georgia','georgia','south  carolina##','West virginia?']

import re
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
print(clean_strings(states, clean_ops))

也可以把函数对象作为其他函数的参数,比如内置的map函数,用于在一组数据上应用一个函数(可以是内置的,也可以是自定义的):

[code]for x in map(remove_punctuation,states):
print(x)
print("-----------")
for x in map(str.strip,states):
print(x)

  • 匿名(lambda)函数

lambda函数仅由单条语句组成,该语句的结果就是返回值,通过lambda关键字定义。

下面两种形式是等价的:

[code]def short_function(x):
return x*2

result = lambda x:x*2

lambda函数在数据分析工作中非常方便,你会发现很多数据转换函数都以函数对象作为参数。直接传入lambda函数比编写完整函数声明要简单很多,也更清晰。比如:

[code]def short_function(x):
return x*2

def apply_to_list(some_list,f):
return [f(x) for x in some_list]   #对列表中的每个元素 应用f函数

ints = [4,0,1,5,6]
apply_to_list(ints, short_function)

apply_to_list(ints, lambda x:x*2)

上面这个例子比较简单,可以直接编写[x*2 for x in ints],但是使用lambda函数可以非常轻松地传入一个自定义运算给apply_to_list函数。

 

假设有一组字符串,需要根据各字符串不同字母的数量对其进行排序:

[code]strings = ['foo','car','bar','aaaa','abab','momentum']
strings.sort(key=lambda x:len(set(list(x))))
strings

key参数传入一个自定义的排序运算,上例中是通过lambda函数定义这个运算并传入的,当然也可以用def定义一个运算再传入,lambda函数更简洁一些。

lambda函数之所以被称为匿名函数,与def声明的函数不同,原因之一是这种函数对象本身是没有提供__name__属性的。

  • 柯里化:部分参数应用

柯里化(currying)是一个计算机科学术语,指的是通过“部分参数应用”从现有函数派生出新函数的技术。

比如,我们执行一个两数相加的简答函数:

[code]def add_numbers(x,y):
return x+y

通过这个函数可以派生出一个新的只有一个参数的函数---add_five,用于对其参数加5:

[code]add_five = lambda y:add_numbers(5,y)

add_numbers的第二个参数称为“柯里化的"(curried)。其实就是定义了一个可以调用现有函数的新函数而已,内置的functools模块可以用partial函数将此过程简化:

[code]from functools import partial

add_five = partial(add_numbers,5)

2.生成器

  • 简单介绍

能以一种一致的方式对序列进行迭代(如列表中的元素或文件中的行等)是Python的一个重要特点。这是通过一种叫迭代器的协议(使对象可迭代的通用方式)的方式实现的,一个原生的使对象可迭代的方法。

比如,对字典进行迭代可以得到其所有的键:

[code]some_dict = {'a':1,'b':2,'c':3}
for key in some_dict:
print(key)
'''
for key in some_dict.keys():
print(key)
'''

当编写for key in some_dict 时,Python解释器首先会尝试从some_dict创建一个迭代器:

[code]dict_iterator = iter(some_dict)
print(dict_iterator)

迭代器是一种特殊的对象,他可以在诸如for循环之类的上下文向Python解释器输送对象(实体化),大部分能接受列表之类的对象的方法也都可以接受任何可迭代对象,如使用min、max、sum等内置函数以及list、tuple、set、dict等类型构造器也可以对迭代器对象进行实体化。

生成器是构造新的可迭代对象(迭代器)的一种简单形式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值后暂停,直到下一个值被请求时再继续。

要创建一个生成器,只需要把函数中的return替换为yeild即可:

[code]def square(n=10):
print("生成从1到{0}的平方数".format(n**2))
for i in range(1,n+1):
yield i**2
gen = square(10) #产生一个生成器,调用生成器,没有任何代码会被立即执行
print(gen)
#直到从生成器中请求元素时,代码才开始执行(对生成器实体化,和对迭代器进行实体化的方法一致)
for x in gen:
print(x,end=" ")

之前使用的reversed(range(n)),range(n)产生一个迭代器,reversed(range(n))产生一个生成器,产生一个新的迭代器。然后可以用之前提到的实体化方法进行实体化。

  • 生成器表达式

可以使用生成器表达式简洁的构造生成器。类似于列表、字典、集合推导式。把列表推导式两端的方括号改为圆括号:

下面两种方式等价:

[code]gen = (x**2 for x in range(100))
print(gen)

def make_gen():
for x in range(100):
yield x ** 2
gen = make_gen()
print(gen)

生成器表达式可以取代列表推导式,作为函数参数:

3.itertools模块

itertools模块中有一组用于许多常见数据算法的生成器。

例如,groupby可以接受任何序列和一个函数,根据函数的返回值对序列中的连续元素进行分组:

[code]import itertools

names = ['ADD','Alice','Bob','BDD','Mata','Ming','Meiko']

for letter,names in itertools.groupby(names,lambda x:x[0]):
print(letter,list(names))

常用的一些itertools函数:

 

4.错误和异常处理

错误和异常处理可以让程序更加健壮。

在数据分析中,许多函数只用于部分输入。如Python中的float函数只可把数字字符串转换为浮点数,输入有误时,会有ValueError错误:

[code]print(float('3.14'))
print(float('something'))

可以使用try/except进行异常处理:

[code]def attempt_float(x):
try:
return float(x)
except:
return x
print(attempt_float('3.14'))
print(attempt_float('sth'))

当float(x)抛出异常时,才会执行except部分。

float()抛出的异常可能不仅仅只有ValueError:

可能你只想处理ValueError和TypeError(输入不是字符串或数值):

[code]def attempt_float(x):
try:
return float(x)
except (ValueError,TypeError):
return x
print(attempt_float('3.14'))
print(attempt_float('sth'))
print(attempt_float((1,2)))

默认except:可以统一处理所有异常,如果你想处理其中的某些异常,可以使用元组包含多个 异常或对不同的异常进行不同的处理。

某些情况下,可能不像抑制异常,无论try是否成功,都执行一段代码,可以使用finally:

[code]f = open(path,'w')

try:
write_to_file(f)
except:
print("failed")
else:   #只在try部分成功的情况下执行代码
print('succeed')
finally:   #无论try部分是否成功 都执行
f.close()
  • Ipython中的异常

如果运行一段脚本或一条/若干语句时抛出异常,Ipython会默认打印完整的调用栈,栈的每个点都会有几行上下文:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: