变量
可以说python只有名字没有变量,不需要定义类型,你赋值啥他就啥类型(所以定义变量时就得赋值,有int、float、str、bool
),甚至可以赋值其他类型,例如:
a=1
a='a'
那么a就从int
变成char
,这是可以的,还有就是比如:
>>> 5+3
8 #两个int,按正常计算
>>> '5+3'
'5+3' #整个是str,所以不变
>>> '5'+'3'
'53' #两个str,结果拼在一起
命名时可以包括字母、数字和下划线,但不能以数字开头,且大小写敏感
注:
python作为一门动态语言不需要指明变量类型,但这样也就少了编译时检查错误的环境(无法做类型检查),只有在运行时才能知道是否存在错误
变量实质
python当中的变量实际就只是一个指针,指向对应数据的地址,因此python的变量可以随意修改指向,举例:
a = 1
print(id(a), id(1))
a = 2
print(id(a), id(1), id(2))
# 会发现a只是将指针指向了2
# 1892936144 1892936144
# 1892936176 1892936144 1892936176
在python内部有个intern机制:将一定范围内的数或者短的字符串提前载入内存,建立全局唯一的对象,下次再使用时则直接指向他
e记法
python里计数可以用科学计数法,例如0.0002
就可以是2e-4
,500000
可以是5e5
,但是e记法的数一定是浮点数,所以5e5 = 5.0e5 = 500000.0
bool类型
True
就是1,False
就是0(记住都是首字母大写),所以可以True * False
(1*0=0),类似这样,但是不能True / False
(0不能做分母),当然这些做法可以,但是不推荐
注:
所有值为0的:0
、0+0j
、0.0
,空字符串''
、空列表[]
、空元组()
、空字典{}
、空集合set()
和None
都是False,其他都是True
强制转型
python支持,例如:a = int(1.666)
,结果a为1,其中float转int是将小数点的数后直接砍掉。还有其他的强转如下:
#类型转换
float() #转成float
str() #转成str,如果是几种类型混合,其会用连接符表示,比如:`a = str(ab1)`,就会变成`'ab+1'`
#进制转换
bin() #转二进制
oct() #转八进制
int() #转十进制
hex() #转十六进制
对上面强转举例:
>>> bin(21)
'0b10101'
>>> type(bin(21))
>>> oct(10)
'0o12'
>>> hex(10000)
'0x2710'
可以看出除了十进制的,其他几种进制强转都是前缀加数字部分,类型也都是字符串型,所以如果想要只取数字部分就可以通过replace()
替换掉前缀
注:
强转时,不要有以其命名的变量,例如:
>>> str = 1
>>> str(2)
Traceback (most recent call last):
File "", line 1, in
str(2)
TypeError: 'int' object is not callable
可以看出报错了,说str是int类型,无法被调用,说白了就是str已经被当成前面的变量1了,所以现在只是个数字而已(实际上现在获取的str是全局空间的,而不是内置空间的str)
python中负数的二进制展示问题:
对于十进制数据,通过bin
转成二进制时,是直接将其转成正数的二进制加上一个负号;而对于十六进制通过bin
转二进制时,才是返回真正的二进制表示,举例:
>>> bin(-100)
'-0b1100100'
>>> bin(-100 & 0xffffffff)
'0b11111111111111111111111110011100'
参考:https://www.runoob.com/w3cnote/python-negative-storage.html
"""长字符串"""
像特别长的字符串甚至有换行,好几行的那种,光用'
或者"
引起来是不行的,此时可以用头尾各3个"
包起来实现,举例:
"""
这是
一个
多行
长字符串
"""
字符串(str)
基本使用
用单引号或双引号包起来的字符组合,但要注意不能单引号/双引号自己给自己嵌套使用,比如下面的用法是不正确的:
'在'单引号'里面嵌套单引号'
"在"双引号"里面嵌套双引号"
所以如果要在字符串中表示单引号/双引号,可以混合着使用,或者使用转义字符,例如下面的方式:
'"双引号被单引号包着"'
"'单引号被双引号包着'"
'使用转义的单引号:\''
其可以几个字符用+
连起来(得都是字符串类型),也可以*
起来,举例:
>>> 'aaa'+ str(2) #+起来时要求同种类型
'aaa2'
>>> 'aaa'*2 #*起来时不要求为同种类型
'aaaaaa'
开头加r
假如给变量赋值如下:str='c:\now'
,那么在print里输出时,\n
会被转义,为了避免这种情况,此时我们可以用\\
替换\
,这是解决只有少数地方会被转义的情况。
但是如果是多路径比如:'c:\now\abc\ttt'
时,每个地方都用\\
来替换会很麻烦,所以此时就可以在字符串前面加个r
,代表原始字符串,例如:str=r'c:\now\abc\ttt'
,则str就会自动变成c:\\now\\abc\\ttt
,用print输出时就是正常的c:\now\abc\ttt
了
开头加u
一般在内容为中文时用,代表内容用utf-8编码的字符串
开头加b
代表字节类型,即byte
类型,字节流通过decode(编码)
来解码成字符串,反过来字符串通过encode(编码)
来编码成字节类型,举例:
>>> type(b'abc') //字节类型
>>> type(u'abc') //字符串类型
>>> type(b'abc'.decode('utf-8')) //字节流解码成字符串
>>> type(u'abc'.encode('utf-8')) //字符串编码成字节流
分片
可以用分片方法截取字符串的一部分,例如str1='abcde'
,只取其中abc就可以:str1[:3]
,取'cbce'
就可以:str1='c'+str1[1:3]+'e'
查询索引
字符串其实可以看作是一个特殊的数组,其每个字符按顺序排放,所以可以像查数组那样,例如上面的str1,如果:str1[1]
就会输出'b'
,即第二个字符
通用方法
字符串能使用的方法特别多,像count()
计数、index()
索引(find
方法也可以索引,使用跟index一样,但index找不到会报错,find会返回-1,还有像rfind
方法,是从右边开始查,很多r开头的都是从右边开始的意思)啥的都能用,参照列表,还有一些常用方法如下:
把第一个字母改为大写
str1.capitalize()
把所有字母改为大写
str1.upper()
将字符串所有内容改为小写
str1.casefold()
将字符串居中
例如我要一个长度10的字符串,然后原来的字符串内容在里面就可以:str1.center(10)
,结果就是' abcde '
;如果要居左就用ljust
,举例:
>>> "abc".ljust(10) #居左
'abc '
>>> "abc".rjust(10) #居右
' abc'
指定编码
str1.encode(encoding='utf-8',error='strict')
表示指定为utf-8编码,error参数代表设置不同错误的处理方案,默认为strict
,此时当编码不符是会报错,如果要设置宽松点的情况可以用ignore
,此时会忽略这个错误
判断字符串是否以某子字符串结束
endswith(sub[,start[,end]])
例如判断上面str1:str1.endswith('e')
,因为是以'e'结尾,所以返回True(判断'de'、'cde'啥的也是对的),如果判断是否以某字符串开头用startswith()
把'\t'变成空格
>>> str2='a\tbcd\tc'
>>> str2.expandtabs()
'a(7个空格)bcd(5个空格)c'
(因为如果方法里不填入数字,默认为8个空格,而tab又是从这个字符串头开始数加上空格共8位,所以a后面加7个空格,bcd加5个)
判断是否为只有字母的非空字符串
str1.isalpha()
结果返回True或False
判断是否只有字母或数字(可以都有)的非空字符串
str1.isalnum()
判断是否只有数字的非空字符串
str1.isnumertic()
,还有isdigit()
都是判断数字,但是numertic就算汉字数字比如四也是True,而digit是False,还有一个是isdecimal()
三者区别参考:
http://www.cnblogs.com/jebeljebel/p/4006433.html
判断字符串是否是标题,即字母都以大写开始,其余位均小写
>>> str1='Abc'
>>> str2='AbC'
>>> str1.istitle()
True
>>> str2.istitle() #(要把字符串变标题形式就用title())
False
>>>
以sub做分隔符插入到字符
str1.join()
,举例:
>>> str1='abc'
>>> str1.join('123')
'1abc2abc3'
替换字符串
str1.replace()
,举例:
>>> str1='abcabcabc'
>>> str1.replace('b','d',2)
'adcadcabc'
对于其参数,第一个是要替换的字符,第二个是替换成的,第三个是最多替换几个(第三个参数可选,默认全部替换)
分割字符串
str1.split()
,举例:
>>> str1='i love you' #第二个中间有3个空格
>>> str1.split() #按1到多个空格分割
['i', 'love', 'you']
>>> str1.split(' ') #按单个空格分割
['i', 'love', '', '', 'you']
>>> str1.split('i')
['', ' love you']
括号是选择按什么字符串分割,默认是空格(1到多个),比如括号里是'i',结果就是['',' love you'],'i'被切了,左边是空字符串
注:
split
还有第二个参数,代表从左到右对前几个指定字符进行分割,举例:
>>> a = "x.y.z.k.q"
>>> a.split('.')
# 默认对所有.都分割
['x', 'y', 'z', 'k', 'q']
>>> a.split('.', 1)
# 只对从左到右第一个点分割
['x', 'y.z.k.q']
>>> a.split('.', 2)
# 只对从左到右第二个点分割
['x', 'y', 'z.k.q']
同理从右到左可以用rsplit
方法实现
按行切割
split
会对一到多个空格、缩进,以及换行等都进行切割,但splitlines
只会根据行来切割,举例:
>>> a = """sda s
dsadas
gdfgd"""
>>>
>>> a.split()
# 可以看到空格和行的都被切割了
['sda', 's', 'dsadas', 'gdfgd']
>>> a.splitlines()
# 可以看到仅按行切割
['sda s', 'dsadas', 'gdfgd']
删除字符串前面或后面的所有某一个字符串
str1.strip()
,举例:
>>> str1='abacaa'
>>> str1.strip('a')
'bac'
只会删除前面和后面,中间不管,括号里没东西默认删空格,如果只要删除左边的某些字符就用:lstrip()
,右边就用rstrip()
,可以叠加使用,比如:
>>> str1='abacaa'
>>> str1.strip('a').rstrip('c')
'ba'
翻转字符串大小写
str1.swapcase()
,举例:
>>> str1='aBc'
>>> str1.swapcase()
'AbC'
翻转字符串内容
常用的方法有分片、列表翻转后转字符串、循环,举例:
#分片
>>> a = 'abcde'
>>> a[::-1]
'edcba'
#列表翻转字符串
>>> b = list(reversed(a))
>>> "".join(b)
'edcba'
#for循环就不举例了,就列表翻转然后一一遍历相加
更多字符串方法参考
http://bbs.fishc.com/thread-38992-1-1.html
列表(list)
创建一个空列表:
empty=[]
不像数组必须都得存放同一种数据,列表里面可以放各种数据类型,例如:
user = [123 ,'abc' ,'用户1' ,[1,2,3,4]]
其中列表内每个对应下标为从0开始,但比较特殊的是比如上面的user[3]是一个列表:[1,2,3,4],所以如果要定位到4的下标就用user[3][3]
,即以二维数组的方式
添加元素
append()方法
例如对上面的user:user.append('新用户')
,这个就会被加到列表的最后一个,因为在最后一位,所以可以通过user[-1]
查看。要注意append只能加一个参数
extend()方法
想要加多个参数时可以用extend()
方法,但是注意extend()也只能传一个参数,不过他可以传一个列表参数(其实是迭代器,但是可以先当成是一个列表),其原理就是把传过去的列表内容分别加到后面,从而传进多个参数,例如对上面的user: user.extend(['a','b'])
,结果就添加了'a'和'b'进去
注意要以列表形式传多个参数,如果直接传多个参数,比如extend('a','b')
就会报错
注:
append()
也能传列表,但和extend()
不同,extend是默认传入列表,所以会依次把列表里的参数传进去;而append传列表时就是单纯的传一个列表进去,比如前面通过两种方法传['a','b'],在extend里传入的是'a'和'b',在append里传入的是['a','b']
insert()方法
想要传到自己想要的位置时可以用insert()
方法,例如想传字符串'c'到上面user列表的第二个位置:user.insert(1,'c')
,其编号也是从0开始的
注:
当对列表使用+
或者+=
(两种方法实质是有区别的)时也可以添加元素,+
操作算是新建一个新的列表并添加元素,而不是对原来列表添加元素,所以原来的列表并不会发生改变,而+=
是在原来的列表里添加元素,所以效率上比+
要高些,举例:
>>> a = [1,2,3]
>>> b = a + [4] #新建一个列表b存放a和[4]相加的列表
>>> b
[1, 2, 3, 4]
>>> a #a并没有发生改变
[1, 2, 3]
>>> a = [1,2,3]
>>> a += [4]
>>> a
[1, 2, 3, 4]
注2:
几种添加元素效率比对(一般情况下):+
<insert
<extend
<append
<+=
调换元素
拿上面user对调第一第二个元素举例:
temp=user[0]
user[0]=user[1]
user[1]=temp
当然python里还可以支持更精简的写法:
user[0], user[1] = user[1], user[0]
结果和前面是一样的,还有就是可以用列表分片方法来替换元素,参考下面的列表分片
删除元素
remove()方法
按变量名删除,例如:user.remove('abc')
,如果有同名的变量就只删除第一个,如果想把某个同名元素全部删除,可以循环判断当存在某个元素时则删除该元素,举例:
while "abc" in user:
li_safe_content.remove("abc")
del函数
按其下标位置删除,例如删除user的第一个变量:del user[1]
,del后面加变量名甚至可以删除整个变量,例如:del user
就会把整个user列表删了
pop()方法
其会弹出一个元素(和删除有点区别,其在删除元素的基础上,还会将该元素作为返回值输出),例如:user.pop(1)
,就会删除列表的第2个元素(如果括号里没数字就默认弹出最后一个),并输出,我们可以把这个给赋值存储以备后面要用,例如:temp = user.pop()
,此时删除最后一个值并赋值给temp
删除重复元素
可以使用numpy
模块下的unique()
方法,然后再用list()
方法转成列表,也可以用set()
方法转成集合后再转回来,举例:
>>> a = [1,2,3,1,2,1]
>>> list(set(a))
[1, 2, 3]
列表分片
可以截取列表的一部分,比如:user[1:3]
说明从下标为1的截取到下标为3的(不包括3),还可以user[:3]
,说明从第1个到下标为3的(不包括3),user[:]
说明截全部,user[:-2]
说明截取去掉最后两个数据后的列表;其还有第三个可选参数,意思是变化量,举例:
>>> d = [1,3,4,5,8]
>>> d[:4:2] #截取从第一个到第4个,每次跨两步(隔一个)取
[1, 4]
>>> d[::-1] #截取整个列表,变化量为-1,即取倒过来的列表
[8, 5, 4, 3, 1]
切片还可以迭代(连续)切片,举例:
>>> d = [1,3,4,5,8]
>>> d[1:4][1:] #先切出2到4个([3,4,5]),然后再切2到后面的所有
[4, 5]
>>> b = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
>>> b[1:][1] [3] #先切出2到后面([[5, 6, 7, 8], [9, 10, 11, 12]]),然后取第2个([9, 10, 11, 12])的第4个
12
上面说的通过列表替换元素方式如下:
>>> a = [1,2,3,4,5]
>>> a[1:3] = [6,6,6,6,6] #把2、3位替换成5个6
>>> a
[1, 6, 6, 6, 6, 6, 4, 5]
注:
分片一般用来截取一个新的列表,例如:temp = user[1:3]
,此时temp就是一个新列表了,这里要注意下:temp1=user[:]
和temp2=user
虽然看起来都是复制整个user,但实际上不一样,temp1应该更像是截取,所以user无论怎么变化,跟temp1都无关,而temp2更像是直接指向user,所以是和user同步的,user做了什么操作,temp2也会跟着改,这也是为什么需要列表分片来复制列表的原因之一
逻辑操作
两个列表比较大小,只比较第一个元素,如果第一个一样再比第二个...;两个列表相加就是后一个的变量加到前一个的后面;列表还可以和一个数相乘,就是把列表里的内容重复几次,例如:
>>> a=[1,2,3]
>>> a*2
[1,2,3,1,2,3]
注:
在数据分析模块里有数组概念,其逻辑操作则和C当中数组一样,两个数组相加就把同一位置的值相加,相乘除就把所有值都乘除一定大小
判断是否在列表
用in
或者not in
(成员关系操作符),例如a[]里面有1,所以:1 in a
就会是True,但只能判断第一层,假如a列表为a[2,3,[1,2,3]] ,那么判断结果就为False,假如这时还想判断是否存在1,可以用:1 in a[2]
,因为在第3个元素里所以用a[2]
计算参数个数
用count()
方法,括号里输入参数名,例如:
>>> a=[1,2,3,1,2]
>>> a.count(1)
2
既然count()
可以计算列表里某个元素的数量,那么就可以通过将其和集合结合起来,获取列表每个数据的数量,举例:
li = [3, 9, 3, 2, 5, 6, 7, 3, 2, 3, 3, 3]
dict1 = {}
#用字典来存储结果
for i in set(li):
dict1[i] = li.count(i)
print(dict1)
结果:
{2: 2, 3: 6, 5: 1, 6: 1, 7: 1, 9: 1}
索引参数位置
用index()
方法,但如果只输入要查找的变量名就只会告诉你第一个该参数的位置,例如对上面的a:a.index(2)
输出就是1,所以可以在index里再加2个参数确定索引的开头和结尾,例如在第三个到第五个之间查2:a.index(2,2,4)
合并列表
在字符串中有split()
来把字符串分割成列表,所以也有join()
把列表合并成字符串,相当于split()的逆方法,比如:
>>> a = '-' #中间用a的值隔开
>>> b = a.join(['sad', '2', 'gds'])
>>> b
'sad-2-gds'
>>> b.split('-')
['sad', '2', 'gds']
翻转列表
reverse()
方法,举例:
>>> a=[1,2,3]
>>> a.reverse()
[3,2,1]
列表排序
sort()
方法,如果不输入参数则默认从小到大排序(列表内容必须为可对比的类型,例如列表里同时存在数字和字母就会报错),举例:
>>> a=[2,4,3]
>>> a.sort()
[2,3,4]
其实sort有3个参数,即sort(function,key,reverse=False)
,function是自己指定的算法,key代表和算法搭配的关键字,第三个代表是否倒序,默认为False代表不倒序,如果要倒序就改成True,例如对b=['s','ac','gdsa']
进行按字符串长度的倒序排序则:b.sort(key=len,reverse=True)
注:
列表翻转和排序只会对当前列表进行操作,且没有返回值,所以如果想把列表翻转/排序后赋值给另一个列表要分两步进行,看下面代码:
>>> a = [2,3,7,5,1]
>>> b = a.sort()
>>> b
#可以看出b是指向空的,并没有把a排序后返回传给b
>>> a
[1, 2, 3, 5, 7]
#但是可以看出a已经排好序了
>>> a.reverse()
>>> b = a
>>> b
[7, 5, 3, 2, 1]
#可以看出如果要翻转/排序后赋值,分两步进行就能成功
打乱列表顺序
参考random模块的shuffle()
方法,把列表作为参数传入即可,举例:
>>> import random
>>> a
[1, 2, 5, 3, 7]
>>> random.shuffle(a)
>>> a
[5, 3, 1, 7, 2]
元组(tuple)
和列表非常像,区别是其内容不可修改,所以像排序啥的操作基本都没有
特点
- 可迭代
- 不可变
- 可拆包
注:
元组的不可变性是指存放的内容地址不可变,并不是内容不可变,举例:
content = [1,2,3]
a = (content,4)
print(a)
content.pop()
print(a)
# ([1, 2, 3], 4)
# ([1, 2], 4)
可以看到元组内部存放的列表内容是可以改变的,但列表的内容改变,列表的内存地址并没有一起改,所以是允许的
与list比较
- 由于不可变,所以其会作为常量,在编译时就能够确定内容,因此性能更好
- 由于不可变,因此可以进行哈希运算(要求元组内部的数据也是可哈希类型的,例如元组内部存放了列表,那么就不能进行哈希运算了),也就可以存放到集合或者字典的key当中
- 线程安全
定义元组
定义方法是用小括号,例如:a=(1,2,3,4,5)
,创建一个空元组:a=()
,但是元组的标志性符号是逗号不是小括号,例如定义:a=(1)
,用type函数会发现a是int型,而定义:a=1,2,3
,会发现a是元组,所以说逗号才是元组的标志,像前面如果想创建只有一个元素的元组就得后面加个逗号,例如:a=(1,)
(不用括号也可以)
查询元组
和列表查询一样,例如查第二个:a[1]
(就创建时候用小括号)
注:
除了定义、删除的语法还有不可修改的规定和列表不同外,其他的使用方法和列表几乎相同,如分片:b=a[1:]
;还有像count()
(计算出现次数)、index()
方法(索引)、in
、not in
和一些逻辑操作符都可以用,参照上面列表用法
更新元组
虽然说元组不能修改,但是我们可以更新他,比如在前面的a的第二个位置插入6,可以这样:a=a[:1]+(6,)+a[1:]
(这里括号和逗号都要在),这时候就更新元组了,这里不是改变,因为a被重新赋值了,已经不是原来的他了(用id(b)
可以发现地址已变)
删除元组
一般删除元组里的内容就用分片截取就好了,跟上面的更新差不多,如果要删除整个元组就用del
函数,例如:del a
注:
上面知道元组的标志是逗号,所以(8)
是代表整数8,(8,)
代表元组8,,所以如果用8与上面两个分别相乘将会是不同的结果:
>>> (8)*8
64
>>> (8,)*8
(8,8,8,8,8,8,8,8)
注2:
列表、元组可以同时给多个变量同时赋值,例如:
>>> x = [1,2,3,4,5]
>>> a,b,c,d,e =x
>>> a
1
>>> a,b,c,d,e
(1, 2, 3, 4, 5)
#可以看出此时a,b,c,d,e就变成对应的1,2,3,4,5了
前提是赋值变量数和列表/元组数值量相同
拆包
a = [1,2,3,4]
b, *c, d = a
print(b, c, d)
# 1 [2, 3] 4
集合(set)
如同字面意思,跟数学的集合一样,里面的数据都是唯一的(最主要意义,所以一般用来去除重复内容),而且是无序的
定义
- 直接用
{}
定义:举例:num1 = {1,2,3,3,4,2,1}
,因为值唯一,所以num1为{1, 2, 3, 4} - 用
set()
函数:举例:set1 = set([1,2,3,3,2,1])
,括号里面可以是列表、字符串、元组、字典等
注:
定义一个空的集合不能用:set1 = {}
,这样set1的类型是dict
,所以应该用:set1 = set()
添加元素
用add()
方法,例如:num1.add(6)
,就会加进去了,但只能加一个数据,而且不能加list、dict等,可以加tuple
移除元素
用remove()
方法,例如:num1.remove(1)
,如果删除的内容不存在则会报错,如果希望删除的数据即使不存在也不会报错,那么可以用discard()
方法
注:
因为集合无序,所以无法索引,要读取里面数据可以用for一个个读取出来,也能用in来判断,或者把他变成一个列表等等
交集
使用intersection()
方法,或者用&
,举例:
>>> a = {1,2,3,5,7}
>>> b = {2,4,5,6}
>>> a.intersection(b)
{2, 5}
>>> a & b
{2, 5}
并集
使用union()
方法,或者用|
,举例:
>>> a = {1,2,3,5,7}
>>> b = {2,4,5,6}
>>> a.union(b)
{1, 2, 3, 4, 5, 6, 7}
>>> b.union(a)
{1, 2, 3, 4, 5, 6, 7}
>>> a | b
{1, 2, 3, 4, 5, 6, 7}
去不同
比如要找出两个集合的不同内容,可以将两个集合相减,举例:
>>> a = {1,2,3,4,5}
>>> b = {1,5,6}
>>> a-b
{2, 3, 4}
#可以看出结果就是将a中有的减去b中有的剩下的值
这里放上来一道网申的笔试题,感觉用集合解决太完美:
现在有一个数组,其值为从1到10000的连续增长的数字。出于某次偶然操作,导致这个数组中丢失了某三个元素,同时顺序被打乱,现在需要你用最快的方法找出丢失的这三个元素,并且将这三个元素根据从小到大重新拼接为一个新数字,计算其除以7的余数。 例:丢失的元素为336,10,8435,得到的新数字为103368435,除以七的余数为2。
输入描述:
输入数据为一行,包含9997个数字,空格隔开。
输出描述:
输出为一行,包含一个数字。
这道题最主要的问题就是用最快方法找出集合1到1w里面没有的3个数,如果用for循环递归一个个比对,显然花的时间不是一点两点,于是这里就可以用到集合相减找不同来实现,把两种方式的代码都放出来如下:
#用迭代的方式
li = input().split()
li.sort()
li_t = []
for i in range(1,10000):
#一个个用if去判断
if str(i) not in li:
li_t.append(i)
result = ''
for each in li_t:
result += str(each)
print(int(result)%7)
#用集合的方式
li = set(input().split())
li2 = set(str(i) for i in range(1,10000))
#创建一个1到10000的集合然后和输入的集合相减,瞬间就得到那3个没有的了
li_result = list(li2 - li)
li_result.sort()
result = ''
for each in li_result:
result += each
print(int(result)%7)
最终虽然两个都能实现,但是迭代花的时间确是集合的几十倍,所以可以看出集合在某些场景下应用的强大能力
更多集合操作参考
https://www.cnblogs.com/sunyang945/p/7859962.html
不可变集合(frozenset)
相当于集合和元组的集合,不可变。通过frozenset()
函数定义,举例:set2 = frozenset([1,2,3,3,2,1])
更多参考
https://blog.csdn.net/weixin_37887248/article/details/81565468
字典(dict)
定义
(1)用{}
定义,键值对引号隔开,例如:dic = {'a':'admin','b':'book','c':'cook'}
,则dic['b']
就是'book'
注:
在python3.6以前的版本当中,字典都是无序的,如果希望使用有序字典,可以使用collections
下的OrderedDict
类;而在3.6及以后的版本当中,字典都是有序的了
(2)往dict()
中插入元组/列表定义,举例:
>>> a = [(1,'one'),[2,'two']]
>>> dic1 = dict(a)
>>> dic1
{1: 'one', 2: 'two'}
注:
dict()
能强转成字典类型,但是str不能直接转成dict,举例:
>>> dict("{'a':1,'b':2}")
Traceback (most recent call last):
File "", line 1, in
dict("{'a':1,'b':2}")
ValueError: dictionary update sequence element #0 has length 1; 2 is required
所以这时候可以用eval()
函数来转成dict,举例:
>>> a = eval("{'a':1,'b':2}")
>>> a
{'b': 2, 'a': 1}
>>> type(a)
(3)用dict()
直接指定键值对定义,例如:dic1 = dict(x='abc',y='def')
注:
第三种方式键是不加引号的,并且关键字会根据键来排序,例如:
>>> dic1 = dict(x='abc',y='def',z='ghi')
>>> dic1
{'z': 'ghi', 'x': 'abc', 'y': 'def'}
注2:
像列表、字典等数据类型里面还能放像函数、类之类的数据,举例:
def abc(x, y):
print(x+y)
class XYZ():
def __init__(selt,x):
print(x)
a = 50
a = {'a':1, 'b':abc, 'c':abc(1000,1000), 'd':lambda x,y : print(x+y), 'e': XYZ, 'f': XYZ(10000)}
#b和c都是函数,但b没传参数,c传了参数,d是没传参的匿名函数,e是没传参的类,f是传参的类
#因为c和f都传了参数,所以调用时就已经执行了,结果会先输出200和1000,但是之后再调用就不会执行了(其已经变成函数的返回值,这里没有,所以是None)
a['b'](1, 1) #调用abc函数,并传入参数1和2
print(a['c']) #输出执行过参数为3和4的abc函数返回值,因为abc没返回,所以输出None
a['d'](5, 5) #调用lambda函数
print(a['e'].a) #调用类XYZ的a属性
a['e'](100) #调用类,因为传入了参数,执行__init__函数
结果为:
2000 #定义a时第三个参数的输出
10000 #定义a时最后一个参数的输出
2
None
10
50
100
修改
直接给键赋值就好了,例如上面的dic1:
>>> dic1['x']='aaa'
{'z': 'ghi', 'x': 'aaa', 'y': 'def'}
如果是不存在的键就会新建这个键值对
有序字典
默认字典是根据hash之类的原理来排序的,如果希望字典的顺序可以像类似列表那样按顺序排的话,可以使用OrderedDict
,具体参考下面链接:
http://c.biancheng.net/view/2438.html
内置函数
fromkeys(键[,值])
创建并返回一个统一值的新字典,其中可以设置键和值,举例:
>>> d1={}
>>> d1.fromkeys((1,2,3)) #创建一个字典,并定义3个键1、2、3
{1: None, 2: None, 3: None}
>>> d1.fromkeys((1,2,3),1) #定义3个键并同时赋值为1
{1: 1, 2: 1, 3: 1}
他只能给所有键设置同一个值,所以不要以为可以通过传入列表、元组来依次赋值,比如:
>>> d1.fromkeys((1,2,3),(4,5,6))
{1: (4, 5, 6), 2: (4, 5, 6), 3: (4, 5, 6)} #结果不是{1:4, 2:5, 3:6},而是值都为(4, 5, 6)
可以看出其还是给所有元素都设置了同一个值
keys()
返回字典的所有键名,例如:
>>> d1={1:2,2:'two'}
>>> d1.keys()
dict_keys([1, 2])
values()
返回字典的所有值,例如:
>>> d1={1:2,2:'two'}
>>> d1.values()
dict_values([2, 'two'])
items()
返回字典的所有键值对,例如:
>>> d1={1:2,2:'two'}
>>> d1.items()
dict_items([(1, 2), (2, 'two')])
注:
keys
、values
、items
常用于循环输出字典,例如:
for key in d1.keys()
print("循环输出所有的键:",key)
get()
根据键获取值,一般常用d1[键]
输出值,但如果键不存在,这种方法就会报错,所以用get就更宽松,比如对上面的d1:d1.get(1)
结果就是2,但是d1.get(3)
不输出也不会报错,如果用print打印结果会是none,我们还可以用get来判断输出,有就输出值,没有就输出我们想要的内容,比如:
>>> d1.get(3,'nothing')
nothing #因为3不存在所以输出nothing,如果3在就输出3的值
注:
get可以判断键在不在字典,in和not in也可以,比如上面的:1 in d1
结果就是True
注2:
因为键是唯一的,所以可以用get(键)
和dict[键]
来获取值,反过来如果想通过值来获取键,可以通过get条件判断来进行遍历,举例:
>>> d
{2: 2, 3: 6, 5: 1, 6: 1, 7: 1, 9: 1}
>>> [i for i in d if d.get(i) == 1]
#链表推导式当i(键)=1(值)时,输出i
[5, 6, 7, 9]
>>> [i for i in d if d.get(i) == 6]
[3]
如果是要求最值的键,则可以用内置的min
/max
函数,举例:
>>> d
{2: 2, 3: 6, 5: 1, 6: 1, 7: 1, 9: 1}
>>> max(d, key=d.get)
3
clear()
清空字典,例如:d1.clear()
结果d1就变成{}
注:
d1={}
会使d1变成空,但是其原来指向的字典没变成空,而clear才是真正意义上的清空,例如:
>>> d1={1:2,2:'two'}
>>> d2 = d1 #此时d2和d1指向同一个地址,可以用id(d2)查看
>>> d1 = {}
>>> d1 #d1变成空
{}
>>> d2 #d2还是指向d1原来的地方,而原来指向的地方没变成空,所以d2没变成空
{1: 2, 2: 'two'}
###这次使用clear
>>> d1={1:2,2:'two'}
>>> d2 = d1
>>> d1.clear()
>>> d1
{}
>>> d2 #这次d2和d1一样都清空了
{}
copy()
浅拷贝,和赋值不太一样,赋值就相当于确定了指向,所以用id()
查看会发现赋值后原来的字典和赋值的字典地址是一样的,但copy的结果地址是不同的,所以如果原字典内容改变,赋值的字典会跟着改变,但copy的字典不变(copy有点像列表里的整个分段赋值)
pop()
括号里给定键弹出对应的值
popitem()
括号里没参数,随机弹出一个项
setdefault()
跟get
有点像,能输出键对应的值,但不同的是如果键不存在他会新加进去,例如:d1.setdefault(1)
结果是2,但d1.setdefault(3)
,结果d1会变成:{1: 2, 2: 'two', 3: None} ,新建同时还可以赋值,例如:d1.setdefault(5,"aaa")
,d1就会变成:{1: 2, 2: 'two', 3: None, 5: 'aaa'}
update()
把一个字典的东西加到另一个字典里去,例如:
>>> d2={6:'bbb'}
>>> d1.update(d2)
{1: 2, 2: 'two', 3: None, 5: 'aaa', 6: 'bbb'} #加入了d2的内容
for输出
- 方法1:其输出是只输出键而不是值,所以如果想输出值就要输出字典的对应项,举例:
dict1 = {'a':'one','b':'two','c':'three'}
for each in dict1:
print("%s -> %s" % (each,dict1[each]),end=' ') #each只是键
结果为:a -> one b -> two c -> three
- 方法2:通过将字典放入迭代器,然后分别保存键、值,举例:
dict1 = {'a':'one','b':'two','c':'three'}
for key,value in dict1.items(): #分别赋值键值到key和value
print(key,value,end=' ')
结果为:b two a one c three
字典和json区别
字典和json样式十分相似,虽然前者是数据类型,后者是一种格式,但最明显的区别就是字典可以用单引号,而在json中得用双引号,所以如果想将一个字典以json格式传递时,记得将单引号都替换成双引号
Ellipsis
这个是python一个自带常量,和...
是等价的(偶然输入...
发现竟然没有报错,原来还有内置这样的常量),意思相当于什么都没有,或者在数组之类的地方起省略意义,一般可以和pass
相互替换,举例:
>>> ...
Ellipsis
>>> print(...)
Ellipsis
>>> print(Ellipsis)
Ellipsis
字典效率、顺序
- 字典的内存开销大,但是查询快,而自定义的对象、以及python提供的对象也都是用字典来包装的
- 在python3.6之前,字典是无序的,因此其存储顺序和元素添加的顺序有关(基于哈希表内部的原理,哈希冲突的处理等),并且添加数据有可能改变已有数据的顺序(例如哈希表内部达到一定占用率时会进行扩容,将重新开辟空间依次存入,导致顺序改变),所以如果想要使用有序字典,可以使用
collections
模块下的OrderedDict
;python3.6开始,字典都是有序的了
其他数据类型
队列(queue)
先入先出,且一个数据只能取一次,需要import queue
,使用也很简单,先新建一个Queue
对象,然后要往队列加东西就用put()
,取东西就用get()
就行了(用get如果队列为空则会卡住,所以也可以用get_nowait
,当队列为空时会出异常),查看用qsize()
,举例:
q = queue.Queue() #新建一个队列
q.put("one")
q.put("two")
q.put([1,2,3])
q.qsize() #结果为3
q.get() #结果为one
q.get() #结果为two
q.get() #结果为[1,2,3]
q.get() #程序卡住,如果是q.get_nowait()则会报出空异常
如果想要限制队列大小,可以在新建对象时里面加入maxsize
参数,比如:
q = queue.Queue(maxsize=3)
此时一旦超过队列长度就会卡住,然后查看put和get会发现里面都有个block
(默认为True)和timeout
(默认为None)参数,前者代表是否卡住,后者代表卡住几秒,所以我们可以设置为False或者设置timeout时长来避免卡住,比如:
c.get(block=False) #当队列为空时报错,不卡住
c.get(timeout=1) #当队列为空时延时一秒,然后报错
#put方法同理
实例
(通过多线程和队列实现生产者消费者顺序购买产品问题)
import threading
import time
import queue
def Producer():
count = 1
while True:
q.put("产品%d" % count)
print("\033[42;m生产第%d个产品\033[0m" % count)
time.sleep(0.3)
count += 1
print("\033[45;m目前还剩%d个产品\033[0m" % q.qsize())
def Consumer(name):
num = 0
while True:
print("\033[43;m%s购买了%s\033[0m" % (name, q.get()))
num += 1
print("\033[45;m%s共买了%d个产品\033[0m" % (name, num))
time.sleep(1)
q = queue.Queue()
producer = threading.Thread(target=Producer)
consumerA = threading.Thread(target=Consumer, args=("热心市民A",))
consumerB = threading.Thread(target=Consumer, args=("热心市民B",))
producer.start()
consumerA.start()
consumerB.start()
栈(stack)
和前面的队列十分相似,也是导入queue
,但是新建的对象是LifoQueue
,其他和队列一样,当然用列表的append()
和pop()
也可以实现。
优先级队列
在queue中还有一个PriorityQueue
是用来实现优先级队列的,其和上面的区别就是在put的时候传入的是个元组,里面同时设置优先值,值越小越优先,举例:
q = queue.PriorityQueue()
q.put((2, "two"))
q.put((5, "five"))
q.put((0, "zero"))
q.put((1, "one"))
q.get() #(0, 'zero')
q.get() #(1, 'one')
q.get() #(2, 'two')
q.get() #(5, 'five')
双向队列
前面的栈和队列都是单向的,一旦定义以后就只能从头或者尾其中一边操作,在list中虽然可以用pop来实现栈的功能,但是却无法实现队列的功能,所以这里可以用双向队列,需要使用到collections
的deque
,可以直接用from import导入deque,然后通过append()
、appendleft()
、pop()
、popleft()
来实现栈和队列,举例:
from collections import deque
a = deque() # deque([])
a.append(5) # deque([5])
a.appendleft(4) # deque([4, 5])
a.appendleft(3) # deque([3, 4, 5])
a.pop() #5
a.popleft() #3
计数器
在前面的collections里有个Counter
还是作为计数器的存在,其可以计算一个数据结构当中各元素出现的次数,并返回一个字典,需要导入Counter,举例:
from collections import Counter
c = Counter([1,2,3,32,1,2,1,2,2,3,4])
#计算该列表里各元素个数
print(c)
结果为:Counter({2: 4, 1: 3, 3: 2, 32: 1, 4: 1})
python内置类型
数值类型
int
float
bool
complex 复数类型
迭代类型
iterator 迭代器
generator 生成器
序列类型
list
bytes
bytearray 字节流,数据只能是0或1
memoryview 二进制序列
range
tuple
str
array 数组,相比于list,其所有数据必须为同一类型,因此内存空间也是连续的,效率更高
映射
dict
集合
set
frozenset 不可变集合
注:集合和映射在python中的实现差不多,因此效率都比较高
上下文管理器
with
其他
模块类型
类对象
类实例
函数类型
方法类型
代码类型
object对象
type类型
ellipsis类型
notimplemented类型
序列类
分类
容器序列:内部可以存放任意类型数据,如list、tuple、deque
扁平序列:只能存放相同类型的数据,如str、array、bytes、bytearray
可变序列:内容可以修改,如list、array、deque、bytearray
不可变序列:内容不能修改,如tuple、str、bytes
相关基类
collections.abc
下提供了Sequence
(不可变序列)/MutableSequence
(可变序列)类
+/extend/+=区别
- +:必须为相同类型,并且返回一个新的序列对象,源码(基于
UserList
稍作修改):
def __add__(self, other):
return self.__class__(self + self.__class__(other))
- extend:只要是序列类就行,也是在原先对象中改变,本质就是for循环,依次
append
,源码:
def extend(self, values):
'S.extend(iterable) -- extend sequence by appending elements from the iterable'
for v in values:
self.append(v)
- +=:只要是序列类就行,并且在原先的对象中进行改变,内部实际是调用
extend
方法,源码:
def __iadd__(self, values):
self.extend(values)
return self
切片
相关操作
>>> a = [1,2,3,4,5]
>>> a[2:] = [5,6,7]
# 替换第三个元素开始的后面所有元素
>>> a
[1, 2, 5, 6, 7]
>>> a[:0] = [0]
# 在头部插入元素
>>> a
[0, 1, 2, 5, 6, 7]
>>> a[2:] = [9, 8, 7]
>>> a
[0, 1, 9, 8, 7]
>>> a[::2] = [10, 20, 30]
# 隔一个改一个元素,右边迭代的长度必须和左边切片的长度相等
>>> a
[10, 1, 20, 8, 30]
>>> a[:1] = []
# 删除第一个元素
>>> a
[1, 20, 8, 30]
>>> del a[::2]
# 隔一个删一个元素
>>> a
[20, 30]
>>> del a[:1]
# 删除第一个元素
>>> a
[30]
>>> a[0:0] = [1,2,3]
# 往第0个位置插入3个元素
>>> a
[1, 2, 3, 30]
实现原理
在通过切片索引时,会自动将切片语法转成slice
对象,该对象包括起始位置、结束位置和步长三个参数,举例:
a = [1,2,3,4,5]
print(a[0::2])
print(a[slice(0, None, 2)])
# [1, 3, 5]
# [1, 3, 5]
可以看出两个结果都是一样的,然后对象只需要实现__getitem__
方法,在内部处理slice
的处理逻辑即可,我们可以自己模拟实现一个自己的slice
对象和相关的切片处理逻辑,举例:
class MySlice:
def __init__(self, start=None, end=None, step=None):
self.start = start
self.end = end
self.step = step
def get_slice(self):
return self.start, self.end, self.step
class Test:
def __init__(self, data=[]):
self.data = data
def __setitem__(self, item, value):
if isinstance(item, MySlice):
start, end, step = self.parse_slice(item)
value = iter(value)
for i in range(start, end, step):
self.data[i] = next(value)
else:
self.data[item] = value
def __getitem__(self, item):
cls = type(self)
if isinstance(item, MySlice):
start, end, step = self.parse_slice(item)
return cls([self.data[i] for i in range(start, end, step)])
# 重新实例化一个自身对象返回
return cls(self.data[item])
def parse_slice(self, s):
"""自定义切片设置默认值"""
start, end, step = s.get_slice()
if start == None:
start = 0
if end == None:
end = len(self.data)
if step == None:
step = 1
return start, end, step
def __str__(self):
return str(self.data)
li = Test([1,2,3,4,5])
sli = MySlice(0, None, 2)
print(li[sli])
li[sli] = [9, 8, 7]
print(li)
# [1, 3, 5]
# [9, 2, 8, 4, 7]
映射类
不可变映射类
MappingProxyType
,继承于Mapping类,相当于一个只读的字典,源码如下:
MappingProxyType = type(type.__dict__)
我们可以从types
模块下导入,使用举例:
from types import MappingProxyType
di = MappingProxyType({"a":1, "b":2})
print(di)
di["a"] = 2
# 不允许修改,这句会报错
可变映射类
dict
,继承于MutableMapping类,而该类继承于Mapping类
UserDict
可以理解为用python实现的字典,由于python中的列表、字典都是用C写的,内部有些魔法方法之类的未必能生效,因此如果希望继承一个字典的话,那么可以继承collection
模块下提供的UserDict
(collection
下还提供了UserList
/UserStr
与之同理),举例:
from collections import UserDict
class A(dict):
def __setitem__(self, item, value):
super().__setitem__(item, value*2)
class B(UserDict):
def __setitem__(self, item, value):
super().__setitem__(item, value*2)
a = A(x=1)
b = B(x=1)
print(a)
print(b)
# {'x': 1}
# {'x': 2}
可以看到继承自dict的类,实例化的对象的__setitem__
方法没生效,但是继承自UserDict
的却生效了
defaultdict
和UserDict
一样是dict的子类,初始化时可以指定当索引内容不存在时的默认值类型,本质是内部实现了__missing__
的魔法方法,当key的值获取失败时,则会设置一个默认类型的值,举例:
def default():
return {
"name": "aaa",
"age": 18
}
a = defaultdict(default)
# 指定默认是一个固定内容的字典,传入的默认对象必须是可调用的,这里传入一个函数,也可以传入list、dict等数据类型
print(a["x"])
a["y"] = 2
print(a)
# {'name': 'aaa', 'age': 18}
# defaultdict(, {'x': {'name': 'aaa', 'age': 18}, 'y': 2})
可哈希对象
- 对于字典的key、集合的元素都必须是可哈希的才能存入
- 不可变对象都是可hash的,如:
str
、fronzeset
、tuple
,如果希望自己定义的类可哈希,那么必须实现__hash__
的魔法方法
注:
不可变对象如果是可哈希的,还必须满足子元素也都是可哈希的条件,举例:
print(hash((1,2,3)))
# 元组内部维护的数字是不可变的,因此可哈希
li = [1,2,3]
print(hash((1,li)))
# 元组内部有一个可变的列表,不可哈希,因此会报错
# 2528502973977326415
# TypeError: unhashable type: 'list'