其中,前五种类型是不可变类型,后三种是可变类型,而不可变类型才能作为集合的元素或者字典的键。
在第三天还讲了:
今天还会继续讲语法部分:
涉及的语法比较多,像匿名函数,类,包等,这些我们知道怎么用即可。
推导式是python比较特殊的语法,其他编程语言比较少见,使用起来很就方便,能提升代码的可读性。
主要包含:
这三种推导式的语法几乎是一样的,放到一起去讲。其语法结构:
例如,我们如果要计算列表中每个元素的平方值,组成新的列表:
data = [1, 2, 3]
# 使用循环的写法
new_data = []
for val in data:
new_data.append(val**2)
print(new_data)data = [1, 2, 3]
# 使用推导式的写法
# 如果是元组,则将下面语句的中括号改为小括号
new_data = [val**2 for val in data]
print(new_data)上面可以看到,对于某些循环语句,改用推导式,将是非常简洁的。事实上,大多数循环语句都能改成使用推导式的形式来实现。不过,注意不要走极端,并不是所有循环语句改成推导式都会提升可读性。
总结一下,列表推导式的语法如下:
[func(item) for item in data]其中,func(item)是指对item做某种运算,如上面例子的平方。
下面看一个复杂一点的推导式。
a = [1, 3, 6]
b = [2, 1, 3]
# 如果我们需要计算:
# a的每个元素和b的每个元素的乘积
c = [ia*ib for ia, ib in zip(a, b)]
print(c)data = [4, 1, 2, 3, 5, 7, 8]
# 如果我们需要计算:
# data的偶数位上的数的平方
new_data = [val**2 for key, val in enumerate(data) if key % 2 == 1]
print(new_data)这是把zip或者enumerate函数和推导式结合在一起使用。
上面说的例子,对于元组和集合完全适用。如:
# 只需要把中括号改成大括号即可
new_data = {val**2 for key, val in enumerate(data) if key % 2 == 1}
print(new_data)字典也可以使用推导式的形式定义:
{key: val for key, val in zip(keys, values)}# 姓名和成绩
names = ['张三', '李四', '王五']
scores = [66, 89, 59]
# 使用字典推导式定义一个字典
# 将姓名和成绩关联起来
data = {name: score for name, score in zip(names, scores)}
print(data)# 将一个字典复制到另一个字典也可以这样写:
data1 = {'张三': 66, '李四': 89, '王五': 59}
data2 = {k: v for k, v in data1.items()}
print(data2)函数的调用方式:value = function_name(param),通过函数名去调用,前面我们已经用过不少了,如print, len等,都是函数。
常用的内置函数(前面有些已经涉及到了):
还有几个可以用于类型转换的函数:
这几个暂时没涉及到,后面可能需要用到的。
像for, in, and, or, not,is等,这些是语法的关键字。
python的常用的内置函数基本就在这里的,我们需要大概掌握它们的用法。如果一时不记得这些函数有哪些参数,我们也需要知道怎么找到说明,一种方式是直接通过搜索引擎查找,第二种当然是找人问,而在notebook中,还有一种常用的方式,如下:
# 函数名的后面,加一个问号,然后运行单元格
# 以range函数为例:
range?其输出结果:
Init signature: range(self, /, *args, **kwargs)
Docstring:
range(stop) -> range object
range(start, stop[, step]) -> range object
Return an object that produces a sequence of integers from start (inclusive)
to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.
start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.
These are exactly the valid indices for a list of 4 elements.
When step is given, it specifies the increment (or decrement).
Type: type
Subclasses:我们需要学会看这样的帮助文档。关于该函数的调用,关键的说明信息是:
range(stop) -> range object
range(start, stop[, step]) -> range object该函数可以接受的参数个数是3个:
前面两种调用形式前面已经涉及过,我们分别来看看:
data1 = range(10)
data2 = range(5, 10)
data3 = range(5, 10, 2)
# 我们将这3个结果转成列表来观察结果
print(list(data1))
print(list(data2))
print(list(data3))显然,step这个参数的作用是步长的意思。
对于字符串,元组,列表,集合和字典等,python都提供了很多内置的函数,而每个函数又有不同的参数,我们很难去记住它们。最重要的是,我们需要大概知道每种类型大概能实现什么功能,然后去哪里可以查到这些方法,及其使用的方式。这是我们学习编程最重要的技能之一。
例如,如果我们需要知道字符串有哪些内置函数则可以在cell中输入:str.之后,然后按Tab键,就会出来该类型所有的方法列表:

str包含的函数很多很丰富,我们往下拉滚动条,就能看到我们之前用过的replace,接着我们可以继续查看该函数的用法:
输入:str.replace?,然后运行单元格,结果如下:
Signature: str.replace(self, old, new, count=-1, /)
Docstring:
Return a copy with all occurrences of substring old replaced by new.
count
Maximum number of occurrences to replace.
-1 (the default value) means replace all occurrences.
If the optional argument count is given, only the first count occurrences are
replaced.
Type: method_descriptor该函数的使用方式是str.replace(self, old, new, count=-1, /),作用是讲字符串中的old字符串替换成new字符串,说明中还有特别说明count参数的作用,还有返回值的说明。这里的self是类本身,这里暂时不用管,在类的部分会讲到。
我们也可以输入"变量名.",然后按Tab键,这也会出现函数列表。
在前面我们使用如下:
s = 'This is a string string.'
# 调用的方式跟上面说到的内置函数有点点不同
new_s = s.replace('string', '字符串')
print(new_s)# 这种方式也是完全一样的
s = 'This is a string.'
s.replace?对于list, set, dict等,查看帮助文档的方式都是类似的。
我们总结一下,这几种类型的常用方法:
这些常见的函数,大家可以都查一查其帮助文档,大致了解其用法。
这只是一部分,还有其他的函数,需要用的时候可以去了解其用法。
s = 'This is a string'
# 按空格分拆字符串
ls = s.split(' ')
print(ls)
# 使用一个等号将列表拼接起来,组成一个新的字符串
'='.join(ls)函数就是一种封装,把相关的功能组合在一起。例如,前面的打印九九乘法表,我们把它封装成一个函数:
# 定义一个函数:nine_nine_multi
def nine_nine_multi():
for i in range(1, 10): # 注意缩进
for j in range(1, i+1):
print("%d * %d = %d" % (i, j, i*j))
# return None
# 函数调用
nine_nine_multi()我们已经知道sum函数可以对列表进行求和,现在我们重新造一个轮子:
# 定义列表的求和函数
# list_sum是函数名
# ls是函数的参数
# 注意:理解缩进的层级
def list_sum(ls):
ls_sum = 0
for val in ls:
ls_sum += val
# 返回语句
return ls_sum # 返回计算结果
a = [1,2,3,4,5]
a_sum = list_sum(a)
print(a_sum)我们重点来理解一下这个函数执行机制,在执行到函数调用的语句时,执行状态如下图:

我们定义的函数名有点类似变量名,它指向了一个函数。
下一步就是执行函数调用:

这里我们需要注意: 外部定义的列表a和函数参数ls其实指向的是同一个列表。如果在函数里面,改变了ls参数的值,会导致外部的列表变量a的值也跟着改变。因为这里的参数是复杂类型,对于其他的类型,如集合,字典,类等都会产生这样的效果。但是对于简单类型则不会产生这样的副作用。
我们继续往前执行,就跟前面的循环类似了:

而执行到return语句时:

这个Return Value在返回之后,会赋值给a_sum变量。
注意:函数是不一定是需要又return语句的,默认会返回一个None。
作为一个练习,大家可以实现一个阶乘的函数,并观察其执行过程。
在python里,函数参数有几种类型:必选参数,默认参数,不定参数。
必选参数就是前面自定义函数时用到的,调用时,必须对应传递,否则就会报错。
我们知道,在调用函数时如果不指定某个参数,Python 解释器会抛出异常。为了解决这个问题,Python 允许为参数设置默认值,即在定义函数时,直接给形式参数指定一个默认值。这样的话,即便调用函数时没有给拥有默认值的形参传递参数,该参数可以直接使用定义函数时设置的默认值。
# a是必选参数
# b就是带默认值的参数
def test(a, b=2):
print(a, b)
# 不传b参数,则b参数使用默认值
test(1)
# 给b参数参数,通常这样:b=4
test(1, b=4)带默认值的参数需要放在必选参数的后面,否则会报语法错误,如下:

我们查看一些函数的帮助文档,例如range,参数里会有*args, **kwargs这样的参数,通常我们自己实现函数的时候,尽量应该避免这种情况,不过,我们还是得理解这种语法,以便我们更好的理解某些函数的用法。
需要说明的是,args和kwargs都可以理解为参数名,使用其他的名字也是一样的,只是args和kwargs是习惯的命名。
对这两个参数大概可以这样理解:
range?
不定参数和必选参数或者是默认参数也是可以一起使用的,例如:

在上面的图,我们定义了一个求和函数,其实有两个变量的作用域:

def test(a):
local_a = 20 # 定义一个函数内部的变量
print(a)
print(global_a) # 这里会打印10
# 定义一个全局变量
global_a = 10
param = 15
test(param)
# 在函数外部使用函数内部的变量会报错
print(local_a)如果我们定义了多个函数,则每个函数都会有自己的内部作用域。
但是需要主要,如果我们在函数内部改变全局变量,会发生什么呢?请看:
def test(a):
# 在函数内部改变重新定义之后,global_a这个变量已经变成了该函数的内部变量
# 在这里改变重新赋值并不会影响全局的global_a
global_a = 5
print('In test: ', global_a) # output: 5
# 定义一个全局变量
global_a = 10
print('Global: ', global_a) # output: 10
test(20)
# 外部的变量并不会改变
print('Global: ', global_a) # output: 10在函数内部赋值之后,其实这两个变量已经完全是两个独立的变量了,虽然它们的名字是一样的。就像在这里有一个叫“张三”的人,而在另一个地方也有一个叫“张三”的人,它们两个同名,但却完全是独立的两个人。

但是如果我们在函数内部尝试操作全局的变量,例如四则运算,则会报错:

如果我们需要作为一个全局变量来使用,则需要使用global语句:
def test(a):
global global_a # 声明该变量是全局变量
global_a += 5
print('In test: ', global_a)
# 定义一个全局变量
global_a = 10
print('Global: ', global_a)
test(20)
print('Global: ', global_a)这会输出:
Global: 10
In test: 15
Global: 15这时,全局的变量就会跟着改变了。
注意:尽量不要使用全局的变量,尽量避免在函数内部的操作对外部造成影响,这会让程序的可读性变得很差。如果需要,通常可以作为函数的参数传入。
现在我们来看一个常用的函数:sorted,这个函数可以用于元组和列表等的排序。先查看它的用法:
Signature: sorted(iterable, /, *, key=None, reverse=False)
Docstring:
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
Type: builtin_function_or_method先看一下其简单用法:
a = [1, 3, 2]
print(sorted(a)) # output: 1,2,3
print(sorted(a, reverse=False)) # output: 1,2,3
# 逆序排序
print(sorted(a, reverse=True)) # output: 3,2,1[1, 2, 3]
[1, 2, 3]
[3, 2, 1]对于一些复杂的列表或者元组,我们可能就需要用到sorted函数的key参数,该参数可以指定排序的值:
# 定义一个列表: 一个班级学生的考试成绩如下
scores = [
['张三', 80],
['李四', 70],
['王五', 90]
]
# 定义一个key参数的值
def score_sort(x):
return x[1] # 指定按照下标1的值,即成绩进行排序
# 如果我们按成绩进行排名
# 这告诉我们,函数也是一个对象,可以作为其他函数的参数,也可以赋值给一个变量
res = sorted(scores, key=score_sort, reverse=True)
print(res)[['王五', 90], ['张三', 80], ['李四', 70]]现在这样是可以实现功能的,但是这样很哆嗦,有没有更简洁优雅的方式呢?
显然是有的,就是匿名函数:
res = sorted(scores, key=lambda x: x[1], reverse=True)
print(res)lambda x: x[1]: 这就是一个匿名函数:
这个匿名函数的效果和前面的score_sort函数的效果是一样的,可以对比一下。

红色椭圆圈住的地方就是匿名函数,它有自己的作用域,有自己的参数,有自己的返回值。
从图示我们可以看到,在实际运行的时候,其实是有点类似循环的,每次取列表的一个元素传入匿名函数的参数x,返回值就是x[1]。
对于做数据分析来说,类并不是那么重要,不过我们得掌握其基础的用法,因为很多场景下,我们会接触到类的对象。
在python中,一切皆对象,我们前面接触到的变量,函数等,都是对象,而所有对象都是某个类的实例化。对于数据分析师来说,我们对“类”秉持工具主义就好了,知道怎么去调用类和类的方法即可。
类方法的调用是需要使用点号进行调用的,在前面已经涉及到,例如:
# 定义一个字符串变量
# s就是一个字符串对象
s = 'string!'
# 调用s对象的replace方法
s.replace('!', '.')跟类相关的主要概念有几个,我们以房子来类比,以辅助理解:
我们不准备深入去讲解这些概念,但是我们需要有所了解。
前面讲到的东西只是python最核心的部分,但是python还有内容众多的包,几乎无所不包,这些包又分为python内置的包,还有浩如烟海的第三方包。
现在我们讲一个数学包:
# 在使用包的函数,需要先引入该包
import math # 这时包引用的语句
# PI的值
# 包的变量:pi是在math这个包中定义好的常量
print(math.pi)
# 求平方根
# sqrt是math包中定义的函数
print(math.sqrt(2))
# 查看包函数的帮助文档
# 如果帮助信息不清晰,则可以使用搜索引擎进行搜索
math.sqrt?包的引用语法还有以下的形式:
# 将numpy引入进来,并命名为np,这时在代码中就可以使用np进行调用
# numpy是一个数据分析常用的第三方包
import numpy as np
# 从math包中单独引入sqrt这个函数
from math import sqrt包引用语句通常放在代码行的最前面。
math这个包的内容很丰富,几乎囊括了中学数学里用到的很多函数,不可能也没必要一一讲解。
前面已经讲过列表字典等变量的复制问题,但是那都不是太彻底的。我们可以看一下下面这个例子:

如果我们观察b变量,发现其值也已经被修改了,因为这个copy只是浅层的copy,如果元素中包含有组合类型的话,就会出现这样的问题。
如果我们需要彻底的复制的话,这时需要用到一个“copy”的包:
from copy import deepcopy
a = [1, 2, ["a", "b", "c"]]
# deep copy a to b
b = deepcopy(a)
# change
a[2][1] = 'abc'
print(b)[1, 2, ['a', 'b', 'c']]显然,使用deepcopy之后,改变a的值并不会导致b的值也关联被改变,这时a和b已经是完全两个独立的变量了。
至此,python数据分析入门的语法部分算是学完了。
F[n]=F[n-1]+F[n-2](n>=3,F[1]=1,F[2]=1)未完待续。。。
ps: 所有代码都在notebook运行。