1 自定义函数
Python中使用def
语句来创建自定义函数.
def hello(name):
print("hello", name)
#hello()
hello("shenxiaotao")
## hello shenxiaotao
和R一样,可以给参数默认值.
def hello(name = "xiaotao"):
print("hello", name)
#hello()
hello()
## hello xiaotao
1.1 给函数编写文档
在def
语句后面,就可以添加该函数的文档说明.称之为文档字符串(docstring).
def square(x):
'Calculates the square of the number x.'
return x * x
文档字符串如果只有一行,可以使用引号括起来,如果有多行,需要使用三引号括起来.
通过下列语句访问文档字符串.
square.__doc__
## 'Calculates the square of the number x.'
_doc_
其实时函数一个方法.双下划线代表他是函数的一个特殊属性.
也可以使用help()
函数获得信息.
help(square)
## Help on function square in module __main__:
##
## square(x)
## Calculates the square of the number x.
一个特殊的语句,return
跟R中一样,也是可以提前结束函数的.
def test():
print("test")
return
print("test2")
test()
## test
在def语句中,位于函数名后面的变量通常称为形参,而调用函数时提供的值称为实参,但本书基本不对此做严格的区分。在很重要的情况下,我会将实参称为值,以便将其与类似于变量的形参区分开来。
1.2 作用域
跟R一样,python中也是存在局部和全局作用域,在函数内部对变量进行操作都是在该函数的局部作用域进行操作的,不会对函数外部的其他局部作用域以及全局作用域造成影响.
使用切片可以对列表等创建符本,这样可以避免在修改某一个变量时,对该变量关联的列表也发生了变化.
函数内的局部名称(包括参数)会与函数外的名称(即全局名称)冲突吗?答案是不会。
1.3 关键字参数和默认值
使用名称指定的参数称为关键字参数,主要优点是有助于澄清各个参数的作用。关键字参数最大的优点在于,可以指定默认值。
1.4 收集参数
在R中,我们知道,有时候需要用户提供长度不同的参数,这在R中,是通过使用...
来实现的,那再python中呢?是通过在参数名字前加一个*
实现的.
def print_params(*params):
print(params)
print_params("shen")
## ('shen',)
print_params("shen", "wang")
## ('shen', 'wang')
print_params(1, 2, 3)
## (1, 2, 3)
如果某个参数前面加了星号,那么该参数下的所有实参,会被放在一个元组中.
def print_params_2(title, *params):
print(title)
print(params)
print_params_2('Params:', 1, 2, 3)
## Params:
## (1, 2, 3)
带星号的参数将会收集之后的所有输入的参数.
如果需要将带星号参数放在中间,那么其后面的参数在调用时,必须指明形参.
def in_the_middle(x, *y, z):
print(x, y, z)
in_the_middle(1,2,3, z = 4)
## 1 (2, 3) 4
型号时不会收集关键字参数的,如果要收集,可以使用两个星号.
def print_params_3(**params):
print(params)
print_params_3(x = 1, y = 2, z = 3)
## {'x': 1, 'y': 2, 'z': 3}
可以看到,这时候将其作为字典使用.
1.5 分配参数
前面介绍了如何将参数收集到元组和字典中,但用同样的两个运算符(*和**
)也可执行相反的操作。
def add(x, y):
return x + y
params = (1, 2)
add(*params)
## 3
2 作用域
变量到底是什么呢?可将其视为指向值的名称。因此,执行赋值语句x=1后,名称x指向值1。这几乎与使用字典时一样(字典中的键指向值),只是你使用的是“看不见”的字典。
这种“看不见的字典”称为命名空间或作用域。那么有多少个命名空间呢?除全局作用域外,每个函数调用都将创建一个。
在函数内使用的变量称为局部变量(与之相对的是全局变量)。参数类似于局部变量,因此参数与全局变量同名不会有任何问题。
到目前为止一切顺利。但如果要在函数中访问全局变量呢?如果只是想读取这种变量的值(不重新关联它),通常不会有任何问题。
但是,在函数中访问全局变量是众多bug的根源。务必慎用全局变量。
读取全局变量的值通常不会有问题,但还是存在出现问题的可能性。如果有一个局部变量或参数与你要访问的全局变量同名,就无法直接访问全局变量,因为它被局部变量遮住了。
如果需要,可使用函数globals
来访问全局变量。这个函数类似于vars
,返回一个包含全局变量的字典。(locals
返回一个包含局部变量的字典。)
重新关联全局变量(使其指向新值)是另一码事。在函数内部给变量赋值时,该变量默认为局部变量,除非你明确地告诉Python它是全局变量。那么如何将这一点告知Python呢?
x = 1
def change_global():
global x
x = x + 1
change_global()
x
## 2
可以看到,需要使用global
语句来申明,从而改变全局变量.
3 函数式编程
至此,你可能习惯了像使用其他对象(字符串、数、序列等)一样使用函数:将其赋给变量,将其作为参数进行传递,以及从函数返回它们。在有些语言(如scheme和Lisp)中,几乎所有的任务都是以这种方式使用函数来完成的。在Python 中,通常不会如此倚重函数(而是创建自定义对象,这将在下一章详细介绍),但完全可以这样做。Python提供了一些有助于进行这种函数式编程的函数:map
、filter
和reduce
。在较新的Python版本中,函数map和filter的用途并不大,应该使用列表推导来替代它们。你可使用map将序列的所有元素传递给函数。
其中map()
函数和R中的apply
家族或者purrr
的map()
家族函数非常像,有点是向量化处理的感觉.
举个例子.
[str(i) for i in range(10)]
## ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
还可以写作:
map(str, range(10))
## <map object at 0x00000000525EBF08>
list(map(str, range(10)))
## ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
map()
函数直接返回的是一个迭代器,因此需要使用list()
函数将他转变为list.使用R可以这么写.
sapply(c(0:9), as.character)
## [1] "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
unlist(purrr::map(c(0:9), as.character))
## [1] "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
filter()
函数非常好用,是一个用来筛选的函数.用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
使用方法:
filter(function, iterable)
function:函数.
iterable:可迭代的对象.
举个例子:
def biggerThan10(x):
if x > 10:
return(True)
else:
return(False)
biggerThan10(10)
## False
filter(biggerThan10, range(0,15,1))
## <filter object at 0x00000000525F1508>
list(filter(biggerThan10, range(0,15,1)))
## [11, 12, 13, 14]
filter()
返回的也是一个迭代器,需要将其转变为list.
当然,也可以使用列表推导,这样就不要自定义函数了.
[x for x in range(0,15,1) if x >10]
## [11, 12, 13, 14]
代码看起来更为简洁,并且也更加清晰.
3.1 lambda表达式
Python提供了一种名为lambda表达式的功能,让你能够创建内嵌的简单函数(主要供map、filter和reduce使用)。
比如上面的map()
和filter()
函数的例子就可以改为下面:
list(filter(lambda x: x > 10, range(0, 15,1)))
## [11, 12, 13, 14]
可以看到使用lambda表达式,其实是创建了一个简单的匿名函数,起到的作用和匿名函数是相同的,只不过更加简洁易读而已.
当然,其实大部分情况下,使用列表推导也非常简洁,所以很多情况下可以使用列表推导就可以了.但是特殊例子在函数reduce()
中.
3.2 redunce()
函数会对参数序列中元素进行累积。
函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2个元素进行操作,得到的结果再与第三个数据用function 函数运算,最后得到一个结果。
使用方法:
reduce(function, iterable[, initializer])
参数:
function: 函数.
iterable: 可迭代对象.
initializer: 可选,初始参数.
例子:
numbers = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
from functools import reduce
reduce(lambda x, y: x + y, numbers)
## 1161
4 总结
新的函数:
Function | Meaning |
---|---|
map(func, seq[, seq, ...]) |
对序列中的所有元素执行函数 |
filter(func, seq) |
返回一个列表,其中包含对其执行函数时结果为真的所有元素 |
reduce(func, seq[, initial]) |
等价于func(func(func(seq[0], seq[1]), seq[2]), …) |
sum(seq) |
返回seq 中所有元素的和 |
apply(func[, args[, kwargs]]) |
调用函数(还提供要传递给函数的参数) |