免费领取Python自动化学习资料 工具,面试宝典面试技巧,加QQ群,785128166,群内还会大佬技术交流
1 含单个元素的元组
Python中有些函数的参数类型为元组,其内有1个元素,这样创建是错误的:
c = (5) # NO!
复制代码
它实际创建一个整型元素5,必须要在元素后加一个逗号
:
c = (5,) # YES!
复制代码
2 默认参数设为空
含有默认参数的函数,如果类型为容器,且设置为空:
def f(a,b=[]): # NO!
print(b)
return b
ret = f(1)
ret.append(1)
ret.append(2)
# 当再调用f(1)时,预计打印为 []
f(1)
# 但是却为 [1,2]
复制代码
这是可变类型的默认参数之坑,请务必设置此类默认参数为None:
def f(a,b=None): # YES!
pass
复制代码
3 共享变量未绑定之坑
有时想要多个函数共享一个全局变量,但却在某个函数内试图修改它为局部变量:
i = 1
def f():
i+=1 #NO!
def g():
print(i)
复制代码
应该在f函数内显示声明i
为global变量:
i = 1
def f():
global i # YES!
i+=1
复制代码
4 lambda自由参数之坑
排序和分组的key函数常使用lambda,表达更加简洁,但是有个坑新手容易掉进去:
a = [lambda x: x+i for i in range(3)] # NO!
for f in a:
print(f(1))
# 你可能期望输出:1,2,3
复制代码
但是实际却输出: 3,3,3. 定义lambda使用的i
被称为自由参数,它只在调用lambda函数时,值才被真正确定下来,这就犹如下面打印出2,你肯定确信无疑吧。
a = 0
a = 1
a = 2
def f(a):
print(a)
复制代码
正确做法是转化自由参数
为lambda函数的默认参数
:
a = [lambda x,i=i: x+i for i in range(3)] # YES!
复制代码
5 分不清是默认参数还是关键字参数
定义函数f,在使用它时,width的以下三种写法都是OK的,如果未了解函数原型,容易分不清width到底是位置参数还是关键字参数。
def f(a,width=10.0):
print(width)
f(1,width=15.0) # 15.0 # 容易分不清width是位置参数还是关键字参数
f(1,15.0) # 15.0
f(1) # 10.0
复制代码
width是带默认参数的位置参数,关键字参数必须使用两个星号声明。因此如果要区分它们,需要关注函数的定义。
def f(a,**b):
print(b)
f(1,width=15.0) # width是关键字参数,不是默认参数
复制代码
6 列表删除之坑
删除一个列表中的元素,此元素可能在列表中重复多次:
def del_item(lst,e):
return [lst.remove(i) for i in e if i==e] # NO!
复制代码
考虑删除这个序列[1,3,3,3,5]中的元素3,结果发现只删除其中两个:
del_item([1,3,3,3,5],3) # 结果:[1,3,5]
复制代码
正确做法:
def del_item(lst,e):
d = dict(zip(range(len(lst)),lst)) # YES! 构造字典
return [v for k,v in d.items() if v!=e]
复制代码
7 列表快速复制之坑
在python中*
与列表操作,实现快速元素复制:
a = [1,3,5] * 3 # [1,3,5,1,3,5,1,3,5]
a[0] = 10 # [10, 2, 3, 1, 2, 3, 1, 2, 3]
复制代码
如果列表元素为列表或字典等复合类型:
a = [[1,3,5],[2,4]] * 3 # [[1, 3, 5], [2, 4], [1, 3, 5], [2, 4], [1, 3, 5], [2, 4]]
a[0][0] = 10 #
复制代码
结果可能出乎你的意料,其他a[1[0]
等也被修改为10
[[10, 3, 5], [2, 4], [10, 3, 5], [2, 4], [10, 3, 5], [2, 4]]
复制代码
这是因为*复制的复合对象都是浅引用,也就是说id(a[0])与id(a[2])门牌号是相等的。如果想要实现深复制效果,这么做:
a = [[] for _ in range(3)]
复制代码
8 字符串驻留
In [1]: a = 'something'
...: b = 'some'+'thing'
...: id(a)==id(b)
Out[1]: True
复制代码
如果上面例子返回True
,但是下面例子为什么是False
:
In [1]: a = '@zglg.com'
In [2]: b = '@zglg'+'.com'
In [3]: id(a)==id(b)
Out[3]: False
复制代码
这与Cpython 编译优化相关,行为称为字符串驻留
,但驻留的字符串中只包含字母,数字或下划线。
9 相同值的不可变对象
In [5]: d = {}
...: d[1] = 'java'
...: d[1.0] = 'python'
In [6]: d
Out[6]: {1: 'python'}
### key=1,value=java的键值对神器消失了
In [7]: d[1]
Out[7]: 'python'
In [8]: d[1.0]
Out[8]: 'python'
复制代码
这是因为具有相同值的不可变对象在Python中始终具有相同的哈希值
由于存在哈希冲突
,不同值的对象也可能具有相同的哈希值。
10 对象销毁顺序
创建一个类SE
:
class SE(object):
def __init__(self):
print('init')
def __del__(self):
print('del')
复制代码
创建两个SE实例,使用is
判断:
In [63]: SE() is SE()
init
init
del
del
Out[63]: False
复制代码
创建两个SE实例,使用id
判断:
In [64]: id(SE()) == id(SE())
init
del
init
del
Out[64]: True
复制代码
调用id
函数, Python 创建一个 SE 类的实例,并使用id
函数获得内存地址后,销毁内存丢弃这个对象。
当连续两次进行此操作, Python会将相同的内存地址分配给第二个对象,所以两个对象的id值是相同的.
但是is行为却与之不同,通过打印顺序就可以看到。
11 充分认识for
In [65]: for i in range(5):
...: print(i)
...: i = 10
0
1
2
3
4
复制代码
为什么不是执行一次就退出?
按照for在Python中的工作方式, i = 10 并不会影响循环。range(5)生成的下一个元素就被解包,并赋值给目标列表的变量i
.
12 认识执行时机
array = [1, 3, 5]
g = (x for x in array if array.count(x) > 0)
复制代码
g
为生成器,list(g)后返回[1,3,5]
,因为每个元素肯定至少都出现一次。所以这个结果这不足为奇。但是,请看下例:
array = [1, 3, 5]
g = (x for x in array if array.count(x) > 0)
array = [5, 7, 9]
复制代码
请问,list(g)等于多少?这不是和上面那个例子结果一样吗,结果也是[1,3,5]
,但是:
In [74]: list(g)
Out[74]: [5]
复制代码
这有些不可思议~~ 原因在于:
生成器表达式中, in 子句在声明时执行, 而条件子句则是在运行时执行。
所以代码:
array = [1, 3, 5]
g = (x for x in array if array.count(x) > 0)
array = [5, 7, 9]
复制代码
等价于:
g = (x for x in [1,3,5] if [5,7,9].count(x) > 0)
复制代码
正在陆续汇总更多Python使用之坑 ...
字符串无所不在,字符串的处理也是最常见的操作。本章节将总结和字符串处理相关的一切操作。主要包括基本的字符串操作;高级字符串操作之正则。目前共有16
个小例子
1 反转字符串
st="python"
#方法1
''.join(reversed(st))
#方法2
st[::-1]
复制代码
2 字符串切片操作
字符串切片操作——查找替换3或5的倍数
In [1]:[str("java"[i%3*4:]+"python"[i%5*6:] or i) for i in range(1,15)]
OUT[1]:['1',
'2',
'java',
'4',
'python',
'java',
'7',
'8',
'java',
'python',
'11',
'java',
'13',
'14']
复制代码
3 join串联字符串
In [4]: mystr = ['1',
...: '2',
...: 'java',
...: '4',
...: 'python',
...: 'java',
...: '7',
...: '8',
...: 'java',
...: 'python',
...: '11',
...: 'java',
...: '13',
...: '14']
In [5]: ','.join(mystr) #用逗号连接字符串
Out[5]: '1,2,java,4,python,java,7,8,java,python,11,java,13,14'
复制代码
4 字符串的字节长度
def str_byte_len(mystr):
return (len(mystr.encode('utf-8')))
str_byte_len('i love python') # 13(个字节)
str_byte_len('字符') # 6(个字节)
复制代码
以下是正则部分
import re
复制代码
1 查找第一个匹配串
s = 'i love python very much'
pat = 'python'
r = re.search(pat,s)
print(r.span()) #(7,13)
复制代码
2 查找所有1的索引
s = '山东省潍坊市青州第1中学高三1班'
pat = '1'
r = re.finditer(pat,s)
for i in r:
print(i)
#
#
复制代码
3 \d 匹配数字[0-9]
findall找出全部位置的所有匹配
s = '一共20行代码运行时间13.59s'
pat = r'\d+' # +表示匹配数字(\d表示数字的通用字符)1次或多次
r = re.findall(pat,s)
print(r)
# ['20', '13', '59']
复制代码
4 匹配浮点数
?表示前一个字符匹配0或1次
s = '一共20行代码运行时间13.59s'
pat = r'\d+\.?\d+' # ?表示匹配小数点(\.)0次或1次
r = re.findall(pat,s)
print(r)
# ['20', '13.59']
复制代码
5 ^匹配字符串的开头
s = 'This module provides regular expression matching operations similar to those found in Perl'
pat = r'^[emrt]' # 查找以字符e,m,r或t开始的字符串
r = re.findall(pat,s)
print(r)
# [],因为字符串的开头是字符`T`,不在emrt匹配范围内,所以返回为空
IN [11]: s2 = 'email for me is [email protected]'
re.findall('^[emrt].*',s2)# 匹配以e,m,r,t开始的字符串,后面是多个任意字符
Out[11]: ['email for me is [email protected]']
复制代码
6 re.I 忽略大小写
s = 'That'
pat = r't'
r = re.findall(pat,s,re.I)
In [22]: r
Out[22]: ['T', 't']
复制代码
7 理解compile的作用
如果要做很多次匹配,可以先编译匹配串:
import re
pat = re.compile('\W+') # \W 匹配不是数字和字母的字符
has_special_chars = pat.search('ed#2@edc')
if has_special_chars:
print(f'str contains special characters:{has_special_chars.group(0)}')
###输出结果:
# str contains special characters:#
### 再次使用pat正则编译对象 做匹配
again_pattern = pat.findall('[email protected]')
if '@' in again_pattern:
print('possibly it is an email')
复制代码
8 使用()捕获单词,不想带空格
使用()
捕获
s = 'This module provides regular expression matching operations similar to those found in Perl'
pat = r'\s([a-zA-Z]+)'
r = re.findall(pat,s)
print(r) #['module', 'provides', 'regular', 'expression', 'matching', 'operations', 'similar', 'to', 'those', 'found', 'in', 'Perl']
复制代码
看到提取单词中未包括第一个单词,使用?
表示前面字符出现0次或1次,但是此字符还有表示贪心或非贪心匹配含义,使用时要谨慎。
s = 'This module provides regular expression matching operations similar to those found in Perl'
pat = r'\s?([a-zA-Z]+)'
r = re.findall(pat,s)
print(r) #['This', 'module', 'provides', 'regular', 'expression', 'matching', 'operations', 'similar', 'to', 'those', 'found', 'in', 'Perl']
复制代码
9 split分割单词
使用以上方法分割单词不是简洁的,仅仅是为了演示。分割单词最简单还是使用split
函数。
s = 'This module provides regular expression matching operations similar to those found in Perl'
pat = r'\s+'
r = re.split(pat,s)
print(r) # ['This', 'module', 'provides', 'regular', 'expression', 'matching', 'operations', 'similar', 'to', 'those', 'found', 'in', 'Perl']
### 上面这句话也可直接使用str自带的split函数:
s.split(' ') #使用空格分隔
### 但是,对于风格符更加复杂的情况,split无能为力,只能使用正则
s = 'This,,, module ; \t provides|| regular ; '
words = re.split('[,\s;|]+',s) #这样分隔出来,最后会有一个空字符串
words = [i for i in words if len(i)>0]
复制代码
10 match从字符串开始位置匹配
注意match
,search
等的不同:
import re
### match
mystr = 'This'
pat = re.compile('hi')
pat.match(mystr) # None
pat.match(mystr,1) # 从位置1处开始匹配
Out[90]:
复制代码
In [91]: mystr = 'This'
...: pat = re.compile('hi')
...: pat.search(mystr)
Out[91]:
复制代码
11 替换匹配的子串
sub
函数实现对匹配子串的替换
content="hello 12345, hello 456321"
pat=re.compile(r'\d+') #要替换的部分
m=pat.sub("666",content)
print(m) # hello 666, hello 666
复制代码
12 贪心捕获
(.*)表示捕获任意多个字符,尽可能多的匹配字符
content='ddedadsad graphbbmathcc'
pat=re.compile(r"(.*)") #贪婪模式
m=pat.findall(content)
print(m) #匹配结果为: ['graph
13 非贪心捕获
仅添加一个问号(?
),得到结果完全不同,这是非贪心匹配,通过这个例子体会贪心和非贪心的匹配的不同。
content='ddedadsad graphbbmathcc'
pat=re.compile(r"(.*?)")
m=pat.findall(content)
print(m) # ['graph', 'math']
复制代码
非贪心捕获,见好就收。
14 常用元字符总结
. 匹配任意字符
^ 匹配字符串开始位置
$ 匹配字符串中结束的位置
* 前面的原子重复0次、1次、多次
? 前面的原子重复0次或者1次
+ 前面的原子重复1次或多次
{n} 前面的原子出现了 n 次
{n,} 前面的原子至少出现 n 次
{n,m} 前面的原子出现次数介于 n-m 之间
( ) 分组,需要输出的部分
复制代码
15 常用通用字符总结
\s 匹配空白字符
\w 匹配任意字母/数字/下划线
\W 和小写 w 相反,匹配任意字母/数字/下划线以外的字符
\d 匹配十进制数字
\D 匹配除了十进制数以外的值
[0-9] 匹配一个0-9之间的数字
[a-z] 匹配小写英文字母
[A-Z] 匹配大写英文字母
复制代码
14 密码安全检查
密码安全要求:1)要求密码为6到20位; 2)密码只包含英文字母和数字
pat = re.compile(r'\w{6,20}') # 这是错误的,因为\w通配符匹配的是字母,数字和下划线,题目要求不能含有下划线
# 使用最稳的方法:\da-zA-Z满足`密码只包含英文字母和数字`
pat = re.compile(r'[\da-zA-Z]{6,20}')
复制代码
选用最保险的fullmatch
方法,查看是否整个字符串都匹配:
pat.fullmatch('qaz12') # 返回 None, 长度小于6
pat.fullmatch('qaz12wsxedcrfvtgb67890942234343434') # None 长度大于22
pat.fullmatch('qaz_231') # None 含有下划线
pat.fullmatch('n0passw0Rd')
Out[4]:
复制代码
15 爬取百度首页标题
import re
from urllib import request
#爬虫爬取百度首页内容
data=request.urlopen("http://www.baidu.com/").read().decode()
#分析网页,确定正则表达式
pat=r'(.*?) '
result=re.search(pat,data)
print(result)
result.group() # 百度一下,你就知道
复制代码
16 批量转化为驼峰格式(Camel)
数据库字段名批量转化为驼峰格式
分析过程
# 用到的正则串讲解
# \s 指匹配: [ \t\n\r\f\v]
# A|B:表示匹配A串或B串
# re.sub(pattern, newchar, string):
# substitue代替,用newchar字符替代与pattern匹配的字符所有.
复制代码
# title(): 转化为大写,例子:
# 'Hello world'.title() # 'Hello World'
复制代码
# print(re.sub(r"\s|_|", "", "He llo_worl\td"))
s = re.sub(r"(\s|_|-)+", " ",
'some_database_field_name').title().replace(" ", "")
#结果: SomeDatabaseFieldName
复制代码
# 可以看到此时的第一个字符为大写,需要转化为小写
s = s[0].lower()+s[1:] # 最终结果
复制代码
整理以上分析得到如下代码:
import re
def camel(s):
s = re.sub(r"(\s|_|-)+", " ", s).title().replace(" ", "")
return s[0].lower() + s[1:]
# 批量转化
def batch_camel(slist):
return [camel(s) for s in slist]
复制代码
测试结果:
s = batch_camel(['student_id', 'student\tname', 'student-add'])
print(s)
# 结果
['studentId', 'studentName', 'studentAdd']
作者:程序员聚宝盆
链接:https://juejin.im/post/6844904034613067783
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。