Python中如何创建函数

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提供了一些有助于进行这种函数式编程的函数:mapfilterreduce。在较新的Python版本中,函数map和filter的用途并不大,应该使用列表推导来替代它们。你可使用map将序列的所有元素传递给函数。

其中map()函数和R中的apply家族或者purrrmap()家族函数非常像,有点是向量化处理的感觉.

举个例子.

[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]]) 调用函数(还提供要传递给函数的参数)

相关

下一页
上一页
comments powered by Disqus