查漏补缺--python小细节拾遗(三)
继续继续:
1.咳咳:(单词时间)
duck(鸭子,鸭子类型/dʌk/)
dick(xx, /dɪk/)
deque(双端队列 /dek/)
每天一读,郎朗上口!
2.内置方法简单介绍(加双下的类函数不需要我们直接去调用,都有另外一种自动触发它的方法)
a.__new__:
我很少写这个方法(根本没写过),但是我们每创建一个类python内部都会自动继承该方法;
实例化对象开辟自己的内存空间就是靠__new__方法;
所以实例化对象时,先执行的是__new__方法,再是__init__方法;
至于其用法,举个单例模式的例子.(其它用处暂时没捅那么深)
1 class Foo: 2 __flag = None 3 4 def __new__(cls, *args, **kwargs): 5 if cls.__flag is None: 6 cls.__flag = object.__new__(cls) 7 return cls.__flag 8 9 def __init__(self): 10 pass 11 12 a = Foo() 13 b = Foo() 14 print(a, b) # <__main__.Foo object at 0x0000016D705D7C08> <__main__.Foo object at 0x0000016D705D7C08>
结果很明显,a,b实例化后均指向了同样的内存空间.
因为a实例化后,__flag已经赋值,不再为None,当b实例化时,不再走if语句,而是直接return刚刚创建的内存空间(a的).
实在写不出来或不理解,就背下来吧,唉,我就是背的......一堆面试官要问的,写不出来真的丢人.
b.__init__:
这个是常用的,一般叫初始化方法,在__new__方法创建内存空间后,将实例化对象属性上传至内存空间,通过self绑定即可.
c.__iter__和__next__:
和迭代相关,之前说过,再说一遍.
可迭代对象必须实现__iter__,但不能实现__next__方法.(可迭代对象一定不能是自身的迭代器)
迭代器应该可以一直迭代(实现了无参数的__next__方法),其__iter__方法应该返回自身.
d.__len__/__eq__/__call__:
__len__和__eq__分别对应len()和"==",可以实现任意你想要的结果.
1 class Foo(object): 2 def __init__(self, a, b): 3 self.a = a 4 self.b = b 5 6 def __len__(self): 7 return self.a + self.b 8 9 def __eq__(self, other): 10 return True 11 12 c = Foo(1,2) 13 print(len(c)) # 3 14 print(c) # <__main__.Foo object at 0x0000018A0EFB7748> 15 print(c == 'c') # True
__call__: 可调用方法,在类中添加该方法,可以使实例化对象像函数一样调用.
1 class TmpTest: 2 def __init__(self, x, y): 3 self.x = x 4 self.y = y 5 6 def __call__(self, x, y): 7 self.x, self.y = x, y 8 return self.x, self.y 9 10 a = TmpTest(1, 2) 11 c, d = a(4, 5) # 像函数一样调用 12 print(c, d, a.x, a.y) # 4 5 4 5
e.__del__:
析构方法,在删除这个类创建的对象的时候会先触发这个方法,在删除对象时做一些清理工作,比如说关闭文件,关闭网络的链接,数据库的链接等等;
f.__hash__:
对应hash函数,原始hash函数是利用hash算法将一个对象转化为一个数字.
其特点为:
在一次运算中,相同值运算后的数字总是一样的,不同值运算后数字总是不同的.
注: 如果一个自定义的__eq__方法依赖的类处于可变的状态,那么就不要在类中去实现__hash__方法;
官网机翻:
如果一个对象的哈希值在其生命周期中从未改变(它需要一个__hash__()方法),并且可以与其他对象进行比较(它需要一个__eq()__方法),
那么这个对象就是hashable的(可哈希的).可哈希对象判断相等时必须具有相同的哈希值.
字典键和集合成员必须具有可hash性,因为这些数据结构在内部使用散列值.
大多数Python的不可变内置对象是可hash的;
可变容器(如列表或字典)则不是;
不可变容器(例如元组和固定集合)只有在它们的元素是可哈希的情况下才是可哈希的.
默认情况下,作为用户定义类实例的对象是可hash的.它们都是不相等的(除了它们自己),而且它们的散列值是从它们的id()派生出来的.
g.__str__和__repr__:
__str__:
给终端用户看的;
print()或%s格式化或str强转数据类型时触发;
__repr__:
方便调试和记录日志的.
和__str__同时存在时,只执行__str__,没有__str__,执行__repr__;
repr(obj)和%r格式化时触发__repr__,条件触发时不用管__str__;
h.还有很多魔术方法,喜欢将类写的花里胡哨的可以多了解下.
这里需要说明的是,除了__init__方法我们经常使用外,通常我们的代码不需要直接使用这些魔术方法.
通过内置方法(str,len,iter等等)来使用特殊方法是最好的选择,对于内置的类来说,其速度也更快.
3.json模块和pickle模块
均可用于本地存储和网络传输.
python化(序列化): 相比于pickle将万物转化为byte类型,json只能支持部分python数据类型转化为json数据,且对数据格式要求严格;
反python化(反序列化): 这里json有点优势,因为几乎所有语言都支持json数据,而只有python接受byte类型;
1 import json 2 ex_dict = {'a': 'abc', '1': '111', 1: ('a', 'b', 'c')} 3 json_ex = json.dumps(ex_dict) 4 print(98, json_ex) # 98 {"a": "abc", "1": "111", "1": ["a", "b", "c"]} <class 'str'> 数字转成了字符串,元组转成了列表 5 json_lat = json.loads(json_ex) 6 print(100, json_ex) # 100 {"a": "abc", "1": "111", "1": ["a", "b", "c"]} <class 'dict'> 回转时无法恢复 7 import pickle 8 ex_dict = {'a': 'abc', '1': '111', 1: ('a', 'b', 'c')} 9 pickle_ex = pickle.dumps(ex_dict) 10 print(104, pickle_ex, type(pickle_ex)) # 104 b'\x80\x03}q\...' <class 'bytes'> 全部转成了字节串 11 pickle_lat = pickle.loads(pickle_ex) 12 print(106, pickle_lat) # 106 {'a': 'abc', '1': '111', 1: ('a', 'b', 'c')} <class 'dict'> 回转时完美恢复
个人记忆方法:loads,load,dumps,dump
关于第三人称单数: dumps和loads,所以有s的处理单个数据,而无s的处理批量数据;
同时,load有装入/载入的含义(打开网页时的loading...),dump有倒出的含义,因为我们是搞python的,所以重心自然是python.
从python倒出(dumps)的结果自然是其它数据类型(json数据),而装入(loads)的结果必然是python数据类型;
所以: json.dumps: python数据类型 ----> json数据
json.loads: json数据 ----> python数据类型
4.collections模块
1.双端队列: from collections import deque
以双向链表的形式实现
不同于queue.Queue,其两端均可插入数据;
且相比于列表头部插入(移除)元素(时间复杂度O(n)),其头部尾部插入(移除)元素时间复杂度均为O(1).
1 from collections import deque 2 a = deque() 3 a.append(5) # a.append(5) 4 a.appendleft(6) # deque([6, 5]),头插 5 a.append(7) # deque([6, 5, 7]),默认尾插 6 a.extend([1, 2]) # deque([6, 5, 7, 1, 2]) 7 a.extendleft([3, 5, 4]) # deque([4, 5, 3, 6, 5, 7, 1, 2]) 8 a.pop() # deque([4, 5, 3, 6, 5, 7, 1]),这里可以接收返回值 9 a.popleft() # deque([5, 3, 6, 5, 7, 1]) 10 print(a.count(5)) # 2 11 a.rotate(1) # deque([1, 5, 3, 6, 5, 7]),右轮转 12 a.rotate(-2) # deque([3, 6, 5, 7, 1, 5]),左轮转 13 a.reverse() # deque([5, 1, 7, 5, 6, 3]),翻转 14 a.remove(5) # deque([1, 7, 5, 6, 3]),从头部开始移除第一个遇到的元素 15 a.insert(1, 5) # deque([1, 5, 7, 5, 6, 3]),中插
2.具名元组: from collections import namedtuple
流畅的python介绍:
collections.namedtuple是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类.
用namedtuple构建的类的实例所消耗的内存和元组是一样的,因为字段名都被存在对应的类里面.
这个实例跟普通的对象实例比起来要小一些,因为python不会用__dict__来存放这些实例的属性.
是一个只有属性没有方法的类,且属性不能更改(元组特性).
namedtuple参数为: 类名(具名元组名), 多个字符串组成的可迭代对象 或者 多个字段 + 空格组成的字符串.
1 from collections import namedtuple 2 3 nametuple_test = namedtuple('Struct_time', 'year month day') 4 ss = nametuple_test(2020, 6, 13) # 根据指定字段传入对应的值 5 print(ss) # Struct_time(year=2020, month=6, day=13) 6 print(ss.year, ss[0]) # 2020 2020 可以通过字段名或者位置来获取一个字段的信息
3.计数器Counter: from collections import Counter
专门针对可哈希序列的各种计数.
1 from collections import Counter 2 c = Counter('abcdeabcdabcaba') 3 print(c) # Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1} 4 print(c.most_common(3)) # [('a', 5), ('b', 4), ('c', 3)] 计算3个出现次数最多的元素 5 print(sorted(c)) # ['a', 'b', 'c', 'd', 'e'] 列出排序后元素,以列表返回 6 print(c) # Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1} 7 print(''.join(sorted(c.elements()))) # 列出所有重复元素,并排列 8 print(sum(c.values())) # 15 总和 9 print(c['a']) # 5 'a'计数 10 # 增加数据,方法一 11 for elem in 'shazam': 12 c[elem] += 1 13 print(c) # Counter({'a': 7, 'b': 4, 'c': 3, 'd': 2, 'e': 1, 's': 1, 'h': 1, 'z': 1, 'm': 1}) 14 print(c['a']) # 7 15 # 增加数据,方法二 16 d = Counter('simsalabim') 17 c.update(d) 18 print(c) # Counter({'a': 9, 'c': 3, 's': 3, 'm': 3, 'd': 2, 'i': 2, 'e': 1, 'h': 1, 'z': 1, 'l': 1, 'b': 1}) 19 print(c['a']) # 9 20 c.clear() # 清空 21 print(c) # Counter() 22 # 如果一个计数被设置为零或减少为零,它将保持在计数器,直到条目被删除或计数器被清除 23 c = Counter('aaabbc') 24 c['b'] -= 2 # 减少次数 25 print(c.most_common()) # [('a', 3), ('c', 1), ('b', 0)] 'b'会一直在里面,但是计数为o 26 del c['b'] # 移除所有的'b'元素 27 print(c['b']) # 0 28 print(c) # Counter({'a': 3, 'c': 1}) 彻底删除'b'
4.defaultdict和OrderedDict:
from collections import defaultdict
from collections import OrderedDict
defaultdict: 这个功能很有实际用途.
OrderedDict: 有序字典(python3.6开始,所有字典均为有序,这玩意估计直接合并了)
1 from collections import defaultdict 2 my_dict = {} 3 key = 'test' 4 new_value = 'test_value' 5 if key not in my_dict: 6 my_dict[key] = [] 7 my_dict[key].append(new_value) # {'test': ['test_value']} 8 # 用setdefault设定默认值 9 my_dict = {} 10 my_dict.setdefault(key, []).append(new_value) 11 print(my_dict) # {'test': ['test_value']} 12 # 用defaultdict生成默认值 13 my_dict = defaultdict(list) 14 my_dict[key] = new_value # 调用list()创建新列表,并生成键值对放入字典 15 print(my_dict) # defaultdict(<class 'list'>, {'test': 'test_value'})
5.collections还有ChainMap,UserDict等等,比较高端,以后有时间再观察观察(随缘).
5.正则模块
30min入门教程: https://deerchao.cn/tutorials/regex/regex.htm(30天估计能掌握一半,正则又深又坑,不建议投入太多精力)
a.re.search和re.match
re.search根据正则规则匹配整个字符串,并分组返回符合条件的值,结果用group函数显示;
re.match从开头开始按正则规则进行匹配,其它和search相同,相当于正则规则自带'^';
b.分组命名和分组约束
正则表达式:
<(?P<tag>.*?)>.*?</(?P=tag)> # 给需要匹配的部分命名标签tag,后面亦是,保证前后匹配到的是一致的
说明: *: (?P<组名>正则表达式) 表示给这个组起一个名字;(能单独使用)
*: (?P=组名) 表示引用之前组的名字,引用部分匹配到的内容必须和之前那个组的内容一模一样;(必须配合上面使用)
1 import re 2 pattern = '<(?P<tag>.*?)>.*?</(?P=tag)>' # 等同于'<(.*?)>.*?</\\1>' 用\1表示复制前面分组内容,同样要求前后匹配一致 3 ret1 = re.search(pattern, '<h1>函数</h1>') 4 ret2 = re.search(pattern, '<h1>函数</h2>') 5 print(ret1) # <re.Match object; span=(0, 11), match='<h1>函数</h1>'> 6 print(ret2) # None 7 if ret1: 8 print(ret1.group()) # <h1>函数</h1> 9 print(ret1.group(0)) # <h1>函数</h1> 10 print(ret1.group(1)) # h1 11 print(ret1.group('tag')) # h1
End!

浙公网安备 33010602011771号