Scott's Blog

学则不固, 知则不惑

0%

Python 函数、生成器、异常处理

函数:命名空间、多个值返回、函数作为对象、Lambda; 生成器:生成器表达式、itertools; 错误与异常捕获:try 语句的使用;

Python 函数

参数

函数是实现代码复用与组织化最重要的方法,函数用 def 关键字定义:

1
2
3
4
5
def my_func(x,y,z=1.5):
if z>1:
return z*(x+y)
else:
return z/(x+y)

python中返回多个值也是没问题的,如果你写的函数没有返回值,那么默认python会返回 None.

每个函数都有 positional argumentskeyword arguments. keyword arguments 在设置函数的默认参数的时候用的比较多,在前面的函数中,x和y是 positional arguments,而z是 keyword arguments

这些参数的顺序是有限制的:

keyword arguments must follow the positional arguments(if any).

命名空间

函数可以访问两种类型的变量,globallocal 的,中文叫全局的或局部的。

局部变量在函数运行的时候马上创建,如果函数运行完毕,马上会被销毁(也有例外,但不在这里的讨论范围之内)。

比如下面的函数:

1
2
3
4
def func():
a = []
for i in range(5):
a.append(i)

func()函数调用后,在它的内部创建了一个空的数组,然后5个元素添加了进去,但当函数运行结束后,a马上就会被销毁。

假设将代码改成这样:

1
2
3
4
a = []
def func():
for i in range(5):
a.append(i)

修改函数外面的变量是可能的,但这些变量必须声明为 global 类型:

1
2
3
4
5
a = None
def bind_a_variable():
global a
a = []
bind_a_variable() # 输出 []

个人不是很推荐使用 global 关键字,通常来说全局变量都用来存储一些系统的状态,如果你需要大量的使用,更推荐你用更面向对象的方式,例如类。

返回多个值

1
2
3
4
5
def f():
a = 5
b = 6
c = 7
return a,b,c

python的函数可以返回多个值,它们会被包装成一个元组,随后再被解包。

函数作为对象

因为python的函数也是对象,所以可以做很多别的语言很难做到的事情。 假设我们有一些数据需要清理:

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

如果你处理过用户提交上来的数据就懂我说的,很多简直想象不到输入都会发生,对于那些输入,我们要去掉首尾空格,多余的符号,正确的首字母大小写等等。一种方法是,我们可以用自带的re正则模块:

1
2
3
4
5
6
7
8
9
import re

def clean_strings(strings):
result = []
for value in strings:
value = value.strip()
value = re.sub('[!#?]','',value)
value = value.title()
result = append(value)

上面的结果看起来是这样的:

1
2
3
clean_strings(states)
输出:
['Alabama','Georgia', 'Georgia', 'Georgia', 'Florida', 'South Carolina', 'West Virginia']

我们函数作为对象,存到数组中,来实现这一需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def remove_punctuation(value):
return re.sub('[!#?]', '', value)
# 函数数组
clean_ops = [str.strip,remove_punctuation,str.title]

# ops参数接受函数数组对象
def clean_string(strings,ops):
result = []
for value in strings:
for function in ops:
value = function(value)
result.append(value)
return result

clean_string(states,clean_ops)

输出是一样的:
['Alabama','Georgia', 'Georgia', 'Georgia', 'Florida', 'South Carolina', 'West Virginia']

这种方式可以让你的代码更解耦。

用map方法也可以将一个函数作用到序列的每一个元素上:

1
2
for x in map(remove_punctuation,states):
print(x)

Lambda

Lambda是把那些很简短的函数形式做了简化,让你可以用一种非常简单的方式定义一个函数,然后将这个小函数用在需要它的地方,这在特定的情境下,非常方便:

1
2
3
4
def short_function(x):
return x* 2
# 等于
equiv_anon = lambda x: x*2

看下面代码:

1
2
3
4
5
def apply_to_list(some_list,f):
return [f(x) for x in some_list]

ints = [1,2,3,4]
apply_to_list(ints,lambdax:x*2)

你可以在参数中,直接定义要传进去的参数。

再来看另一个例子,你有一堆字符,你需要根据每一个字符中字母出现的个数多少来排序(重复的不算):

1
2
strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
strings.sort(key= lambda x: len(set(list(x))))
  • list,会将字母串分解
  • set,会去掉重复的字母

最后算出每个字母串的长度并以此排序。

lambda函数是没有__name__属性的

生成器

可以将iter函数作用到序列上,这样便返回了一个生成器。

1
2
3
4
dict_iterator = iter(some_dict)

dict_iterator
输出: <dict_keyiterator at 0x7fbbd5a9f908>

可以用list包含一个生成器,得到内部的值:

1
list(dict_iterator)

生成器是惰性的,比如你要读一个文件有一万行,你不需要一次性全部的读取,用生成器,你只需要一行一行的返回。

生成器表达式

生成器表达式和列表推导式有些类似,不过包裹它的是括号:

1
2
3
gen = (x ** 2 for x in range(100))
gen
# 输出 <generator object <genexpr> at 0x109cbf3b8>

上面这种写法和下面的代码完全一样:

1
2
3
4
def make_gen():
for x in range(100):
yield x**2
gen = make_gen()

生成器可以像数组一样当作函数的参数,比如计算list內所有数的和:

1
2
3
4
5
gen = [x ** 2 for x in range(100)]
sum(gen) # 输出328350

gen = (x ** 2 for x in range(100))
sum(gen) # 生成器当作参数,一样输出328350

itertools

itertools标准库为一些常用的数据,内置了一些通用的生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import itertools

first_letter = lambda x: x[0]

names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']

for letter, names in itertools.groupby(names, first_letter):
print(letter, list(names)) # names is a generator

# 输出
A ['Alan', 'Adam']
W ['Wes', 'Will']
A ['Albert']
S ['Steven']

错误与异常捕获

健壮的程序一定要有错误与异常捕获,我么要进行类型转化:

1
2
float('1.2345') ---> 1.2345
float('string') ---> value error

我们定义一个函数来捕获这个异常:

1
2
3
4
5
def attemp_float(x)
tyr:
return float(x)
except:
return x

这时候,如果捕获到了value error,就会返回输出的数据:

1
2
float('1.2345') ---> 1.2345
float('string') ---> string

函数有可能会返回别的错误,这时候需要把可能出现的错误写出来:

1
2
3
...
except(TypeError,ValueError)
...

在文件读取中,你打开了一个文件,不管你有没有操作,你都需要把它关闭,这时候就需要用到final语句,即不管前面是否捕获到了异常,最终都要做的事情:

1
2
3
4
5
f = open(path,'w')
try:
write_to_file(f)
finally:
f.close()