Tip:
1、Python "自带文档",甚至不需用户查询文档
# dir() : 列出指定类或模块包含的全部内容(包括函数、方法、类、变量等),其中以“__”开头、“__”结尾的方法被约定成私有方法,不希望被外部直接调用。 # # 如果希望查看某个方法的用法,则可使用help()函数 # help() : 查看某个函数或方法的帮助文档 # 例如查看str类包含的全部内容,可以在交互式解释器中输入如下命令: >>> dir(str) ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] >>> >>> help(str.title) Help on method_descriptor: title(self, /) Return a version of the string where each word is titlecased. More specifically, words start with uppercased characters and all remaining cased characters have lower case. >>> # 从上面介绍可以看出,str类的ti tle()方法的作用是将每个单词的首字母大写,其他字母保持不变
1、平方运算符 **
2、异或运算符 ^
3、术语列表:
problem solving: 问题解决
high-level language: 高级语言
l ow-level language: 低级语言
portability: 可移植性
interpreter: 解释器
prompt: 提示符
operator: 运算符
integer: 浮点数
natural language: 自然语言
formal language: 公式语言
parse: 解释
variable:变量
assignment:赋值,给一个变量赋予值。
state diagram:状态图,图形化表征每个变量的值。
operand:运算数,运算符来进行运算操作的数值。
expression:表达式,一组变量、运算数的组合,会产生单值作为结果。
evaluate:求解,把表达式所表示的运算计算出来,得到一个单独的值。
statement:声明,一组表示一种命令或者动作的代码,目前我们了解的只有赋值语句和打印语句。
interactive mode:交互模式
execute:运行
order of operations:运算符优先级
concatenate:拼接,把两个运算对象相互连接到一起。
exception:异常,程序运行的时候被探测到的错误。
semantics:语义,程序的意义。
semantic error:语义错误,程序运行的结果和料想的不一样,没有完成设计的功能,而是干了点其他的事情。
4、变量名称不能以数字开头
5、一个声明语句的左边必须是变量名,任何其他的表达式放到等号左边都会导致语法错误(也有例外)
6、函数
def 函数名():
函数体
第一行叫做函数头,函数名后面的()为空,表示这个函数不需要参数,函数体必须是相对于函数头部有缩进的,距离行首要有四个空格的距离。
在函数定义完毕的结尾,必须输入一行空白行,定义函数会创建一个函数类的对象。
函数之间可以嵌套使用,在一个函数的函数体中,可以使用另一个函数。
7、函数形式参数和实际参数
形参就是形式上的参数,可以理解为数学的X,没有实际的值,通过别人赋值后才有意义。相当于变量。
实参就是实际意义上的参数,是一个实际存在的参数,可以是字符串或是数字等。
# 函数头括号内的就是形式参数,要传递给它的值就是实际参数
def print_name(name,age):
print("My name is" + name + ",I am " + age + "years old!")
# ① 默认参数
# 此种情况下,函数头部已经确定了要使用参数的值,调用函数时不需要再传值
# ② 位置实参 VAR_POSITIONAL
# 调用函数时,必须将函数调用中的每个实参都关联到函数定义中的一个形参。关联方式是基于实参的顺序,这被称为位置参数
print_name("zjib","22")
# ③ 关键字参数 VAR_KEYWORD
# 关键字实参是以等号的形式,直接将形参与实参关联起来,这样就不存在顺序的问题了。
print_name(name="zjib",age="22")
"""
④ 上述几种方式是有几个形参,就传进去几个实参。
但有时候会不确定有多少个参数,以一个 *加上形参名 的方式来表示这个函数的参数个数不定,可能为0个,可能为n个。
注意,不管有多少个,在函数内部都被存放在以形参名为标识符的元组中,如下
"""
def print_args(*args):
print(args)
print_args(1,2,3) √
(1, 2, 3)
print_args(args=1) ×
'''
⑤ 两个 * 加形参名 ,表示在函数内部将被存放在以形参名为标识符的字典dictionary中,
这时调用函数的方法需要采用 arg1=value1,arg2=value2 这样的形式
'''
def print_args(**args):
print(args)
print_args(x=1,y=2,z=3)
{'x': 1, 'y': 2, 'z': 3}
9、turtle模块
import turtle
t = turle.Turtle()
t.fd()
…………
fd 向前
bk 向后
rt 右转,角度为参数
lt 左转,角度为参数
10、for语句
头部后一定要用冒号,一个缩进的循环体
11、封装
用函数的形式把一段代码包装起来,叫做封装。这样有一个好处,就是给代码起了个名字,有类似于文档说明的功能,更好理解了。另外一个好处是下次重复使用这段代码的时候,再次调用函数就可以了,这比复制粘贴函数体方便多了。
import turtle bob = turtle.Turtle() def square(t): for i in range(4): t.fd(100) t.rt(90) square(bob)
12、泛化
import turtle bob = turtle.Turtle() square_length = 100 n = 6 def polygon(t,length,n): for i in range(n): t.fd(length) t.rt(360/n) polygon(bob,square_length,n)
给函数添加参数,就叫做泛化,这样可以让函数的功能更广泛。
13、接口设计
函数的接口就是关于它如何工作的一个概述:都有什么变量?函数实现什么功能?以及返回值是什么?允许调用者随意操作而不用处理一些无关紧要的细节,这种函数接口就是简洁的。
import turtle import math bob = turtle.Turtle() def polygon(t,length,n): for i in range(n): t.fd(length) t.rt(360/n) def circle(t,r,n): circle_long = 2 * math.pi * r per_length = circle_long / n polygon(t,per_length,n) # 令多边形周长尽量约等于圆周长,2Πr = n * length # 2 * 3 * 833 ≈ 100 * 50 circle(bob,833,50)
14、重构
15、开发计划
开发计划是写程序的一系列过程。如上所用的就是『封装-泛化』的模式。这一过程的步骤如下
开始写一个特别小的程序,没有函数定义。
一旦有你的程序能用了,确定一下实现功能的这部分有练习的语句,封装成函数,并命名一下。
通过逐步给这个函数增加参数的方式来泛化。
重复1-3步骤,一直到你有了一系列能工作的函数为止。把函数复制粘贴出来,避免重复输入或者修改了。
看看是不是有通过重构来改进函数的可能。比如,假设你在一些地方看到了相似的代码,就可以把这部分代码做成一个函数。
这个模式有一些缺点,我们后续会看到一些替代的方式,但这个模式是很有用的,尤其对实现不值得怎么去把程序分成多个函数的情况。
16、函数接口调试
一个交互接口,就像是函数和调用者的一个中间人。调用者提供特定的参数,函数完成特定的任务。
例如上面的polygon函数, t 应该是一个Turtle小乌龟、length应该是一个正数、n必须是一个整型;这些要求叫做 [ 前置条件 ],因为要在函数开始之前就要实现才行。
相应的在函数的结尾那里的条件叫 [ 后置条件 ] ,后置条件包含函数的预期效果(如画线段)和其他作用(如移动海龟)
。
前置条件是准备给函数调用者的。如果调用者违背了(妥当标注的)前置条件,然后函数不能正常工作,这个bug就会反馈在函数调用者上,而不是函数本身
如果前置条件得到了满足,而后置条件未能满足,这个bug就是函数的了。所以如果你的前后置条件都弄清晰,对调试很有帮助
17、floor除法
运算符使两个右斜杠 “ // ”,与传统除法不同,floor除法会把运算结果的小数位给舍弃,返回整值·。
例如,一部电影的时间长度是105分钟,如果转换为小时制,传统的除法运算如下:
>>> minutes = 105 >>> hours = 105 / 60 1.75
如果不写有小数的时候,floor除法返回的就是整的小时数,舍弃掉小数位:
>>> minutes = 105 >>> minutes // 60 1
若想要知道所舍弃的那一部分的大小,可以用分钟数减去这一个小时,然后剩下的分钟数就是:
>>> remainder = minutes // 60 >>> remainder 45
求模运算同样可以得到余数,运算符为 “ % ”,
>>> remainder = minutes % 60 >>> remainder 45
求模运算还可以用来判断按一个数能否被另一个数整除---比如 x % y 如果等于 0 了,那就是意味着能被y整除。另外,x % 10 可以得到该数的个位数字,x % 100就可以得到该数个位和百位的数字
18、布尔表达式
布尔表达式是一种非对即错的表达式,只有两个值,true(真)、false(假)。
>>> type(True) <class 'bool'> >>> type(False) <class 'bool'>
19、关系运算符
x==y # x is equal to y 二者相等
x != y # x is not equal to y 二者不相等
x > y # x is greater than y 前者更大
x < y # x is less than y 前者更小
x >= y # x is greater than or equal to y 大于等于
x >= y # x is greater than or equal to y 大于等于
x <= y # x is less than or equal to y 小于等于
20、逻辑运算符
逻辑运算符有三种:且、或以及非
21、if判断
条件执行:
if后面的布尔表达式就叫做条件。如果条件为真,随后缩进的语句就运行。如果条件为假,就不运行
if x > 0: print("x is positive")
if语句与函数定义的结构基本一样:一个头部,后面跟着缩进的语句。这样的语句叫做复合语句。
复合语句中语句体内的语句数量是不限制的,但至少要有一个。有的时候会遇到一个语句体内不放语句的情况,比如空出来用来后续补充。这种情况下,你就可以用pass语句,就是啥也不会做的。
if x < 0: pass
选择执行:
if语句的第二种形式就是『选择执行』,这种情况下会存在两种备选的语句,根据条件来判断执行哪一个
if x % 2 == 0: print("x is even") else: print("x is odd")
这里条件非真即假,只有两个选择。这些选择也叫『分支』,因为在运行流程上产生了不同的分支。
链式条件:
有时候遇到的情况可能性不止两种,需要更多的分支。之时候可以用连锁条件来实现:
if x > y: print("x is less than y") elif x > y: print("x is greater than y") else: print("x and y are equal")
elif是『else if』的缩写。这回也还是只会有一个分支的语句会被运行。elif语句的数量是无限制的。如果有else语句的话,这个else语句必须放到整个条件链的末尾,不过else语句并不是必须有的。
if choice == 'a': draw_a() elif choice == 'b': draw_b() elif choice == 'c': draw_c()
每一个条件都会依次被检查。如果第一个是假,下一个就会被检查,依此类推。如果有一个为真了,相应的分支语句就运行了,这些条件判断的语句就都结束了。如果有多个条件为真,只有先出现的为真的条件所对应的分支语句会运行
嵌套条件:
一个条件判断也可以嵌套在另一个条件判断内。
if x == y: print("x and y are equal") else: if x < y: print("x is less than y") else: print("x is greater than y")
逻辑运算符有时候对简化嵌套条件判断很有用,
if x > 0: if x < 10: print("x 是个小于10的正数") # 我们可以用逻辑运算符来实现同样的效果 if x > 0 and x < 10: print("x 是个小于10的正数") # 或者,python提供的更简洁的方法 if 0 < x < 10: print("x 是个小于10的正数")
22、递归运算
一个函数可以去调用另一个函数;函数也可以来调用自己,这就是递归。
def countdown(n): if n < = 0: print("Blastoff") else: print(n) countdown(n-1)
如果n为0或者负数,程序会输出 “ Blastoff ”。其他情况下,程序会调用自身来运行,以自身参数n减去1为参数。
>>> countdown(3) 3 2 1 Blastoff!
调用自身的函数就是递归的;执行这种函数的过程就叫递归运算。
def print_n(s,n): if n <= 0: return print(s) print_n(s,n-1) s = "Python is super cool" n = 4 print_n(s,n)
如果n小于等于0了,返回语句return就会终止函数的运行。运行流程立即返回到函数调用者,函数其余各行的代码也都不会执行。
函数其余部分的代码很容易理解:print一下s,然后调用自身,用n-1做参数来继续运行,这样就额外对s进行了n-1次的显示。所以输出的行数是1+(n-1),最终一共有n行输出。
上面这种简单的例子,实际上用for循环更简单。不过有些情况下用for循环不太好写,这些情况往往用递归更简单
23、无穷递归
如果一个递归一直都不能到达基准条件,那就会持续不断地进行自我调用,程序也就永远不会终止了。这就叫无穷递归。
def recurse(): recurse()
在大多数的开发环境下,无穷递归的程序并不会真的永远运行下去。Python会在函数达到允许递归的最大层次后返回一个错误信息:
File "<stdin>", line 2, in recurse RuntimeError: Maximum recursion depth exceeded
这种错误出现的时候,栈中都已经有1000层递归框架了!
24、键盘输入
Python提供了内置的一个函数,名叫input,这个函数会停止程序运行,等待用户来输入一些内容。用户按下 ESC键 或 Enter回车键。程序就恢复运行,input函数就把用户输入的内容作为字符串返回。
>>> text = input() What are you 弄啥嘞? >>> text 'What are you 弄啥嘞?'
在用户输入内容之前,最好显示一些提示来告诉用户需要输入什么内容。input函数能够把提示内容作为参数:
>>> name = input("What is your name?\n") What is your name? ishells >>> name 'ishells'
用户从键盘输入的类型默认是“ str ”,如果想得到其他类型的变量,需要手动转换一下“:
>>> text = "你一般跑步时长是多少分钟?" >>> time = input(text) "你一般跑步时长是多少分钟?" 42 >>> time 42 >>> type(time) <class 'str'> >>> int(time) 42
这个时候,如果调皮的用户输入的不是一串数字,而是其他内容,使用int()进行类型转换就会得到一个错误:
>>> time = input(text) "你一般跑步时长是多少分钟?" 42minutes >>> int(minutes) ValueError: invalid literal for int() with base 10
25、调试
当语法错误或者运行错误出现的时候,错误信息会包含很多有用的信息,不过信息量太大,太繁杂。最有用的也就下面这两类:
错误的类型是什么
错误的位置在哪里
>>> x = 5 >>> y = 6 File "<stdin>", line 1 y = 6 ^ IndentationError: unexpected inden
这个例子里面,错误的地方是第二行开头用一个空格来缩进了。但这个错误是指向y的,这就有点误导了。一般情况下,错误信息都会表示出发现问题的位置,但具体的错误可能是在此位置之前的代码引起的,有的时候甚至是前一行
import math signal_power = 9 noiser_power = 10 ratio = signal_power // noise_power decibels = 10 * math.log10(ratio) print(decibels) # 运行上面的程序就会得到如下错误信息: Traceback (most recent call last): File "snr.py", line 5, in ? decibels = 10 * math.log10(ratio) ValueError: math domain err
这个错误信息提示第五行,但那一行实际上并没有错。要找到真正的错误,就要输出一下ratio的值来看一下,结果发现是0了。那问题实际是在第四行,应该用浮点除法,结果多打了一个右斜杠,弄成了地板除法,才导致的错误。
所以得花点时间仔细阅读错误信息,但不要轻易就认为出错信息说的内容都是完全正确可靠的
26、有返回值的函数
函数使用return返回的值是局部变量,函数外部是不能直接通过该局部变量获取到函数返回的结果的,是需要定义一个值来获取函数返回的结果
import math def area(radius): a = math.pi * radius ** 2 return a # 在有返回值的函数中,返回语句可以包含表达式,返回语句的意思是:立即返回下面的表达式作为返回值。返回语句里面的表达式可以随便多复杂都行。 def area(radius): return math.pi * radius**2 area(2) # 上面的例子中,a是一个局部变量,并不能在函数外部使用,如果要获取到函数内部的a的值,就要在函数外部定义一个变量获取该值。
另外,有一些临时变量可以让后续的调试过程更简单。所以有时候可以多设置几条返回语句,每一条都对应一种情况
def absolute_value(x): if x < 0: return -x else: return x # 因为这些返回语句对应的是不同条件,因此实际上最终只会有一个返回动作执行
返回语句运行的时候,函数就结束了,也不会运行任何其他的语句了。返回语句后面的代码,执行流程里所有其他的位置都无法再触碰了,这种代码叫做『死亡代码』
def absolute_value(x): if x < 0: return -x if x > 0: return x # 这个函数就是错误的,因为一旦x等于0了,两个条件都没满足,没有触发返回语句,函数就结束了。执行流程走完这个函数之后,返回的就是空(None),而本应该返回0的绝对值的
顺便说一下,Python内置函数就有一个叫abs的,就是用来求绝对值的,abs(-10)
27、增量式开发
写一些复杂函数的时候,会发现要花很多时间调试。
要应对越来越复杂的程序,其实不妨来试试增量式开发的办法。**增量式开发的目的是避免长时间的调试过程,一点点对已有的小规模代码进行增补和测试。**比如写一个函数的时候,先确保语法正确,在尝试一步一步的完善函数体,逐步完成要实现的功能
28、更多递归
def factorial(n): if n == 0: return 1 else: recurse = factorial(n-1) result = n * recurse return result # 以3为参数来调用一下这个阶乘函数: ''' 3不是0,所以分支一下,计算n-1的阶乘。。。 2不是0,所以分支一下,计算n-1的阶乘。。。 1不是0,所以分支一下,计算n-1的阶乘。。。 第一遍: recurse = factorial(3-1) result = 3 * factorial(3-1) 第二遍: recurse = factorial(2-1) result = 2 * factorial(2-1) 第三遍: recurse = factorial(1-1) result = 1 * factorial(1-1) 第四遍: 返回1 然后return返回: result = 3 * 2 * 1 * 1 '''
跟随着运行流程是阅读程序的一种方法,但很快就容易弄糊涂。**当你遇到一个函数调用的时候,你不用去追踪具体的执行流程,而是假设这个函数工作正常并且返回正确的结果。**其实当使用内置函数的时候,就已经使用过这种方法了。比如当你调用math.cos的时候,你并没有仔细查看这些函数的函数体,而是直接假设它们都工作,直接使用了。
对于递归函数而言也是同样道理。当你进行递归调用的时候,并不用追踪执行流程,你只需要假设递归调用正常工作,返回正确结果
Tip:
isinstance(obj,class_or_tuple,/) # 内置函数,可以判断对象的类型 # 例: >>> isinstance(1.5,int) False >>> isinstance(1.5,float) True
29、列表、元组
① 列表元组都是一个可以放置任意数据类型的
有序
集合。在绝大多数编程语言中,集合的数据类型必须一致。不过,对于Python的列表和元组来说,并无此要求。
list = [1, 2, 'hello', 'world'] # 列表中同时含有int和string类型的元素 tuple = ('ishells', 22) # 元组中同时含有int和string类型的元素 # 列表是动态的,长度大小不固定,可以随意地增加、删减或修改元素 # 元组是静态的,无法增加、删减、或改变
>>> list = [1, 2, 'hello', 'world'] >>> list[0] = 40 >>> list [40, 2, 'hello', 'world'] >>> tuple = ('ishells', 22) tuple[1] = 40 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignme
上例中,对于列表可以很轻松的通过索引改变其值,但是如果对元组采取同样的操作,就会报错,原因就是元组是不可变的。
② 列表和元组都支持切片操作:
切片操作:
https://www.cnblogs.com/malinqing/p/11272485.html
# 切片左包右不包 >>> l = [1, 2, 3, 4] >>> l[1:3] # 返回列表中索引从1到2的子列表,从索引是1的开始取到索引是2的列表元素,即不包含索引为3的元素 [2,3] >>> tup = (1,2,3,4) >>> tup[1:3] # 返回元组中索引从1到2的子元组 (2,3)
③ 列表、元组随意嵌套、互相转换
# 随意嵌套 l = [[1, 2, 3],[4,5]] # 列表的每一个元素也是一个列表 tup = ((1, 2, 3), (4, 5, 6)) # 元组的每一个元素也是一元组
# 相互转换 >>> list((1, 2, 3)) [1, 2, 3] >>> tuple([1, 2, 3]) (1, 2, 3)
④ 常用函数
>>> l = [3, 2, 3, 7, 8, 1] >>> l.count(3) # 统计列表中元素 3 出现的次数 2 >>> l.index(7) 3 >>> print(l.reverse()) # 反向列表中元素 [1, 8, 7, 3, 2, 3] >>> print(l.sort()) [1, 2, 3, 3, 7, 8] # 反向、排序等函数都没有改变列表、元组本身,只是输出的时候形式变了 tup = (3, 2, 3, 7, 8, 1) >>> tup.count(3) 2 >>> tup.index(7) 3 >>> list(reversed(tup)) # 反向元组中的元素 [1, 8, 7, 3, 2, 3] >>> sorted(tup) [1, 2, 3, 3, 7, 8]
⑤ 列表是可变的,元组是不可变的,这样的差异性势必会影响两者存储方式的差异。
>>> l = [1, 2, 3] >>> l.__sizeof__() 64 >>> tup = (1, 2, 3) >>> tup.__sizeof__() 48 # 可以看到,对于列表和元组,我们放置了相同的元素,但是元组的存储空间,却比列表要少16 字节。这是为什么呢? 事实上,由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述例子中,对于int 型,8 字节)。另外,由于列表可变,所以需要额外存储已经分配的长度大小(8 字节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间
如果想对已有的元组做任何改变,就只能新开辟一块内存,创建新的元组,比如下例,想增加一个元素5给元组,实际上就是创建了一个新的元组,然后把两个元组的值依次填充进去
>>> tuple = ('ishells', 22) # 创建新元组,并以此填充原元组的值 >>> new_turple = tuple + (5, ) >>> >>> # 错误示例: >>> new_turple = tuple + (5)
列表只需要简单在列表末尾加上对应元素即可,也不用创建新的列表
>>> list = [1, 2, 'hello', 'world'] >>> list.append(5) >>> list [1, 2, 'hello', 'world', 5]
和其他语言不同,Python中的列表和元组都支持负数索引,-1表示最后一个元素,-2表示倒数第二个元素
>>> l = [1, 2, 3, 4] >>> l[-1] 4 >>> tup = (1, 2, 3, 4) >>> tup[-1] 4
3
⑥ 列表和元组的性能
元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表。
另外,Python 会在后台,对静态数据做一些资源缓存(resource caching)。通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,Python 就会回收它们所占用的内存,返还给操作系统,以便其他变量或其他应用使用。
但是 对于一些静态变量,比如元组,如果它不被使用并且占用空间不大时,Python 会暂时缓存这部分内存。这样,下次我们再创建同样大小的元组时,Python 就可以不用再向操作系统发出请求,去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。
这个例子是来计算初始化一个相同元素的元组和列表分别需要的时间,可以看到:元组的初始化速度,要比列表快 5 倍
(python37) root@ishells ~# python -m timeit 'x=(1,2,3,4,5,6)' 20000000 loops, best of 5: 12.5 nsec per loop (python37) root@ishells ~# (python37) root@ishells ~# python -m timeit 'x=[1,2,3,4,5,6]' 5000000 loops, best of 5: 60.4 nsec per loop
但如果是索引操作,两者的速度差别非常小
(python37) root@ishells ~# python -m timeit -s 'x=[1,2,3,4,5,6]' 'y=x[3]' 10000000 loops, best of 5: 28.8 nsec per loop (python37) root@ishells ~# (python37) root@ishells ~# python -m timeit -s 'x=(1,2,3,4,5,6)' 'y=x[3]' 10000000 loops, best of 5: 30.1 nsec per loop
如果你想要增加、删减或者改变元素,那么列表显然更适合
⑦ 列表和元组的使用场景
a、如果存储的数据和数量不变,那么肯定选用元组更合适
b、如果存储的数据或数量是可变的,比如社交平台上的一个日志功能,是统计一个用户在一周之内看了哪些用户的帖子,那么则用列表更合适
viewer_owner_id_list = [] # 里面的每个元素记录了这个 viewer 一周内看过的所有 owner 的 id records = queryDB(viewer_id) # 索引数据库,拿到某个 viewer 一周内的日志 for record in records: viewer_owner_id_list.append(record.id)
30、列表生成式
顾名思义,列表生成式就是用来生成复杂的列表,代替复杂的操作来完成列表的生成
### 列表生成式 格式必须:<font color=red>[ 变量 for 变量 in 范围 ]</font> # 例如,要生成列表 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 简单列表,可以直接使用 >>> list(range(1,11)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 但是要生成 [1x1, 2x2, 3x3, ..., 10x10]的话,只能用循环 >>> list = [] >>> for x in range(1,11): ... list.append(x * x) ... >>> list [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] # 1、使用循环生成复杂的列表,稍显麻烦一点,这时候就可以直接使用列表生成式来完成任务 >>> [ x * x for x in range(1,11) ] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
2、for循环后面还可以加上if判断,这样我们就可以筛选出偶数的平方:
>>> [x * x for x in range(1, 11) if x % 2 == 0] [4, 16, 36, 64, 100]
3、列表生成式结合if-else
列表生成式中 for 后的 if 不能加 else ,否则会报错
因为列表生成式 for 后面的 if 是一个筛选条件,带上 else 就不能称为一个筛选条件>>> [x for x in range(1, 11) if x % 2 == 0 else 0] File "<stdin>", line 1 [x for x in range(1, 11) if x % 2 == 0 else 0] ^ SyntaxError: invalid syntax
如果 if 写在列表生成式 for 的前面,一定要带上 else,否则报错!
因为 for 前面的部分是一个表达式,他必须根据 x 计算出出一个结果,而表达式 x if x % 2 == 0,如果 x 不能整除 2 ,那么它无法根据x计算出结果,所以必须加上 else让它知道不能被 2 整除时应该怎么做
>>> [x if x % 2 == 0 for x in range(1, 11)] File "<stdin>", line 1 [x if x % 2 == 0 for x in range(1, 11)] ^ SyntaxError: invalid syntax >>> [x if x % 2 == 0 else -x for x in range(1, 11)] [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value: >>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> for k, v in d.items(): ... print(k, '=', v) ... y = B x = A z = C 因此,列表生成式也可以使用两个变量来生成list: >>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> [k + '=' + v for k, v in d.items()] ['y=B', 'x=A', 'z=C'] 最后把一个list中所有的字符串变成小写: >>> L = ['Hello', 'World', 'IBM', 'Apple'] >>> [s.lower() for s in L] ['hello', 'world', 'ibm', 'apple']
# os模块结合列表生成式,输出当前目录名及文件名 import os list = [d for d in os.listdir('.')] print(list) 输出: ['.idea', 'demoCsv.py', 'demoOpenpyxl.py', 'frisobackupreport.csv']
31、字典和集合基础
在 Python3.7+,字典被确定为有序( 注意:在 3.6 中,字典有序是一个 implementation detail,在 3.7 才正式成为语言特性,因此 3.6 中无法 100% 确保其有序性 ),而 3.6 之前是无序的,其长度大小可变,元素可以任意地删减和改变。
集合一直是无序的
相比于列表和元组,字典的性能更优,特别是对于查找、添加和删除操作,字典都能在常数时间复杂度内完成
① 字典、集合的创建
而集合和字典基本相同,唯一的区别,就是
集合
没有键和值的配对,是一系列有序的
、唯一的元素组合# 字典和集合的创建通常有下面这几种方式: >>> d1 = {'name ':'ishells','age':'99','gender':'female'} >>> d2 = dict({'name ':'ishells','age':'99','gender':'female'}) >>> d3 = dict([('name', 'ishells'), ('age', 20), ('gender', 'male')]) >>> d4 = dict(name='ishells', age=20, gender='male') >>> d1 == d2 == d3 == d4 True # 集合 >>> s1 = {1, 2, 3} >>> s2 = set([1,2,3]) >>> s1 == s2 True
注意,Python 中字典和集合,无论是键还是值,都可以是混合类型。即,可以是字符串,可以是整形、浮点型。
① 字典访问
# 字典访问可以直接索引键,如果不存在,就会抛出异常 >>> d = {'name':'ishells','age':20} >>> d['name'] 'ishells' >>> d['location'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'location' # 也可以使用get(key,default)函数进行索引,如果键不在,调用get()函数可以返回一个默认值 >>> d = {'name':'ishells','age':20} >>> d.get('name') 'ishells' >>> d.get('location') >>> >>> d.get('location','none') 'none' >>> >>> d.get('location','null') 'null'
② 集合访问
集合并不支持索引操作,因为集合本质上是一个哈希表,和列表不一样。所以,下例的操作会报错:
>>> s = {1, 3, 3} >>> s[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'set' object is not subscriptable >>> # 想要判断一个元素在不在字典或集合内,我们可以用 value in dict/set 来判断 >>> s = {1, 3, 3} >>> 1 in s True >>> 2 in s False >>> >>> d = {'name':'ishells','age':20} >>> >>> 'name' in d # 判断时,字符串必须带上 '' 符号 True >>> 'ishells' in d # 字典使用 value in dict 方法判断时,只能判断 键 是否存在,不能判断值 False >>>
1
③ 字典、集合的增加、删除、更新
# 集合 >>> d = {'name': 'jason', 'age': 20} >>> d['gender'] = 'male' # 增加元素对'gender': 'male' >>> d['dob'] = '1999-02-01' # 增加元素对'dob': '1999-02-01' >>> d {'name': 'jason', 'age': 20, 'gender': 'male', 'dob': '1999-02-01'} >>> d['dob'] = '1998-01-01' # 更新键'dob'对应的值 >>> d.pop('dob') # 删除键为'dob'的元素对'1998-01-01' >>>d{'name': 'jason', 'age': 20, 'gender': 'male'} # 集合 s = {1, 2, 3} s.add(4) # 增加元素 4 到集合s{1, 2, 3, 4} s.remove(4) # 从集合中删除元素 4
集合的pop()操作是删除集合中的最后一个元素,可是集合本身是无序的,你无法知道会删除哪个元素,因此这个操作得谨慎使用
④ 字典的排序
>>> d = {'b': 1, 'a': 2, 'c': 10} >>> d_sorted_by_key = sorted(d.items(), key=lambda x:x[0]) # 根据字典键的升序排序 >>> d_sorted_by_value = sorted(d.items(), key=lambda x:x[1]) # 根据字典值的升序排序 >>> d_sorted_by_key [('a', 2), ('b', 1), ('c', 10)] >>> d_sorted_by_value [('b', 1), ('a', 2), ('c', 10)] # 返回了一个列表,列表中的每个元素,是由原字典的键和值组成的元组
⑤ 集合的排序
集合的排序和列表、元组的排序很类似,直接调用sorted( set )即可,结果会返回一个排好序的列表
>>> s = {1, 4, 6, 3} >>> print(sorted(s)) [1, 3, 4, 6]
⑥ 字典和集合的性能
字典与列表对比:
假设列表有 n 个元素,而查找的过程要遍历列表,那么时间复杂度就为 O(n)。即使我们先对列表进行排序,然后使用二分查找,也会需要 O(logn) 的时间复杂度,更何况,列表的排序还需要 O(nlogn) 的时间。
但如果我们用字典来存储这些数据,那么查找就会非常便捷高效,只需 O(1) 的时间复杂度就可以完成。原因也很简单,刚刚提到过的,字典的内部组成是一张哈希表,你可以直接通过键的哈希值,找到其对应的值
集合与列表相比:
如果需求是找出商品有多少种不同的价格,我们来比较一下
# 如果选择使用列表,对应的代码如下,其中,A 和 B 是两层循环。同样假设原始列表有 n 个元素,那么,在最差情况下,需要 O(n^2) 的时间复杂度 # list version def find_unique_price_using_list(products): unique_price_list = [] for _, price in products: # A if price not in unique_price_list: #B unique_price_list.append(price) return len(unique_price_list) products = [ (143121312, 100), (432314553, 30), (32421912367, 150), (937153201, 30)] print('number of unique price is: {}'.format(find_unique_price_using_list(products))) # 输出 number of unique price is: 3
但如果我们选择使用集合这个数据结构,由于集合是高度优化的哈希表,里面元素不能重复,并且其添加和查找操作只需 O(1) 的复杂度,那么,总的时间复杂度就只有 O(n)
# set version def find_unique_price_using_set(products): unique_price_set = set() for _, price in products: unique_price_set.add(price) return len(unique_price_set) products = [ (143121312, 100), (432314553, 30), (32421912367, 150), (937153201, 30)] print('number of unique price is: {}'.format(find_unique_price_using_set(products))) # 输出 number of unique price is: 3
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
>>>a = [1,2,3] >>> b = [4,5,6] >>> c = [4,5,6,7,8] >>> zipped = zip(a,b) # 打包为元组的列表 [(1, 4), (2, 5), (3, 6)] >>> zip(a,c) # 元素个数与最短的列表一致 [(1, 4), (2, 5), (3, 6)] >>> zip(*zipped) # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式 [(1, 2, 3), (4, 5, 6)]
⑦ 字典和集合的工作原理
对于字典来说,老版本python存储方式没有充分利用空间,很多地方都是空的,很稀疏,如下图
比如举个例子,我们有这样一个字典它就会存成下面的形式:
{'name':'mike','dob':'1999-01-01','gender':'male'}
这样的设计结构显然非常浪费存储空间。为了提高存储空间的利用率,现在的哈希表除了字典本身的结构,会把索引和哈希值、键、值单独分开,也就是下面这样新的结构
个人理解:也就是说,把索引单独拿出来存储,哈希值、键、值存在一起,这样就不会有稀疏的空间
刚刚的例子在新的哈希表结构下的存储形式,就会变成下面这样:
至于他为什么会有空的,个人认为跟数据结构有关系吧,我也没有深究
⑧ 字典、集合的插入、查找、删除
插入操作
每次向字典或集合插入一个元素时,Python 会首先计算键的哈希值(hash(key)),再和mask = PyDicMinSize - 1 做与操作,计算这个元素应该插入哈希表的位置 index =hash(key) & mask。如果哈希表中此位置是空的,那么这个元素就会被插入其中
而如果此位置已被占用,Python 便会比较两个元素的哈希值和键是否相等。
若两者都相等,则表明这个元素已经存在,如果值不同,则更新值。
若两者中有一个不相等,这种情况我们通常称为哈希冲突(hash collision),意思是两个元素的键不相等,但是哈希值相等。这种情况下,Python 便会继续寻找表中空余的位置,直到找到位置为止
值得一提的是,通常来说,遇到这种情况,最简单的方式是线性寻找,即从这个位置开始,挨个往后寻找空位。当然,Python 内部对此进行了优化(这一点无需深入了解,你有兴趣可以查看源码,我就不再赘述),让这个步骤更加高效
查找操作
和前面的插入操作类似,Python 会根据哈希值,找到其应该处于的位置;然后,比较哈希表这个位置中元素的哈希值和键,与需要查找的元素是否相等。如果相等,则直接返回;如果不等,则继续查找,直到找到空位或者抛出异常为止
删除操作
对于删除操作,Python 会暂时对这个位置的元素,赋于一个特殊的值,等到重新调整哈希表的大小时,再将其删除。
不难理解,哈希冲突的发生,往往会降低字典和集合操作的速度。因此,为了保证其高效性,字典和集合内的哈希表,通常会保证其至少留有 1/3 的剩余空间。随着元素的不停插入,当剩余空间小于 1/3 时,Python 会重新获取更大的内存空间,扩充哈希表。不过,这种情况下,表内所有的元素位置都会被重新排放。
虽然哈希冲突和哈希表大小的调整,都会导致速度减缓,但是这种情况发生的次数极少。所以,平均情况下,这仍能保证插入、查找和删除的时间复杂度为 O(1)
32、字符串
字符串是由独立字符组成的一个序列,通常包含在单引号、双引号或者三引号之中。
可以将字符串想象成一个由单个字符串组成的数组,所以,Python的字符串同样支持索引、切片和遍历等操作
>>> name = 'ishells' >>> name[0] 'i' >>> >>> name[0:3] 'ish' >>> # 与其他数据结构如列表、元组一样,字符串的索引同样从0开始,index=0表示第一个元素(字符) >>> for char in name: ... print(char) ... i s h e l l s # 特别注意,Python的字符串是不可变的。 >>> name 'ishells' >>> >>> name[0] = 'I' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment # Python字符串的改变,只能通过创建新的字符串来完成。 >>> name 'ishells' >>> >>> new_name = 'I' + name[1:] >>> new_name 'Ishells # Python中并没有类似java中StringBuilder的可变字符串类型(每次添加、改变、删除字符,无需创建新的字符串,时间复杂度仅为O(1)),Python中想要改变字符串只能老老实实地创建新的字符串,往往需要O(n)的复杂度,n为新字符串的长度。
Python字符串的改变,通常只能通过创建新的字符串来完成,替换字符可以采用这个做法
s = 'H' + s[1:] s = s.replace('h','H')
加法操作符 ' += ' 的字符串拼接方法:
# 加法操作符打破了字符串不可变的特性 >>> name 'ishells' >>> >>> new_name 'Ishells' >>> # name += new_name #表示name = name + new_name >>> name += new_name >>> name 'ishellsIshells' >>> >>>
s = ' ' for n in range(0,100000): s += str(n) # 这个例子,时间复杂度是多少呢? 在python 2.5 之前,每次创建一个新的字符串都需要O(n)的时间复杂度,总的时间复杂度是O(1)+O(2)+……O(n)=O(n^2),但是python 2.5 开始,每次处理字符串的拼接操作时(str1 += str2),Python首先会检查str1还有没有其他的引用,如果没有的话,就会尝试原地扩充字符串buffer的大小,而不是重新分配一块内存来创建新的字符串并拷贝。这样的话,上述例子中的时间复杂度就仅为O(n)了。
对于字符串拼接问题,除了使用加法操作符,我们还可以使用字符串内置的join函数
l = [] for i in range(0,10000): l.append(str(n)) l = ' '.join(l) #由于列表的append操作是O(1)复杂度,字符串同理,因此,这个含有for循环例子的时间复复杂度为 n*O(1) = O(n) # join函数用法: 'sep'.join(seq) # sep为分隔符,可为空,seq 是要链接的元素序列、字符串、元组、字典 # 返回值:返回一个以分隔符sep连接 seq的各个元素后生成的字符串 >>> str =' ' >>> str2 = str.join('zjib') >>> print(str2) z j i b >>> >>> l = ['jzib','ishells','cn'] >>> print(' '.join(l)) jzib ishells cn >>>
字符串分割函数split(),string.split(separator),表示把字符串按照separator分割成子字符串,并返回一个分割后子字符串组合的列表,常常应用于对数据的解析处理,比如我们读取了某个文件的路径,想要调取数据库的API,去读取对应的数据:
""" 语法: split(str="",num=string.count(str))[n] 参数: str表示分隔符,默认为空格,不能为空,若字符串中没有分隔符,则把整个字符串作为列表的一个元素 num表示分割次数,如果存在参数num,则仅分隔成 num+1 个子字符串,并且每一个子字符串可以赋给新的变量 [n]:表示选取第n个分片 注意:当使用空格作为分隔符时,对于中间为空的项会自动忽略 按照指定的分隔符分割之后返回数据是列表形式,然后可以使用[]分片进行获取具体的字符,如同列表按索引获取元素 """ def query_data(namespace,table): """ 给定名称空间,查询数据库以获得对应的数据 """ path = 'hive://ads/training_table' namespace = path.split('//')[1].split('/')[0] # 返回‘ads’ table = path.split('//')[1].split('/')[1] # 返回‘training_table’ data = query_data(namespace,table)
1
# 按照指定的分隔符分割之后返回数据是列表形式,然后可以使用[]分片进行获取具体的字符,如同列表按索引获取元素 >>> path = 'hive://ads/training_table' >>> print(path.split('//')) ['hive:', 'ads/training_table'] >>> print(path.split('//')[0]) hive: >>> print(path.split('//')[1]) ads/training_table >>> >>> print(path.split('//')[1].split('/')[0]) ads
其他常见处理字符串的函数:
string.strip(str), # 表示去掉首尾的str字符串 string.lstrip(str), # 表示只去掉开头的str字符串 string.rstrip(str), # 表示只去掉尾部的str字符串
这些函数在数据处理中同样很常见,比如从文件读进来的字符串中,开头和结尾都含有空字符,我们需要去掉他们,就可以用strip()函数
Python 中字符串还有很多常用操作,比如,
string.find(sub,start,end)
,表示从start 到 end 查找字符串中子字符串sub的位置等等。这里只强调了最常用并且容器出错的几个函数字符串的格式化:
# 通常,我们使用一个字符串作为模板,模板中会有格式符,这些格式符为后续真实值预留位置,已呈现出真实值应该呈现的格式。字符串的格式化,通常会用在程序的输出、logging等场景 # 举一个常见的例子,比如我们有一任务,给定一个用户的userid,要去数据库查询该用户的一些信息,并返回。而如果数据库中没有此人的信息,我们通常会记录下来,这样有利于往后的日志分析,或者是线上bug调试等等。 print('no data available for persion with id:{},name:{}'.format(id,name)) # 其中string.format(),就是所谓的格式化函数;而大括号{}就是所谓的格式符,用来为后面的真实值--变量name预留位置。如果id='123' 、name = 'ishells',那么输出就是 'no data available for person with id:123,name:ishells' # 不过要注意,string.format()是最新的字符串格式函数与规范。自然,我们还有其他的表示方法,比如在Python之前版本中,字符串格式化通常使用 % 来表示,那么上述的例子,就可以写成 print('no data available for person with id: %s, name: %s' % (id, name)) 其中 %s 表示带符号的字符串型,%d 表示带符号的十进制整型等等,%o带符号的八进制整数,%x表示带符号的十六进制整数 # Python支持如下标志。 # -:指定左对齐。 # +:表示数值总要带着符号(正数带“+”,负数带“_,,)。 # 0:表示不补充空格,而是补充0 (这三个标志可以同时存在) # 整数格式化例如如下代码: # 最小宽度为6,左边补o print("num2 is: %06d" %num2) num2 is: 000030 # 最小宽度为6,左边补0,总带上符号 print("num2 is: %+06d" %num2 ) num2 is: +00030 # 最小宽度为6,左对齐 print("num is: %-6d" %num2) num2 is: 30 # 浮点数格式化如下: value = 3.001415926535 # 最小宽度为8,小数点后保留3位 print("value is %8.3f" %value) # 最小宽度为8,小数点后保留3位,左边补0 print("value is %08.3f" %value) # 最小宽度为8,小数点后保留3位,左边补0,始终带符号 print("value is %+08.3f" %value) # 只保留3个字符 name = "zjibishells" print("name is %.3s" %name ) # 输出zji # 只保留2个字符,最小宽度位10 print("name is %10.2s" %name)
总结:
1
字符串切片:[index:index+n]
2
遍历字符串:for 循环
3
字符串的改变:只能通过创建新的字符串来完成string.replace('old_char','new_char')
4
加法操作符 '+='
5
join函数拼接字符串'sep'.join(seq)
6
split函数分割字符串split(str="",num=string.count(str))[n]
7
处理字符串string.strip(str), # 表示去掉首尾的str字符串 string.lstrip(str), # 表示只去掉开头的str字符串 string.rstrip(str), # 表示只去掉尾部的str字符串
8
字符串格式化print('no data available for person with id: {}, name: {}'.format(id, name)) # 其中的 string.format(),就是所谓的格式化函数;而大括号{}就是所谓的格式符,用来为后面的真实值——变量 name 预留位置。
33、导入模块 (参考来源)
一般情况下模块导入规则: import xxx 时搜索文件的优先级如下:
1.在当前目录下搜索该模块
2.在环境变量 PYTHONPATH 中指定的路径列表中依次搜索
3.在 Python 安装路径的 lib 库中搜索在 Python 程序启动时进行配置,自动将 top-level file 的 home 目录(或用一个''表示当前工作目录)、PYTHONPATH 设置的目录、.pth 文件里的目录、标准库目录合并成一个 list ,组成每次 import 时 Python 搜索的目录列表,放到sys.path 中