diff --git a/1.数据结构与算法/1.序列分解.py b/1.数据结构与算法/1.序列分解.py new file mode 100644 index 0000000..e5688f4 --- /dev/null +++ b/1.数据结构与算法/1.序列分解.py @@ -0,0 +1,14 @@ + +# 普通的序列分解方式 +def normal_func(list): + # list是可迭代对象,能够直接分解成多个元素 + length = list.__len__() + length= 2 + x, y = list + + # 如果list长度和接数据的元素数量不匹配,会造成错误 + x, y, z = list + + # 在知道元素顺序的情况下想要丢弃list中的某些元素,可以用下划线来无视变量 + _ ,y = list + \ No newline at end of file diff --git a/1.数据结构与算法/10.移除重复元素保留顺序.py b/1.数据结构与算法/10.移除重复元素保留顺序.py new file mode 100644 index 0000000..d95e7b5 --- /dev/null +++ b/1.数据结构与算法/10.移除重复元素保留顺序.py @@ -0,0 +1,25 @@ +# 如果序列中存储的元素是可哈希的,那就可以使用集合与生成器解决 + +def dequpe(items): + seen = set() + for item in items: + if item not in seen: + yield item + seen.add(item) + +# 如果序列里的元素是不可哈希的,比如字典这种东西,就要先转化为可哈希对象; +# 这里自定义的key函数就起到了给字典解包的作用 + +def dequpe(items, key): + seen = set() + for item in items: + val = item if key is None else key(item) + if val not in seen: + yield item + seen.add(val) + +# 如果单纯的做不改变顺序的去重,直接转集合再转列表就行了 + +a = [1,5,2,1,9,1,5,10] +a = list(set(a)) +print(a) diff --git a/1.数据结构与算法/11.切片命名.py b/1.数据结构与算法/11.切片命名.py new file mode 100644 index 0000000..159e852 --- /dev/null +++ b/1.数据结构与算法/11.切片命名.py @@ -0,0 +1,18 @@ +items = [0,1,2,3,4,5,6] + +# 我们可以使用内置的slice函数对切片进行命名,参数分别为start|stop|step +a = slice(2,6,2) + +print(items[a]) +print(a.start, a.stop, a.step) + +# indeces方法可以帮我们把start和stop自动限制在序列长度内,并返回一个限制后的切片 +s = "sukahelloworld" +# 上面那个可能看着不明显,但是下面这个看着就明显多了 +# s = "suka" + +print(a.indices(len(s))) + +# 因为返回的是元组,所以要使用得先解包 +for i in range(*a.indices(len(s))): + print(s[i]) diff --git a/1.数据结构与算法/12.找出现最多元素.py b/1.数据结构与算法/12.找出现最多元素.py new file mode 100644 index 0000000..470bada --- /dev/null +++ b/1.数据结构与算法/12.找出现最多元素.py @@ -0,0 +1,24 @@ +words = ["look", "into", "my", "eyes", "look", "into", "my", "eyes", + "the", "eyes", "the", "eyes", "the", "eyes", "not", "around", "the", + "eyes", "don't", "look", "around", "the", "eyes", "look", "into", + "my", "eyes", "you're", "under"] + +# collect里面的Counter库可以帮我们计数 +from collections import Counter + +word_counts = Counter(words) +top_three = word_counts.most_common(3) +print(top_three) + +# Counter的底层是一个字典,是一个DefaultDict,这里处于学习目的把它实现一遍 +from collections import defaultdict + +def counter(word_list): + counter = defaultdict(int) + for word in word_list: + counter[word] += 1 + + return counter + +print(counter(words)) + \ No newline at end of file diff --git a/1.数据结构与算法/13.字典公共键排序.py b/1.数据结构与算法/13.字典公共键排序.py new file mode 100644 index 0000000..7029201 --- /dev/null +++ b/1.数据结构与算法/13.字典公共键排序.py @@ -0,0 +1,25 @@ +# 我们有一个字典列表,想要按照字典里的某些键对列表进行排序 +# 字典列表如下: +rows = [ + {"fname":"Brian", "lname": "Jones", "uid":1003}, + {"fname":"David", "lname": "Beazley", "uid":1002}, + {"fname":"John", "lname": "Cleese", "uid":1001}, + {"fname":"Big", "lname": "Jones", "uid":1004} +] + +# 这时候用到itemgetter模块 +from operator import itemgetter + +# itemgetter的主要功能是通过可查询的标记进行数据获取; +# 下面分别根据fname和uid进行数据获取 + +rows_by_fname = sorted(rows, key=itemgetter("fname")) +rows_by_uid = sorted(rows, key=itemgetter("uid")) + +print(rows_by_fname) +print(rows_by_uid) + +# 当然,itemgetter也能被lambda函数替代,替代操作如下: + +rows_by_fname_v2 = sorted(rows, key= lambda row: row["fname"]) +print(rows_by_fname_v2) diff --git a/1.数据结构与算法/14.原生不支持比较操作的对象排序.py b/1.数据结构与算法/14.原生不支持比较操作的对象排序.py new file mode 100644 index 0000000..e81ea5b --- /dev/null +++ b/1.数据结构与算法/14.原生不支持比较操作的对象排序.py @@ -0,0 +1,32 @@ +# 众所周知,对class这种原生不支持比较的对象直接进行比较会造成暴毙; +# 我们但如果非比不可呢?比如这个东西: +class User: + def __init__(self,user_id): + self.user_id = user_id + + def __repr__(self): + return "User({})".format(self.user_id) + +# 这里有一个列表user +user = [User(23), User(3), User(99)] + +# 如果这时候需要按照某个属性给这个列表排序,那么直接sorted会报错: +try: + sorted(user) + pass +except: + print("你看吧,报错了") + +# 这里的原因是对象不能直接进行比较,所以想要排序只能按照对象里的某个属性 +# 有两种解决方案: +sorted_list = sorted(user, key=lambda x: x.user_id) +print(sorted_list) + +# 要么就用operator库里的attrgetter +from operator import attrgetter +sorted_list = sorted(user, key=attrgetter("user_id")) +print(sorted_list) + +# 用这东西的好处和针对字典的itemgetter一样,可以取多个字段 +# sorted_list = sorted(user, key=attrgetter("user_id", "word_2", "word_3", ...)) +# 同样的,max和min这种有key参数的内建函数都能用这些东西折腾 diff --git a/1.数据结构与算法/15.根据字段分组.py b/1.数据结构与算法/15.根据字段分组.py new file mode 100644 index 0000000..cb69974 --- /dev/null +++ b/1.数据结构与算法/15.根据字段分组.py @@ -0,0 +1,39 @@ +# 假如我们有一个记录需要进行分组 + +rows = [ + {"address": "5412 N CLARK", "date": "07/01/2012"}, + {"address": "5148 N CLARK", "date": "07/04/2012"}, + {"address": "5800 E 58TH", "date": "07/02/2012"}, + {"address": "2122 N CLARK", "date": "07/03/2012"}, + {"address": "5645 N RAVENSWOOD", "date": "07/02/2012"}, + {"address": "1060 W ADDISON", "date": "07/02/2012"}, + {"address": "4801 N BROADWAY", "date": "07/01/2012"}, + {"address": "1039 W GRANVILLE", "date": "07/04/2012"}, +] + +# 如果我们想按日期进行分组,那itertools里的groupby会很好用 +from operator import itemgetter +from itertools import groupby + +# 因为groupby只能检查连续的项,所以我们先对列表进行排序 +rows.sort(key=itemgetter("date")) +print(rows) + +# 然后我们进行分组操作: +for date, items in groupby(rows, itemgetter("date")): + print(date) + for i in items: + print(' ', i) +# groupby每次返回的是一个值(分组名称)和一个子迭代器(组内数据) + + +# 当然如果是单纯的分组,一键多值字典是个好东西 +from collections import defaultdict +data = defaultdict(list) +for item in rows: + data[item["date"]].append(item) + +print(data) + +# 在不考虑内存开销的情况下,这东西比先排序再groupby快 + diff --git a/1.数据结构与算法/16.元素筛选.py b/1.数据结构与算法/16.元素筛选.py new file mode 100644 index 0000000..9adf275 --- /dev/null +++ b/1.数据结构与算法/16.元素筛选.py @@ -0,0 +1,71 @@ +# 比如我有一个列表 +mylist = [1,4,-5,10,-7,2,3,-1] + +# 最脑瘫的方法就是用列表推导式重新生成一遍 +filted_list_b0 = [x for x in mylist if x > 0] +filted_list_s0 = [x for x in mylist if x < 0] +# print(filted_list_b0) + + + +# 当然如果列表很大,这样做会裂开,因为列表推导式本质上是遍历,会建立一份mylist的复制 +# 这个时候我们可以用生成器的方式表达解析结果以节省内存 +filted_list_b0 = (x for x in mylist if x > 0) +# for i in filted_list_b0: +# print(i) + + + +# 当然有的列表比较抽象: +mylist_suka = ['1', '2', '-3', '-', '4', 'N\A', '5'] + +# 这个时候我们可以先写逻辑进行过滤: +def drop_suka(val): + try: + x = int(val) + return True + except ValueError: + return False + + +# 然后我们用内建的filter函数进行过滤, +# 第一个位置是一个值判断布尔函数,第二个参数是要过滤的列表,布尔函数里写过滤逻辑; +# filter返回一个迭代器,所以可以直接用list方法转成列表 +my_nonsuka_list = list(filter(drop_suka, mylist_suka)) +# print(my_nonsuka_list) + + + +# 列表推导式和生成器推导式都可以在值的位置进行: +# 1.计算 x*2 +# 2.三元判断 x*2 if x < 1 else x +filted_list_b0 = [x*2 if x < 1 else x for x in mylist if x > 0] +filted_list_b0 = (x*2 if x < 1 else x for x in mylist if x > 0) + + + +# 如果要筛选的元素在另一个列表里怎么办?比如我们有: +rows = [ + "5412 N CLARK", + "5148 N CLARK", + "5800 E 58TH", + "2122 N CLARK", + "5645 N RAVENSWOOD", + "1060 W ADDISON", + "4801 N BROADWAY", + "1039 W GRANVILLE", +] + +counts = [0,3,10,4,1,7,6,1] + +# counts表示rows里面对应元素出现的次数,如果我们想找count>5的元素咋整? +# 1.搞一个布尔列表,告诉程序谁才是我们要的 +wanted = [n > 5 for n in counts] # 这和[n for n in counts if n > 5]不一样,之前是在for的同时筛选,现在是for完看看是否符合n>5 +# print(wanted) +# 2.用itertools的compress函数来进行对比 +from itertools import compress + +# itertools里的compress函数输入一个列表和一个布尔列表,返回一个迭代器,里面是列表中同样位置在布尔列表里为True的元素 +result = list(compress(rows, wanted)) +print(result) + diff --git a/1.数据结构与算法/17.字典子集.py b/1.数据结构与算法/17.字典子集.py new file mode 100644 index 0000000..9851100 --- /dev/null +++ b/1.数据结构与算法/17.字典子集.py @@ -0,0 +1,19 @@ +# 假如我们有一个字典,需要提取子集: +price = { + "ACME": 45.23, + "AAPL": 612.78, + "IBM": 205.55, + "HPQ": 37.2, + "FB": 10.75 +} + +# 直接用字典推导式 +p1 = {key :value for key, value in price.items() if value > 200} + +# 提取key也是 +keys = {"AAPL", "IBM", "HPQ", "MSFT"} +p2 = {key : value for key, value in price.items() if key in keys} + +# 字典推导式是最快的方法,比下面两个办法都快: +dict((key, value) for key, value in price if value > 100) # 快两倍 +{key:price[key] for key in price.keys() if key in price.keys() & keys} # 快1.6倍 diff --git a/1.数据结构与算法/18.将名称映射到序列元素.py b/1.数据结构与算法/18.将名称映射到序列元素.py new file mode 100644 index 0000000..5db9a56 --- /dev/null +++ b/1.数据结构与算法/18.将名称映射到序列元素.py @@ -0,0 +1,37 @@ +# collection库中有一个东西叫做namedtuple,有点像结构体,可以给元组中的元素起名 +from collections import namedtuple + +Subscribe = namedtuple('Subscriber', ["addr", "joined"]) +sub = Subscribe("suka@qq.com", "2024/07/17") +print(sub) +print(sub.addr) +print(sub.joined) + +# 可以看到,这个东西和对象有点相似,但这种操作又保留了适用于元组的所有操作,比如索引和分解 +print(sub[0]) +print(len(sub)) +addr, joined = sub +print(addr, joined) + +# 如果我们获取的数据总是list类型,不是标准的json数据,那这东西就很有用 +# 比如返回的数据是这种东西: data = [0,1,1,3,2,6] +# 如果是这种非标数据,普通代码处理长这样: +def compute_cost(records): + total = 0.0 + for rec in records: + total += rec[1] * rec[2] + + return total + +# 这造成一个问题,鬼知道rec[1]和rec[2]是什么东西,代码可读性很傻逼; +# 所以我们可以在拿到数据的时候先标准化一下 +Stock = namedtuple("Stock", ['name', 'share', 'price']) +def compute_cost(records): + total = 0.0 + for rec in records: + stock = Stock(*rec) + total += stock.share * stock.price + + return total + +# 当然如果返回的已经是对象了,那就没有做namedtuple的必要了,在不想用类的时候可以用这东西替代字典 \ No newline at end of file diff --git a/1.数据结构与算法/19.同时对数据做转化和换算.py b/1.数据结构与算法/19.同时对数据做转化和换算.py new file mode 100644 index 0000000..a467f80 --- /dev/null +++ b/1.数据结构与算法/19.同时对数据做转化和换算.py @@ -0,0 +1,30 @@ +# 这是一种简化操作,可以将转换和换算合为一体,比如我们有一个列表 +nums = [1,2,3,4,5] + +# 计算平方和我们一般这样子做: +nums_square = [x*x for x in nums] +s = sum(nums_square) + +# 这个时候我们就可以这样来简化一下运算: +s = sum(x*x for x in nums) + + +# 又或者我们要在一堆参数中间加一些分隔符: +# 注意,这里的join和os.path.join不一样,这里是用来给jion输入的列表加分隔符的 +s = ("ACME", 50, 123.45) +print(','.join(str(x) for x in s)) + +# 这里我们将生成器表达式从()里薅出来,当作可迭代参数塞进了函数里,这样避免了列表生成器造成的内存浪费 + +# 对单个字典内,我们用键值反转的方法来找元素,但是如果这是一个字典列表,那情况就大大不同了 +portfolio = [ + {'name': 'GOOG', 'shares': 50}, + {'name': 'YHOO', 'shares': 75}, + {'name': 'AOL', 'shares': 20}, + {'name': 'SCOX', 'shares': 65} +] +max_share = max(portfolio, key=lambda x: x["shares"]) +print(max_share) +# 当然如果仅仅只要拿到最小值就用到了生成器表达式内嵌的方法 +max_share_num = max(s['shares'] for s in portfolio) +print(max_share_num) \ No newline at end of file diff --git a/1.数据结构与算法/2.从任意可迭代对象中分解元素.py b/1.数据结构与算法/2.从任意可迭代对象中分解元素.py new file mode 100644 index 0000000..b4aa9b7 --- /dev/null +++ b/1.数据结构与算法/2.从任意可迭代对象中分解元素.py @@ -0,0 +1,36 @@ + +# 可迭代对象分解 +def iterable_breakdown(list): + # 如果可迭代对象长度大于接数据的变量数量,分解值过多报错 + length = list.__len__() + length = 20 + # 这样会报错,因为list长度是20,但分解变量只有两个 + x, y = list + + # 想要接住任意长度的列表,需要用* + # *x是包含list前n-1个元素的列表,y是最后一个元素 + # 我们可以在任意位置接入带*的参数来接住一些值,这些值会在*变量中以列表存储 + *x, y = list + x, *y, z = list + + # 假设有一个变长元组序列 + record = [("foo",1,2),("bar","suka"),("foo",2,4)] + + def do_foo(x,y): + print(x,y) + + def do_bar(s): + print(s) + + # 我们可以使用*来解包从而将参数薅出来 + for tag, *args in record: + if tag == "foo": + do_foo(*args) + elif tag == "bar": + do_bar(*args) + + # 同理,也可以用*变量来占位挖掉不要的变量,比如在特定位置用*ignore或*_来挖掉参数 + + + + \ No newline at end of file diff --git a/1.数据结构与算法/20.多个映射转换成单个映射.py b/1.数据结构与算法/20.多个映射转换成单个映射.py new file mode 100644 index 0000000..187cf85 --- /dev/null +++ b/1.数据结构与算法/20.多个映射转换成单个映射.py @@ -0,0 +1,57 @@ +# 假如又两个字典: +a = {"x":1, "z":3} +b = {"y":2, "z":4} + +# 怎么将这两个字典映射到一个新字典里呢?愚蠢的办法是使用字典合并 + +# 这个方法会把右边的b刷写到a里 +c = a|b +print(c) +# 但是,你会发现,如果原字典被改了,那c不会有变化: +a["x"] = 2 +print(c) + +# 这个时候使用映射方案的chainmap就会很好用 +from collections import ChainMap +c = ChainMap(a,b) + +print(",".join(str(x) for x in [c['x'], c['y'], c['z']])) +print(len(c)) + +# 如果此时更改原字典的值: +a["x"] = 1 + +# 输出也跟着更改了 +print(",".join(str(x) for x in [c['x'], c['y'], c['z']])) +print(len(c)) + +# 注,chainmap中如果有重复的键,则会采用左值,比如上面的例子使用a["z"]作为重复键z的取值; +# 如果修改字典值,也会作用在左值上 +del c["z"] +print(",".join(str(x) for x in [c['x'], c['y'], c['z']])) +print(a, b) +# 如果尝试删除a里已经没有但b里有的键,那么会报错 +try: + del c["z"] +except: + print("在a里没找到键") + +# 这东西的底层其实是一个链表,演示如下: +dic = ChainMap() +dic["x"] = 1 +dic = dic.new_child() +print(dic) +dic["x"] = 2 +dic = dic.new_child() +print(dic) +dic["x"] = 3 +print(dic) +# 从这里我们开始回溯历史 +dic = dic.parents +print(dic) +dic = dic.parents +print(dic) +dic = dic.parents +print(dic) + +# 可以看到我们用new_child()函数创建了一个新节点;用parents属性来回溯 \ No newline at end of file diff --git a/1.数据结构与算法/3.保存最后N个元素.py b/1.数据结构与算法/3.保存最后N个元素.py new file mode 100644 index 0000000..45482d4 --- /dev/null +++ b/1.数据结构与算法/3.保存最后N个元素.py @@ -0,0 +1,19 @@ +# 队列可以对最后N个元素进行保存 +from collections import deque + +# 从历史记录列表history得到length条最新历史记录 +def latest_history(history, length): + window = deque(maxlen=length) + for record in history: + window.append(record) + + # window引用双头队列组件deque,该组件有方法如下 + # 从队尾加入 + window.append(1) + # 从队尾弹出 + window.pop() + # 从队头加入 + window.appendleft(1) + # 从队头弹出 + window.popleft() + \ No newline at end of file diff --git a/1.数据结构与算法/4.最大N个和最小N个.py b/1.数据结构与算法/4.最大N个和最小N个.py new file mode 100644 index 0000000..794faea --- /dev/null +++ b/1.数据结构与算法/4.最大N个和最小N个.py @@ -0,0 +1,33 @@ +import heapq + +# heapq里有两个函数,nlargest和nsmallest可以找最大N个和最小N个; +# 参数接受一个列表和一个N + +def max_n(search_list, n): + list_max_n = heapq.nlargest(n ,search_list) + return list_max_n + +def min_n(search_list, n): + list_min_n = heapq.nsmallest(n, search_list) + return list_min_n + +# 这两个函数都提供了一个key输入来实现在字典列表中的应用 +def max_in_dictlist(search_list, n, key): + max_key_n = heapq.nlargest(n, search_list, key=lambda dic : dic[key]) + return max_key_n + +# 如果寻找最大N个和最小N个元素,这个方法在N的规模不大的时候好用; +# 当日在N=1的时候还是用max和min方法更快,要是N巨大那就得排序了 + +# heapq事实上是一个堆方法,将列表在底层序列化为一个最小堆 +def heapq_func(): + ori_list = [1,2,3,4,5] + heap = list(ori_list) + # 将列表进行堆排序 + heapq.heapify(heap) + # 弹出堆顶元素 + heapq.heappop(heap) + +if __name__ == '__main__': + l = [1,2,3,4,5] + print(max_n(l, 3)) diff --git a/1.数据结构与算法/5.优先级队列.py b/1.数据结构与算法/5.优先级队列.py new file mode 100644 index 0000000..5f7301e --- /dev/null +++ b/1.数据结构与算法/5.优先级队列.py @@ -0,0 +1,43 @@ +import heapq + + +# 优先级队列(使用小根堆实现) +class PriorityQueue: + def __init__(self): + self._queue = [] + # 由于优先级在比较大小时可能相同造成比较失败,维护一个下标index来进行二级辨识 + self._index = 0 + + # 由于heapq的原理是生成一个小根堆,所以优先级取负,这样优先级越大,堆识别到的优先级越小;引入index,在优先级相同时比较入栈先后顺序 + def push(self, item, priority): + heapq.heappush(self._queue, (-priority, self._index, item)) + self._index += 1 + + # 弹出堆顶值 + def pop(self): + return heapq.heappop(self._queue)[-1] + + +# 在Python中,实例化对象是不可以直接进行比较的,例如: +class Item: + def __init__(self, name): + self.name = name + + def __repr__(self): + + return 'Item {!r}'.format(self.name) +# a = Iter('foo') b = Iter('bar'), 进行a 0: + print("T-minus", n) + n -= 1 + time.sleep(5) + +def countdown2(n): + while n > 0: + print("T-minus", n) + n -= 1 + time.sleep(6) + +from threading import Thread + +t = Thread(target=countdown, args=(10, )) +t.start() +t2 = Thread(target=countdown2, args=(10, )) +t2.start() diff --git a/2.字符串和文本/1.任意多分隔符字符串拆分.py b/2.字符串和文本/1.任意多分隔符字符串拆分.py new file mode 100644 index 0000000..6eb5e84 --- /dev/null +++ b/2.字符串和文本/1.任意多分隔符字符串拆分.py @@ -0,0 +1,23 @@ +# 众所周知,split函数可以针对单个分隔符把字符串拆开 + +a = "suka,caonimade,shabi" +b = a.split(",") +print(b) + +# 但是假如系统可以检测到这种粗鄙之语,那就会让🐎🐎消失 +# 想要保卫自己的🐎,那就要粗鄙的隐晦一点: + +a = "suka,*&&**caonimade,_&^shabi" +# 这时候关键词检测就暴毙了 +b = a.split(",") +print(b) + +# 但是聪明的审核会用re库来解决这个问题: +import re +# re库中的split函数接受多个输入,可以同时干掉多种干扰: +b = re.split(r'[,*&^_]', a) +print(b) + +# 如果要保留分隔符, 记得使用正则闭包 +b = re.split(r'(,|&|^|_)', a) +print(b) \ No newline at end of file diff --git a/2.字符串和文本/10.用正则处理unicode.py b/2.字符串和文本/10.用正则处理unicode.py new file mode 100644 index 0000000..cd19767 --- /dev/null +++ b/2.字符串和文本/10.用正则处理unicode.py @@ -0,0 +1,11 @@ +import re + +if __name__ == '__main__': + # 默认情况下,re模块已经认识了某些unicode字符,比如\d现在已经可以匹配unicode的数字 + num = re.compile(r'\d+') + # match会从第一个字符开始匹配,如果不匹配就返回None + print(num.match('123')) + + print(num.match('\u0661\u0662\u0663')) + + # 我的评价是别这样干,在处理东西之前先将输入标准化为ascii编码是一个程序员的基本素养 diff --git a/2.字符串和文本/11.从字符串中去掉不需要的字符.py b/2.字符串和文本/11.从字符串中去掉不需要的字符.py new file mode 100644 index 0000000..840aa73 --- /dev/null +++ b/2.字符串和文本/11.从字符串中去掉不需要的字符.py @@ -0,0 +1,24 @@ + +if __name__ == '__main__': + # 使用strip方法可以删除不要的字符串,默认是删除空格 + s = ' hello world \n' + print(s.strip()) + + # 同样的,这个方法还有进阶版本,lstrip和rstrip可以从左右开始检测指定符号进行删除 + # 如果左边开头或右边开头没有指定符号,则不会起作用 + s2 = '---hello world===' + print(s2.lstrip('-')) + print(s2.rstrip('=')) + print(s2.strip('-=')) + + # 在这个例子中,hello world中间的空格不会被strip系列函数删除,因为这个方法不会管字符串中间的字符 + # 如果需要删除hello world中间的空格,请使用replace方法 + + # 在一个文件中,我们可以这样进行每一行的信息过滤: + + # with open(file) as f: + # lines = (line.strip() for line in f) + # for line in lines: + # print(line) + + diff --git a/2.字符串和文本/12.文本过滤和清理.py b/2.字符串和文本/12.文本过滤和清理.py new file mode 100644 index 0000000..d8e16b8 --- /dev/null +++ b/2.字符串和文本/12.文本过滤和清理.py @@ -0,0 +1,26 @@ +import sys + +import unicodedata + +if __name__ == '__main__': + # 总有脑瘫喜欢输入一些奇奇怪怪的东西,这个时候我们就需要做一些过滤操作 + s = 'pyth\u0303on\fis\tawesome\r\n' + # 这个字符串看起来就非常恶心了,这时候我们可以用translate方法对一些特殊符号进行迭代: + remap = { + ord('\t'): ' ', + ord('\f'): ' ', + ord('\r'): None, + } + s = s.translate(remap) + print(s) + + # 这个时候我们就过滤掉了各种空格符和回车符 + # 我们也可以构建更大的order_dict去把unicode过滤掉 + b = unicodedata.normalize("NFD", s) + # 对每一个unicode建立一个None的映射, + cmb_dict = dict.fromkeys(c for c in range(sys.maxunicode) if unicodedata.combining(chr(c))) + b = b.translate(cmb_dict) + print(b) + + # 如果只是简单的替换操作,那么replace函数已经很好了,速度也很快; + # 在做字符映射操作的时候可以使用translate方法并构建一个映射字典 \ No newline at end of file diff --git a/2.字符串和文本/13.对齐文本.py b/2.字符串和文本/13.对齐文本.py new file mode 100644 index 0000000..99e4c5e --- /dev/null +++ b/2.字符串和文本/13.对齐文本.py @@ -0,0 +1,22 @@ + + +if __name__ == "__main__": + text = 'Hello World' + # 如果想要对齐字符串,可以使用ljest、rjust和center方法 + print(text.ljust(20)) + print(text.rjust(20)) + print(text.center(20)) + + # 当然,这个轮椅也支持填充操作 + print(text.ljust(20, '=')) + print(text.rjust(20, '-')) + print(text.center(20, '=')) + + # 除了轮椅函数,建议使用format函数,这个东西更加泛用,还能进行格式转换 + print(format(text, '-<20')) # <表示宽度20居左 + print(format(text, '=>20')) # >表示宽度20居右 + print(format(text, '+^20')) # ^表示宽度20居中 + # <>^前面的符号表示使用该符号填充 + # format的好处主要在于,它的处理对象不仅是字符串,可以对任何数据形式进行格式化 + num = 1.23456 + print(format(num, '=^20.2f')) # 这表示宽度20,保留两位小数,居中,使用=填充 diff --git a/2.字符串和文本/14.字符串连接与合并.py b/2.字符串和文本/14.字符串连接与合并.py new file mode 100644 index 0000000..bb3a613 --- /dev/null +++ b/2.字符串和文本/14.字符串连接与合并.py @@ -0,0 +1,15 @@ + + +if __name__ == "__main__": + # 如果有一堆字符串, 那我想join函数是一个好选择, 但如果就几个那用+就可以了 + parts = ["Is", "Chicago", "Not", "Chicago?"] + str_c = ' '.join(parts) + print(str_c) + + # 如果我有密钥的两部分,想把a和b连接在一起,那可以这样 + keyword = 'su' 'ka' + print(keyword) + + # 在实际运用中,如果遇到一堆字符串拼接,千万别用+=,这样会产生大量内存垃圾 + # 如果有大量的片段需要拼接,则应该考虑使用生成器函数(字段确定的情况下) + diff --git a/2.字符串和文本/15.给字符串中的变量做插值处理.py b/2.字符串和文本/15.给字符串中的变量做插值处理.py new file mode 100644 index 0000000..05193a5 --- /dev/null +++ b/2.字符串和文本/15.给字符串中的变量做插值处理.py @@ -0,0 +1,43 @@ +import sys + +if __name__ == "__main__": + # 一般来说,我们用{}和format函数对字符串进行插值 + s = "{name} has {n} messages" + s1 = s.format(name="Sam", n=10) + print(s1) + + # 当然如果你想偷懒,也可以这样: + name = "Sam" + n = 10 + s2 = s.format_map(vars()) + print(s2) + # 在上面这段代码中,vars函数会从堆栈里自动寻找变量名称的值 + # vars还能解析类实例: + class INFO: + def __init__(self, name, n): + self.name = name + self.n = n + info = INFO(name, n) + s3 = s.format_map(vars(info)) + print(s3) + + # 爽吗?确实爽,但是有一个缺点,在填充值缺失的时候这玩意儿会报错 + try: + s4 = s.format(name=name) + except KeyError: + print("少参数") + + # 我们可以定义一个类的__missing__方法来防止出现这种情况 + class Safe_Sub(dict): + def __missing__(self, key): + return '{'+ key +'}' + + del n + print(s.format_map(Safe_Sub(vars()))) + + # 如果经常操作,那么可以把这个功能藏在函数里,同时从当前调用的堆栈里找到变量(我反正不建议这么干,不过得知道) + def sub(text): + # 不!要!在!代码里!碰!tmd!堆栈!这是手贱行为,可能导致各种bug,除非有必要的理由否则不要这么做 + return text.format_map(Safe_Sub(sys._getframe(1).f_locals)) + name = "Suka" + print(sub(s)) diff --git a/2.字符串和文本/16.以固定列数重新格式化文本.py b/2.字符串和文本/16.以固定列数重新格式化文本.py new file mode 100644 index 0000000..6966630 --- /dev/null +++ b/2.字符串和文本/16.以固定列数重新格式化文本.py @@ -0,0 +1,20 @@ +import textwrap +import os + +if __name__ == '__main__': + words = ' '.join(["look", "into", "my", "eyes", "look", "into", "my", "eyes", + "the", "eyes", "the", "eyes", "the", "eyes", "not", "around", "the", + "eyes", "don't", "look", "around", "the", "eyes", "look", "into", + "my", "eyes", "you're", "under"]) + print(words) + # 可以看到这样打印出来的字符串巨长无比,如果想要控制行宽度,就用textwrap模块 + print(textwrap.fill(words, 70)) + print(textwrap.fill(words, 40)) + # 第二个参数width可以控制显示宽度,如果是控制台输出,你还能去找os.get_terminal_size() + # 注意,由于未知原因这在pycharm中不好使(2024.8.27) + t_width = os.get_terminal_size().columns + print(t_width) + print(textwrap.fill(words, t_width)) + + # 用的应该不多,在需要显示长字符串的时候去查textwrap库就行 + diff --git a/2.字符串和文本/17.处理HTML和XML文件.py b/2.字符串和文本/17.处理HTML和XML文件.py new file mode 100644 index 0000000..daa78c9 --- /dev/null +++ b/2.字符串和文本/17.处理HTML和XML文件.py @@ -0,0 +1,4 @@ + +# 这本书的年代有些久远,当时HTML和XML解析器还不是特别完善 + +# 在现在的工具中,想要处理HTML和XML文件,只需要寻找HTML Parser和XML解析库就行 \ No newline at end of file diff --git a/2.字符串和文本/18.文本分词.py b/2.字符串和文本/18.文本分词.py new file mode 100644 index 0000000..8a3843c --- /dev/null +++ b/2.字符串和文本/18.文本分词.py @@ -0,0 +1,37 @@ +import re +from collections import namedtuple + +if __name__ == "__main__": + text = 'foo = 23 + 42 * 10' + # 我们想要将这个字符串解析成这个样子: + # tokens = [('NAME', 'foo'), ('EQ', '='), ('NUMS', '23'), ('PLUS', '+'), + # ('NUMS', '42'), ('TIMES', '*'), ('NUMS', '10')] + # 首先,我们要对每种情况编写一个捕获组 + NAME = r'(?P[a-zA-Z_][a-zA-Z_0-9]*)' + NUM = r'(?P\d+)' + PLUS = r'(?P\+)' + TIMES = r'(?P\*)' + EQ = r'(?P=)' + WS = r'(?P\s+)' + # 把这些捕获组静态编译给re模块 + master_pat = re.compile('|'.join([NAME,NUM,PLUS,TIMES,EQ,WS])) + + # 定义一个数据结构来存储每个部分 + Token = namedtuple('Token', ['type', 'value']) + + def generate_tokens(pat, text): + # 调用re.compile的scanner模块来扫描,并用iter模块将其变成可迭代对象进行迭代 + scanner = pat.scanner(text) + for m in iter(scanner.match, None): + # 生成结构来存储分词 + yield Token(m.lastgroup, m.group()) + + # 对分词类型进行过滤,去掉空格 + tokens = (tok for tok in generate_tokens(master_pat, text) if tok.type != 'WS') + for tok in tokens: + print(tok) + + + # 看起来很简单是吧,唯一的难点就在compile的时候, + # 要把大词放前面先搜完再搜小词,保证较长的模式先匹配;比如<=的顺序就应该比<靠前 + \ No newline at end of file diff --git a/2.字符串和文本/19.递归下降解析器.py b/2.字符串和文本/19.递归下降解析器.py new file mode 100644 index 0000000..8868b06 --- /dev/null +++ b/2.字符串和文本/19.递归下降解析器.py @@ -0,0 +1,3 @@ +""" +我的评价是去看编译原理的文本解析部分 +""" \ No newline at end of file diff --git a/2.字符串和文本/2.字符串首尾文字匹配.py b/2.字符串和文本/2.字符串首尾文字匹配.py new file mode 100644 index 0000000..dd5e270 --- /dev/null +++ b/2.字符串和文本/2.字符串首尾文字匹配.py @@ -0,0 +1,21 @@ +from numpy.core.defchararray import startswith + +if __name__ == '__main__': + str = "https://www.baidu.com" + str2 = "http://www.baidu.cn" + str3 = "suka" + # 如果想要在字符串首进行匹配,可以使用: + is_start_with_https = str.startswith("https") + print(is_start_with_https) + # 这会输出一个布尔值, + ## 比如上面匹配成功,就会输出True + + # 同理,可以设置endswith + is_end_with_dotcom = str.endswith(".com") + print(is_end_with_dotcom) + + # 如果想要匹配多种开头和结尾,可以将函数输入改成元组() + is_url = [url for url in [str, str2, str3] if url.startswith(('http', 'https'))] + print(is_url) + + # 当然,复杂的操作还请使用正则表达式进行匹配,不过简单的检查用这个方法那是又快又好 diff --git a/2.字符串和文本/20.在字节串上执行文本操作.py b/2.字符串和文本/20.在字节串上执行文本操作.py new file mode 100644 index 0000000..c382a3f --- /dev/null +++ b/2.字符串和文本/20.在字节串上执行文本操作.py @@ -0,0 +1,33 @@ +import re + +from pyasn1.codec.ber.decoder import decode + +if __name__ == '__main__': + data = b'Hello World' + # 目前字节串已经支持大部分和字符串相同的操作 + data05 = data[0:5] + print(data05) + + is_startwith_hello = data.startswith(b'Hello') + print(is_startwith_hello) + + data_sp = data.split() + print(data_sp) + + data_suka = data.replace(b'Hello', b'Hello Suka') + print(data_suka) + + # 唯一需要注意的是,如果用re做了匹配,那记得要用字节串的形式来匹配 + data = b'foo:bar,spam' + try: + re.split('[:,]', data) + except: + print("匹配错误,换字节串匹配") + data_bsp = re.split(b'[:,]', data) + print(data_bsp) + + # 字节串上的字符不能直接被识别,需要先用ascii解码 + print(data[0]) + print(data.decode("ascii")[0]) + + # 我的建议是除非万不得已,请不要用字节串处理文本,统一使用ascii解码成字符串后再进行操作 diff --git a/2.字符串和文本/3.shell通配符做字符串匹配.py b/2.字符串和文本/3.shell通配符做字符串匹配.py new file mode 100644 index 0000000..bca6318 --- /dev/null +++ b/2.字符串和文本/3.shell通配符做字符串匹配.py @@ -0,0 +1,26 @@ +from fnmatch import fnmatch, fnmatchcase + +if __name__ == '__main__': + # 如果想用shell通配符做字符串匹配,可以用上面的这两个函数: + is_txt = fnmatch("suka.txt", "*.txt") + print(is_txt) + is_txt = fnmatchcase("suka.txt", "??ka.txt") + print(is_txt) + + # 注意,fnmatchcase不会对输入的name进行大小写标准化,而fnmatch会对输入标准化后再进行匹配 + # 需要小心的是,大小写标准化的模式与底层文件系统相同,比如windows不需要区分大小写,但是mac要 + + address = [ + '5412 N CLARK ST', + '1060 W ADDISON ST', + '1039 W GRANVILLE AVE', + '2122 N CLARK ST', + '4802 N BROADWAY' + ] + + # 可以用正则进行筛选 + ST = [addr for addr in address if fnmatchcase(addr, "* ST")] + print(ST) + NUM_S = [addr for addr in address if fnmatchcase(addr, '54[0-9][0-9] *CLARK*')] + print(NUM_S) + \ No newline at end of file diff --git a/2.字符串和文本/4.使用正则对文本进行匹配和查找.py b/2.字符串和文本/4.使用正则对文本进行匹配和查找.py new file mode 100644 index 0000000..458e530 --- /dev/null +++ b/2.字符串和文本/4.使用正则对文本进行匹配和查找.py @@ -0,0 +1,49 @@ +import re + +if __name__ == '__main__': + # 如果只是简单的文字匹配或者查找,下面三个方法足以解决问题: + url = "http://www.baidu.com" + url.startswith("http") + url.endswith(".com") + url.find("baidu") + + # 但如果是更加复杂的匹配,就要用到re库的正则了 + text1 = '11/27/2012' + text2 = 'Nov 27, 2012' + + if re.match(r'\d+/\d+/\d+', text1): + print("yes") + else: + print("no") + + if re.match(r'\d+/\d+/\d+', text2): + print("yes") + else: + print("no") + + # match可以被一次性消费,但是如果想要多次匹配,就要先把正则编译 + datepat = re.compile(r'\d+/\d+/\d+') + + if datepat.match(text1): + print("yes") + else: + print("no") + + if datepat.match(text2): + print("yes") + else: + print("no") + + # 这里要注意的是,match方法是从头匹配,如果要匹配的内容在一堆垃圾里面,请使用findall + + # 我们还会使用捕获组,这样可以把每个组单独提取出来 + datepat = re.compile(r'(\d+)/(\d+)/(\d+)') + m = datepat.match(text1) + print(m.group(0)) + print(m.group(1)) + print(m.group(2)) + print(m.group(3)) + + # match只能匹配开头,它不管结尾,如果想要精确匹配需要加休止符$ + datepat = re.compile(r'(\d+)/(\d+)/(\d+)$') + \ No newline at end of file diff --git a/2.字符串和文本/5.查找和替换文本.py b/2.字符串和文本/5.查找和替换文本.py new file mode 100644 index 0000000..a0d858b --- /dev/null +++ b/2.字符串和文本/5.查找和替换文本.py @@ -0,0 +1,22 @@ +import re + +if __name__ == "__main__": + text = "yeah, but no, but yeah, but no, but yeah" + + # 简单的替换可以使用replace函数来完成 + text_change = text.replace('yeah', 'yep') + print(text_change) + + # 对复杂的替换,我们可以使用re.sub模块 + text = "Today is 11/27/2012. PyCon starts 3/12/2013" + text_change = re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text) + print(text_change) + + # 如果需要多次替换,记得先编译再sub + datepat = re.compile(r'(\d+)/(\d+)/(\d+)') + print(datepat.sub(r'\3-\1-\2', text)) + + # 想要知道完成了几次替换,可以使用subn + # subn返回一个元组,结构为(替换后的字符串, 替换次数) + print(datepat.subn(r'\3-\1-\2', text)) + diff --git a/2.字符串和文本/6.不区分大小写的对文本进行匹配和区分大小写的替换.py b/2.字符串和文本/6.不区分大小写的对文本进行匹配和区分大小写的替换.py new file mode 100644 index 0000000..f4f79c0 --- /dev/null +++ b/2.字符串和文本/6.不区分大小写的对文本进行匹配和区分大小写的替换.py @@ -0,0 +1,35 @@ +import re + +if __name__ == '__main__': + text = "UPPER PYTHON, lower python, mixed Python" + + # 想要找到所有的python,可以在函数里面找一下有没有flag关键字,并设为re.IGNORECASE + + pythons = re.findall('python', text, flags=re.IGNORECASE) + print(pythons) + + # 但是这样在替换的时候就有问题发生,不能把替换文本的大小写设置成和原文本一样 + python_replace = re.sub('python', 'snake', text, flags=re.IGNORECASE) + print(python_replace) + # 这个时候我们需要一个辅助函数 + def matchcase(word): + def replace(m): + text = m.group() + if text.isupper(): + return word.upper() + elif text.islower(): + return word.lower() + elif text[0].isupper(): + # 这个函数将字符串的首字母大写,其余部分转成小写 + return word.capitalize() + else: + return word + return replace + python_replace_with_func = re.sub('python', matchcase('snake'), text, flags=re.IGNORECASE) + print(python_replace_with_func) + # 这个辅助函数结构陌生,这里记个笔记备注一下防止以后再看忘记: + # 1.首先,sub函数检测到要调用matchcase函数,进入函数体返回replace函数 sub->matchcsae + # 2.此时matchcase函数的输入word仍在堆栈中等待调用 matchcase -replace-> sub + # 3.在替换的时候,sub函数将识别到的python替换成replace(被识别到的部分) text -> (a1,a2,a3,...) + # 4.replace函数返回大小写处理结果 replace(a1), replace(a2), ... + # 5.函数返回被替换字符串,re.sub函数进行替换 将识别到的关键词替换成处理过的word \ No newline at end of file diff --git a/2.字符串和文本/7.定义实现最短匹配的正则表达式.py b/2.字符串和文本/7.定义实现最短匹配的正则表达式.py new file mode 100644 index 0000000..d27ad66 --- /dev/null +++ b/2.字符串和文本/7.定义实现最短匹配的正则表达式.py @@ -0,0 +1,18 @@ +import re + +if __name__ == '__main__': + # 在进行文本匹配的时候,re库使用的是贪心算法,即找最长匹配字符串 + text1 = 'Computer says "no".' + text2 = 'Computer says "yes" and "no".' + # 这样的算法在简单的环境中不会出现问题,但如果遇到闭包匹配(最典型的是双引号)就会出现问题 + said = re.compile(r'\"(.*)\"') + print(said.findall(text1)) + + # 看,这里就输出了距离最长的两个引号中间的内容 + print(said.findall(text2)) + + # 问题就出现在.匹配字符上,默认.*会匹配除了\n以外的所有字符,其中也包括“” + # 解决方案是强制取消正则的贪心算法,进行最短匹配 + # 技术上我们在.*后界面加一个?来强制取消贪心 + said_shot = re.compile(r'\"(.*?)\"') + print(said_shot.findall(text2)) diff --git a/2.字符串和文本/8.编写多行模式的正则表达式.py b/2.字符串和文本/8.编写多行模式的正则表达式.py new file mode 100644 index 0000000..a8b5677 --- /dev/null +++ b/2.字符串和文本/8.编写多行模式的正则表达式.py @@ -0,0 +1,20 @@ +import re + +if __name__ == '__main__': + # 正常情况下,我们会想用.来匹配所有字符串,但这个东西不能匹配换行符 + text1 = '/*This is a comment*/' + text2 = '''/*This is a + multiline comment */''' + + comment = re.compile(r'/\*(.*?)\*/') + print(comment.findall(text1)) + print(comment.findall(text2)) + # 你会发现欸我靠咋匹配不到了,那是因为由于没办法识别\n,第二行被抛弃了 + # 想要识别出\n,需要使用(?:.|\n)指定一个非捕获组,意思是在识别到/n的时候只做匹配但不触发捕获退出 + comment_pro = re.compile(r'\*((?:.|\n)*?)\*/') + print(comment_pro.findall(text2)) + + # 对于这种简单的情况,可以在compile函数中加入一个参数re.DOTALL来让.匹配包括\n在内的所有字符串 + # PS:复杂情况请另请高明 + comment_pro_se = re.compile(r'\*(.*?)\*/', re.DOTALL) + print(comment_pro_se.findall(text2)) diff --git a/2.字符串和文本/9.将unicode文本进行统一规范表示.py b/2.字符串和文本/9.将unicode文本进行统一规范表示.py new file mode 100644 index 0000000..bbedad8 --- /dev/null +++ b/2.字符串和文本/9.将unicode文本进行统一规范表示.py @@ -0,0 +1,37 @@ +import unicodedata + +if __name__ == '__main__': + s1 = 'Spicy Jalape\u00f1o' + s2 = 'Spicy Jalapen\u0303o' + + # 可以看到,某些字符串的表示在unicode下有多种选项 + print(s1,s2) + print(s1==s2) + + # 显然,这种情况在我们判断字符串的时候极其不利,这时候我们就需要把unicode编码进行规范 + + # 有两种规范方式,NFC全组成和NFD组合字符 + t1 = unicodedata.normalize('NFD', s1) + t2 = unicodedata.normalize('NFD', s2) + print(t1, t2) + print(t1==t2) + print(ascii(t1)) + + t3 = unicodedata.normalize('NFC', s1) + t4 = unicodedata.normalize('NFC', s2) + print(ascii(t3)) + + # unicodedata同时还提供NFKC和NFKD编码,这种编码提供了额外的兼容功能,能把下面这种字符分开 + s = '\ufb01' + print(s) + print(unicodedata.normalize('NFD', s)) + print(unicodedata.normalize('NFKC', s)) + print(unicodedata.normalize('NFKD', s)) + + # 如果要去除音符标记~,那么我们可以先用组合字符NFD标准化,再进行去除 + suka = unicodedata.normalize('NFD', s1) + print(suka) + suka = ''.join(c for c in unicodedata.normalize('NFD', suka) if not unicodedata.combining(c)) + print(suka) + + # 在上面的例子里,我们使用了unicodedata.combining()函数来判断字符是否属于组合型字符 diff --git a/3.数字日期和时间/1.对数值进行取整.py b/3.数字日期和时间/1.对数值进行取整.py new file mode 100644 index 0000000..59661b9 --- /dev/null +++ b/3.数字日期和时间/1.对数值进行取整.py @@ -0,0 +1,16 @@ + + +if __name__ == '__main__': + # 很多时候,我们需要对浮点数进行取整操作,通常情况下,使用自带的round函数就能解决问题 + f = 1.23456 + print(round(f, 2)) + + # 虽然round函数可以很方便的对数值进行取整,但一般情况下如果需要规范输出还是用format(.2f)来做 + ff2 = format(f, '.2f') + print(ff2) + # 因为浮点数计算因为算法的局限性可能带来一些奇妙的误差 + a = 2.1 + b = 4.2 + print(a+b) + # 所以一般来说我们就是算完直接按照需要取小数位数规整完格式就行了 + # 除非我们的程序对数据精度要求非常高,那这时候就要用decimal库了 \ No newline at end of file diff --git a/3.数字日期和时间/10.矩阵和线性代数的计算.py b/3.数字日期和时间/10.矩阵和线性代数的计算.py new file mode 100644 index 0000000..51a0d91 --- /dev/null +++ b/3.数字日期和时间/10.矩阵和线性代数的计算.py @@ -0,0 +1,16 @@ +import numpy as np + +if __name__ == '__main__': + # np在线性代数处理上有一个子包叫matrix,专门使用线性代数规则来处理数据,比如矩阵乘法、求行列式和解线性方程 + m = np.matrix([[1,-2,3],[0,4,5],[7,8,-9]]) + print(m) + + # 做一个矩阵转置 + print(m.T) + # 求可逆矩阵 + print(m.I) + + v = np.matrix([[2], [3], [4]]) + print(m * v) + + # 更多线代操作在np.linalg模块下可以找到,随用随查就行 diff --git a/3.数字日期和时间/11.随机选择.py b/3.数字日期和时间/11.随机选择.py new file mode 100644 index 0000000..c5810c0 --- /dev/null +++ b/3.数字日期和时间/11.随机选择.py @@ -0,0 +1,34 @@ +import random + +from gevent.subprocess import value + +if __name__ == '__main__': + # 如果想要进行随机数操作,那么random库是我们最好的选择 + values = [1, 2, 3, 4, 5, 6, 7, 8, 9] + # 使用random.choice进行抽卡 + random_num = random.choice(values) + print(random_num) + + # 使用random.choices进行多次抽卡(k次),每次抽一张 + random_nums = random.choices(values, k=2) + print(random_nums) + + # 使用random.sample进行抽卡并剔除,这个函数只抽取一次,每次抽k张,所以k不能大于数组长度 + random_num = random.sample(values, k=3) + print(random_num) + + # 如果只是想洗牌,那用shuffle就行了 + random.shuffle(values) # 注意,这个函数不返回任何东西,它直接在原数组上进行操作 + print(values) + + # 生成随机数,可以使用random.randint, 它接受两个参数,生成a和b之间的随机数 + print(random.randint(0, 10)) + + # 如果要0-1之间的float,可以用random.random + print(random.random()) + + # 在配密钥的时候,我们有的时候会想要得到N比特位的随机整数,这时候可以用random.getrandbits(n) + print(random.getrandbits(12)) + + # 切记,random虽然可以产生随机数,但其使用的梅森旋转算法是确定算法,只要破解seed就能计算出固定值, + # 如果需要在密钥中搞到随机数,请使用ssl模块产生随机加密字节 \ No newline at end of file diff --git a/3.数字日期和时间/12.时间换算.py b/3.数字日期和时间/12.时间换算.py new file mode 100644 index 0000000..30dbe57 --- /dev/null +++ b/3.数字日期和时间/12.时间换算.py @@ -0,0 +1,38 @@ +from datetime import timedelta, datetime + +if __name__ == '__main__': + # 使用timedelta表示时间间隔 + a = timedelta(days=2, hours=6) + b = timedelta(hours=4.5) + c = a + b + print(c.days) + # 这是计算小时的秒数 + print(c.seconds) + # 这是计算天+小时的秒数 + print(c.total_seconds()) + + # 如果要创建特定的日期和时间,可以用datetime来表示 + time1 = datetime(2024, 8, 28) + birthday = datetime(2001, 4, 29) + life_time = time1 - birthday + print(life_time.days) + + now = datetime.now() + print(now) + + # 值得注意的是,datetime模块可以正确处理闰年的二月并计算出正确的天数,我的评价是我直接不管 + + # 对绝大部分的问题,datetime模块已经足够使用,但是如果需要处理复杂时间问题,比如时区、模糊时间范围、计算节日日期等 + # 请他娘的使用dateutil模块 + + a = datetime(2012, 9, 23) + # 很明显,因为timedelta没有month这个key,所以我们不能直接加一个月,它的精度最大只到days + + # 这时候dateutil下的relativedelta就派上用场 + from dateutil.relativedelta import relativedelta + b = a + relativedelta(years=10, months=2) + print(b) + + # 包括计算日期差 + d = relativedelta(b, a) + print(d.years, d.months, d.days) diff --git a/3.数字日期和时间/13.计算上周五的日期.py b/3.数字日期和时间/13.计算上周五的日期.py new file mode 100644 index 0000000..13288df --- /dev/null +++ b/3.数字日期和时间/13.计算上周五的日期.py @@ -0,0 +1,31 @@ +from datetime import datetime, timedelta + +if __name__ == '__main__': + weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] + + def get_previous_by_day(day_name, start_date=None): + if start_date is None: + start_date = datetime.today() + + day_num = start_date.weekday() + day_num_target = weekdays.index(day_name) + + days_ago = (7 + day_num - day_num_target) % 7 + + if days_ago == 0: + days_ago = 7 + target_date = start_date - timedelta(days=days_ago) + return target_date + + get_previous_by_day("Friday") + + # 但是如果要经常这么干,请使用dateutil包 + d = datetime.now() + from dateutil.relativedelta import relativedelta + from dateutil.rrule import * + + # 找最近的周五,FR是rrule里的函数 + print(d + relativedelta(weekday=FR)) + + # 找上一个周五 + print(d + relativedelta(weekday=FR(-1))) diff --git a/3.数字日期和时间/14.找出当月日期范围.py b/3.数字日期和时间/14.找出当月日期范围.py new file mode 100644 index 0000000..13a255a --- /dev/null +++ b/3.数字日期和时间/14.找出当月日期范围.py @@ -0,0 +1,33 @@ +from datetime import datetime, date, timedelta +import calendar + +from fontTools.misc.plistlib import end_date + +if __name__ == "__main__": + + def get_month_range(start_date=None): + if start_date is None: + start_date = date.today().replace(day=1) + _, days_in_month = calendar.monthrange(start_date.year, start_date.month) + + end_date = start_date + timedelta(days = days_in_month) + + return start_date, end_date + + a_day = timedelta(days=1) + first_day, last_day = get_month_range() + + while first_day < last_day: + print(first_day) + first_day = first_day + a_day + + # 在获取一个月天数的时候,calendar库会非常好用,monthrange会返回第一个工作日的日期和当月的天数 + + # 如果想要实现和Python内建的range一样的遍历效果,可以写一个生成器 + def date_range(start_date, end_date, step=timedelta(days=1)): + while start_date < end_date: + yield start_date + start_date += step + + for d in date_range(first_day, last_day): + print(d) diff --git a/3.数字日期和时间/15.将字符串转成日期.py b/3.数字日期和时间/15.将字符串转成日期.py new file mode 100644 index 0000000..1f242b1 --- /dev/null +++ b/3.数字日期和时间/15.将字符串转成日期.py @@ -0,0 +1,22 @@ +from datetime import datetime, date + +if __name__ == '__main__': + text = "2012-09-20" + + # str -> time + y = datetime.strptime(text, "%Y-%m-%d") + z = datetime.now() + diff = z - y + print(diff) + + # 如果你觉得不够美观,那就格式化一下 + # time -> str + struct_time_str = datetime.strftime(y, "%A %B %d, %Y") + print(struct_time_str) + + # 当然,strptime这个函数的性能相当糟糕,大量使用时如果考虑到效率问题还请自己动手 + def parse_date(date_str): + y, m, d = date_str.split("-") + return datetime(int(y), int(m), int(d)) + + print(parse_date(text)) \ No newline at end of file diff --git a/3.数字日期和时间/16.处理涉及时区的日期问题.py b/3.数字日期和时间/16.处理涉及时区的日期问题.py new file mode 100644 index 0000000..8cb8e42 --- /dev/null +++ b/3.数字日期和时间/16.处理涉及时区的日期问题.py @@ -0,0 +1,14 @@ +from datetime import datetime +from pytz import timezone + +if __name__ == "__main__": + d = datetime(2012, 12, 21, 9, 30, 0) + print(d) + + central = timezone('US/Central') + loc_d = central.localize(d) + print(loc_d) + + bang_d = loc_d.astimezone(timezone("Asia/Kolkata")) + print(bang_d) + diff --git a/3.数字日期和时间/2.执行精确小数计算.py b/3.数字日期和时间/2.执行精确小数计算.py new file mode 100644 index 0000000..b87cfc5 --- /dev/null +++ b/3.数字日期和时间/2.执行精确小数计算.py @@ -0,0 +1,12 @@ +from decimal import Decimal + + +if __name__ == '__main__': + # 可以看到,使用float的ieee754浮点标准进行计算会产生误差 + a = 4.2 + b = 2.1 + print(a+b) + # 如果需要高精度小数,我们的处理方法是将数字字符串转成decimal类型 + a = Decimal(str(4.2)) + b = Decimal(str(2.1)) + print(a+b) diff --git a/3.数字日期和时间/3.对数值做格式化输出.py b/3.数字日期和时间/3.对数值做格式化输出.py new file mode 100644 index 0000000..9a066a2 --- /dev/null +++ b/3.数字日期和时间/3.对数值做格式化输出.py @@ -0,0 +1,14 @@ + + +if __name__ == '__main__': + x = 1234.56789 + # 对数值格式化输出,我们使用经典format函数 + print(format(x, '.2f')) + print(format(x, '+<10.2f')) + print(format(x, '=>10.2f')) + print(format(x, '-^10.2f')) + # 有一些特殊的,比如数值特有的千位逗号1,000 + print(format(x, ',.2f')) + # 如果想用科学计数法,把f改成e就行 + print(format(x, '.2e')) + print(format(x, 'e')) diff --git a/3.数字日期和时间/4.二进制、八进制和十六进制.py b/3.数字日期和时间/4.二进制、八进制和十六进制.py new file mode 100644 index 0000000..ee873ca --- /dev/null +++ b/3.数字日期和时间/4.二进制、八进制和十六进制.py @@ -0,0 +1,25 @@ + + +if __name__ == '__main__': + # 想要将十进制数字转换成二进制、八进制和十六进制,可以使用内建函数 + a = 1234 + a_bin = bin(a) + print(a_bin) + a_oct = oct(a) + print(a_oct) + a_hex = hex(a) + print(a_hex) + + # 如果不想要出现0b、0o、0x这样的前缀,可以使用format函数格式化做转换 + print(format(a, 'b')) + print(format(a, 'o')) + print(format(a, 'x')) + + # 如果我们需要一个32位无符号整数,可以这样干 + x = 1234 + x = format(2**32 + x, 'b') + print(x) + # 要转回10进制的时候用int函数+字符串进制就可以了 + x = int(a_bin, 2) + print(x) + \ No newline at end of file diff --git a/3.数字日期和时间/5.从字符串中打包和解包大整数.py b/3.数字日期和时间/5.从字符串中打包和解包大整数.py new file mode 100644 index 0000000..74e380c --- /dev/null +++ b/3.数字日期和时间/5.从字符串中打包和解包大整数.py @@ -0,0 +1,35 @@ +import struct +import math + +if __name__ == '__main__': + # 假如我们有一个用字节串存储的大整数 + data = b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004' + # 要将其还原成整数,我们需要: + print(int.from_bytes(data, byteorder="little")) + print(int.from_bytes(data, byteorder="big")) + + # 而要将整数转成字节串,我们可以做一个逆向操作 + int_to_b = int.from_bytes(data, byteorder="little") + print(int.to_bytes(int_to_b, byteorder="little", length=16)) + + # 在IPV6中,网络地址以一个128位的整数表示,这时候我们可以使用struct模块来完成解包 + hi, lo = struct.unpack('>QQ', data) + print((hi << 64) + lo) + + # 通过byteorder,我们可以指定在编成字节码的时候使用大端排序还是小端排序 + byte_num = 0x01020304 + + print(byte_num.to_bytes(4, 'big')) + print(byte_num.to_bytes(4, 'little')) + + # 但是请务必使用合适的字节串大小来保存字节串,否则python会因为防止信息丢失报错 + x = 523**23 + try: + x.to_bytes(4, 'little') + except OverflowError: + print("数据溢出") + # 这时候就要先算一下字节长度再行转换 + bit_length = x.bit_length() + # 为了让内存结构更加严整,我们要将字节串补全成8的整数倍长度 + bytes_num = math.ceil(bit_length / 8) + print(x.to_bytes(bytes_num, 'little')) diff --git a/3.数字日期和时间/6.复数运算.py b/3.数字日期和时间/6.复数运算.py new file mode 100644 index 0000000..e077dda --- /dev/null +++ b/3.数字日期和时间/6.复数运算.py @@ -0,0 +1,34 @@ + + +if __name__ == '__main__': + # 在python中,我们使用complex函数或者在浮点数后面加j的方式来定义一个复数 + a = complex(2,4) + b = 2 + 4j + print(a, b) + + # 我们可以提取复数的实部、虚部和共轭复数 + print(a.real) + print(a.imag) + print(a.conjugate()) + + # 同样的,复数可以进行所有常见的算术操作 + print(a + b) + print(a - b) + print(a * b) + print(a / b) + print(abs(a)) + + # 但如果需要对复数求正弦余弦或平方根这种操作,请: + import cmath + cmath.sin(a) + cmath.cos(a) + cmath.sqrt(a) + + # 使用numpy可以生成复数数组并对它进行操作 + import numpy as np + + complex_array = np.array([1+2j, 2+3j, 3+4j]) + print(complex_array + 2) + print(np.sin(complex_array)) + + # 在标准的math库中,所有的操作都不会产生复数结果,如果想要产出复数结果,请使用支持复数感知的库,比如cmath \ No newline at end of file diff --git a/3.数字日期和时间/7.处理无穷大和NAN.py b/3.数字日期和时间/7.处理无穷大和NAN.py new file mode 100644 index 0000000..be6c353 --- /dev/null +++ b/3.数字日期和时间/7.处理无穷大和NAN.py @@ -0,0 +1,20 @@ +import math + +if __name__ == '__main__': + # python没有原生的东西来表示这些值,但他们可以被创建 + a = float('inf') + b = float('-inf') + c = float('nan') + print(a, b, c) + + # 如果想要感知这些值,请使用isnan或isinf函数 + print(math.isnan(c)) + print(math.isinf(a)) + + # 要尤其注意,inf在计算中会被传播 + print(a+45) + # 但是一些神奇操作会产生nan,比如: + print(a + b) + print(a / a) + + # 尤其注意,nan会在所有的操作中传播,且不会引起任何报错,请务必在计算前进行使用isnan函数进行安全检测 \ No newline at end of file diff --git a/3.数字日期和时间/8.分数的计算.py b/3.数字日期和时间/8.分数的计算.py new file mode 100644 index 0000000..4974d87 --- /dev/null +++ b/3.数字日期和时间/8.分数的计算.py @@ -0,0 +1,24 @@ +from fractions import Fraction + +from unicodedata import decimal +from xlrd.formula import oMSNG + +if __name__ == '__main__': + # 如果遇到分数问题,那么我们就需要使用fractions模块 + a = Fraction(1, 2) + print("这是二分之一:{}".format(a)) + + # 同样的,分数可以进行所有的算术运算 + print(a * a) + + # 我们可以通过一些方法得到分子和分母 + print("分子是:{}".format(a.numerator)) + print("分母是:{}".format(a.denominator)) + + # 或者我们可以把分数转成浮点数 + print(float(a)) + + # 下面展示如何正确计算高精度的pi/8 + import numpy as np + from decimal import Decimal + print(np.pi * Fraction(1, 8)) diff --git a/3.数字日期和时间/9.处理大型数组的计算.py b/3.数字日期和时间/9.处理大型数组的计算.py new file mode 100644 index 0000000..eef858f --- /dev/null +++ b/3.数字日期和时间/9.处理大型数组的计算.py @@ -0,0 +1,39 @@ +import numpy as np + +if __name__ == '__main__': + # 终于到了最喜欢的环节,没有np我真的会原地爆炸,np万岁 + + # 如果是普通的python list, 想要处理是很麻烦的 + li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # print(li * 2) + + # 但如果转成nparray,事情就变得简单了起来 + np_arr = np.array(li) + # print(np_arr * 2) + + # np对一维矩阵的运算也做了加强: + li2 = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + np_arr2 = np.array(li2) + # print(np_arr * np_arr2) + + # 你甚至可以计算多项式的值 + # print(3 * np_arr**2 + 2 * np_arr + 1) + + # numpy提供了很多通用函数来对nparray进行操作,比如: + np.sin(np_arr) + np.sqrt(np_arr) + # 这些操作的效率很高,平时要多用 + + # 在底层,numpy使用和C一样的连续内存空间,所以只要内存够大,你可以为所欲为 + grid = np.zeros(shape=(10000, 10000), dtype=float) + # print(np.sin(np.sqrt(grid + 10))) + + # numpy对多维数组也提供了很好的支持 + nd_array = np.array([[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15]]) + print(nd_array) + print(nd_array[1]) + print(nd_array[:, 1]) + print(nd_array[1:3, 1:3]) + print(nd_array + [1,2,3,4,5]) + # np.where,没见过,第一个参数是条件,满足就输出第二个参数x,不满足就输出第三个参数y + print(np.where(nd_array < 10, nd_array, np.nan)) diff --git a/4.迭代器与生成器/1.手动访问迭代器中的元素.py b/4.迭代器与生成器/1.手动访问迭代器中的元素.py new file mode 100644 index 0000000..3c90c95 --- /dev/null +++ b/4.迭代器与生成器/1.手动访问迭代器中的元素.py @@ -0,0 +1,24 @@ +from scipy.stats import trim1 + +if __name__ == "__main__": + items = [1, 2, 3] + items = iter(items) + # 如果需要访问可迭代对象中的元素,可以使用next函数 + while True: + item = next(items, None) + + if item is not None: + print(item) + else: + break + + # next函数的第一个参数是一个可迭代对象,第二个参数是None,是没有元素的时候的返回值 + + # 如果不限制迭代器在没有元素后的返回值,迭代器在迭代结束后会抛出一个StopIteration的错误 + while True: + try: + item = next(items) + except StopIteration: + print("没东西了") + break + diff --git a/4.迭代器与生成器/10.以键值对的方式迭代索引.py b/4.迭代器与生成器/10.以键值对的方式迭代索引.py new file mode 100644 index 0000000..849a8a8 --- /dev/null +++ b/4.迭代器与生成器/10.以键值对的方式迭代索引.py @@ -0,0 +1,13 @@ + + +if __name__ == "__main__": + my_list = [1,2,3,4,5,6,7,8,9] + # 有时候,我们想要在迭代的时候知道元素的下标,有些傻子(比如我)就很傻逼的做了个计数器 + # 但是,其实直接使用enumerate就行 + for index, number in enumerate(my_list, start=0): + print(index, number) + + # 但是如果可迭代对象的子元素是元组,那请务必别把元组的标识拆开 + my_list = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)] + for index, (x, y) in enumerate(my_list, start=1): + print(index, x, y) diff --git a/4.迭代器与生成器/11.迭代多个序列.py b/4.迭代器与生成器/11.迭代多个序列.py new file mode 100644 index 0000000..ed90e69 --- /dev/null +++ b/4.迭代器与生成器/11.迭代多个序列.py @@ -0,0 +1,30 @@ + + +if __name__ == '__main__': + # 如果想要同时迭代多个序列,那么可以使用zip函数 + list1 = [1, 2, 3, 4, 5] + list2 = ['a', 'b', 'c', 'd', 'e'] + + for i, j in zip(list1, list2): + print(i, j) + + # zip遵循最短木板原则,迭代长度等于最短的可迭代对象长度,如果想要打破这种限制,请使用itertools.zip_longest() + + from itertools import zip_longest + short_list = [1, 2, 3] + # fillvalue字段用来填充短列表的缺失值,默认为None + for i, j in zip_longest(short_list, list1, fillvalue=0): + print(i, j) + + # zip函数会构建一个元组,它的长度与输入的可迭代对象数量有关 + for i in zip(short_list, list2, list1): + print(i) + + # 双对象元组可以直接视作键值对转成字典 + dic = dict(zip(list1, list2)) + print(dic) + + # 重点!!!zip函数返回的是一个一次性迭代器,如果需要长久保存,请转成列表在内存里持久化 + storage_list = list(zip(list1, list2)) + + \ No newline at end of file diff --git a/4.迭代器与生成器/12.在不同的容器中迭代.py b/4.迭代器与生成器/12.在不同的容器中迭代.py new file mode 100644 index 0000000..4e62ec0 --- /dev/null +++ b/4.迭代器与生成器/12.在不同的容器中迭代.py @@ -0,0 +1,12 @@ +from itertools import chain + +if __name__ == '__main__': + a = [1,2,3,4,5] + b = ['a','b','c','d','e'] + + # 正常来说,我们想要迭代这两个东西需要写两个循环构建两个迭代器,但这样很操蛋,不如直接用chain把他俩作链表连起来 + for i in chain(a, b): + print(i) + + # 使用chain可以避开 a + b 要求a和b类型相同的限制,同时这种操作更加快速,因为其底层是用指针完成,而不会创建一个新的东西 + \ No newline at end of file diff --git a/4.迭代器与生成器/13.创建处理数据的管道.py b/4.迭代器与生成器/13.创建处理数据的管道.py new file mode 100644 index 0000000..c63fea8 --- /dev/null +++ b/4.迭代器与生成器/13.创建处理数据的管道.py @@ -0,0 +1,41 @@ +import os +from fnmatch import fnmatchcase + +if __name__ == "__main__": + base_path = "D:\Code\Learn\practice" + + # 想要建立一个处理管道,需要将几个生成器函数堆叠,比如,我现在要输出所有practice文件夹下的注释行内容 + # 1.首先生成文件夹路径 + def gen_dir_path(fpath): + for path, dir, files in os.walk(fpath): + for file in files: + if (not fnmatchcase(path, "*.[git][idea]*")) and (not fnmatchcase(file, "*.md")): + yield os.path.join(path, file) + + # 2.打开文件 + def gen_file(paths): + for path in paths: + f = open(path, 'rt', encoding='utf-8') + yield f + f.close() + + # 3.读取文件内容 + def gen_file_txt(opened_files): + for file in opened_files: + yield from file + print("done") + + # 4.匹配符合条件的文件行 + def read_file_lines(file_lines): + for line in file_lines: + if "#" in line: + print(line) + + # 将这些函数堆在一起就组成了一条管道,每次生成一个对象进行流水线处理, + # 这样避免了for循环的嵌套 + paths = gen_dir_path(base_path) + files = gen_file(paths) + lines = gen_file_txt(files) + read_file_lines(lines) + + diff --git a/4.迭代器与生成器/14.扁平化处理嵌套型的序列.py b/4.迭代器与生成器/14.扁平化处理嵌套型的序列.py new file mode 100644 index 0000000..fa62a97 --- /dev/null +++ b/4.迭代器与生成器/14.扁平化处理嵌套型的序列.py @@ -0,0 +1,18 @@ +from collections import Iterable + +if __name__ == '__main__': + + # 将复杂的序列扁平化,使用递归来脱壳 + def flatten(items, ignore=(str, bytes)): + for item in items: + # 要进行递归,首先这个元素要是可迭代的,其次它不能是字符串或二进制串 + if isinstance(item, Iterable) and not isinstance(item, ignore): + # yield from 将请求转发到flatten函数返回的迭代器进行嵌套,避免再写for循环 + # yield from 在后面的协程和基于生成器的并发程序里有重要作用 + yield from flatten(item, ignore) + else: + yield item + + a = [1, 2, [3, 4, [5, 6], 7], 8] + for item in flatten(a): + print(item) diff --git a/4.迭代器与生成器/15.合并多个有序序列,再对整个有序序列进行迭代.py b/4.迭代器与生成器/15.合并多个有序序列,再对整个有序序列进行迭代.py new file mode 100644 index 0000000..c5b1b5b --- /dev/null +++ b/4.迭代器与生成器/15.合并多个有序序列,再对整个有序序列进行迭代.py @@ -0,0 +1,17 @@ +import heapq +from itertools import chain + +if __name__ == '__main__': + a = [1, 4, 7, 10] + b = [2, 5, 6, 11] + + # 首先复习一下前面的知识,如果不考虑合并后也是有序的,用chain + for item in chain(a, b): + print(item) + + # 但如果我们想要得到有序合并序列,那就要用堆自带的merge功能了,利用小根堆的特殊性来排序 + new_arr = heapq.merge(a, b) + for item in new_arr: + print(item) + + # 记住,heapq的merge事先不会对输入可迭代对象的有序性进行检查,而是每次从可迭代对象序列里取出最小的第一个进堆 diff --git a/4.迭代器与生成器/16.test.txt b/4.迭代器与生成器/16.test.txt new file mode 100644 index 0000000..49de3f8 --- /dev/null +++ b/4.迭代器与生成器/16.test.txt @@ -0,0 +1 @@ +Hello PythonCookBook \ No newline at end of file diff --git a/4.迭代器与生成器/16.用迭代器取代while循环.py b/4.迭代器与生成器/16.用迭代器取代while循环.py new file mode 100644 index 0000000..46480c2 --- /dev/null +++ b/4.迭代器与生成器/16.用迭代器取代while循环.py @@ -0,0 +1,33 @@ + + +if __name__ == "__main__": + CHUNKSIZE = 10 + + def render(s): + # 军火展示 + print(s.read()) + s.seek(0) + + # 在处理文件的时候,我们习惯用while循环来迭代数据 + print("一般while处理:") + while True: + data = s.read(CHUNKSIZE) + if data == b'': + break + print(data) + + s.seek(0) + + + # 但是可以使用迭代器来升级一下 + print("进化成迭代器:") + for chunk in iter(lambda: s.read(CHUNKSIZE), b''): + print(chunk) + + path = "4.迭代器与生成器/16.test.txt" + # 二进制打开文件记得用rb,下一章进化一下文件读取方面的知识 + f = open(path, 'rb') + + render(f) + print("Done") + diff --git a/4.迭代器与生成器/2.委托迭代.py b/4.迭代器与生成器/2.委托迭代.py new file mode 100644 index 0000000..eecd39c --- /dev/null +++ b/4.迭代器与生成器/2.委托迭代.py @@ -0,0 +1,28 @@ + + +if __name__ == "__main__": + class Node: + def __init__(self, value): + self._value = value + self._children = [] + + def __repr__(self): + return 'Node({!r})'.format(self._value) + + def add_child(self, node): + self._children.append(node) + + # 对象的内置iter方法,在迭代对象的时候将迭代请求转发,返回children的迭代器对象 + # iter函数和len函数一样,底层原理都是返回对象的底层方法,iter返回对象的__iter__() + def __iter__(self): + return iter(self._children) + + root = Node(0) + child1 = Node(1) + child2 = Node(2) + root.add_child(child1) + root.add_child(child2) + + for ch in root: + print(ch) + diff --git a/4.迭代器与生成器/3.使用生成器创建新的迭代模式.py b/4.迭代器与生成器/3.使用生成器创建新的迭代模式.py new file mode 100644 index 0000000..6c2908a --- /dev/null +++ b/4.迭代器与生成器/3.使用生成器创建新的迭代模式.py @@ -0,0 +1,52 @@ + + +if __name__ == '__main__': + + def fbb(start, stop, step): + x = start + while x <= stop: + yield x + x += step + # 上面是一个生成器函数,它只会在相应迭代时运行 + + # 在调用生成器函数的时候,因为直接生成了数字,所以有返回值 + for i in fbb(0, 10, 1): + print(i) + + # 尝试使用fbb生成金字塔 + + # 先用生成器整一个斐波那契数列生成函数 + def fbb_arr(n): + index = 0 + a0 = 1 + a1 = 1 + while index < n: + yield a0 + a0, a1 = a1, a0 + a1 + + index += 1 + + # 按照n行打印出来 + def print_num_delta(n): + # 计算需要生成多少个数字 + fbb_number_num = sum(range(1, n + 1)) + # 生成数字并保存 + fbb_list = list(fbb_arr(fbb_number_num)) + # 数字下标,用于遍历中保存位置 + start_index = 0 + + # 每一行的数字数量 + for number_num in range(1, n + 1): + # 切片用空格分隔后转成字符串 + nums = ' '.join(str(item) for item in fbb_list[start_index: start_index + number_num]) + # 格式化打印 + print(format(nums, r' ^{}'.format(len(str(fbb_list[-1])) * n))) + # 更新下一行的数字下标 + start_index = start_index + number_num + + print_num_delta(5) + + # 生成器里为什么没有return: 如果一个函数里包含yield语句,那就会被识别为生成器函数, + # 函数里的return此时自动失效被替换成生成器 + # 生成器函数在识别到return时,意识到生成结束,返回的StopIteration,交给for识别 + diff --git a/4.迭代器与生成器/4.迭代协议.py b/4.迭代器与生成器/4.迭代协议.py new file mode 100644 index 0000000..f677fa8 --- /dev/null +++ b/4.迭代器与生成器/4.迭代协议.py @@ -0,0 +1,31 @@ +# 这是标准的 +class Node: + def __init__(self, value): + self.value = value + self._children = [] + + def __repr__(self): + return "Node({})".format(self.value) + + def add_child(self, node): + self._children.append(node) + + def __iter__(self): + return iter(self._children) + + def depth_first(self): + yield self + for child in self: + yield from child.depth_first() + +if __name__ == '__main__': + root = Node(0) + child1 = Node(1) + child2 = Node(2) + root.add_child(child1) + root.add_child(child2) + child1.add_child(Node(3)) + child2.add_child(Node(4)) + + for ch in root.depth_first(): + print(ch) diff --git a/4.迭代器与生成器/5.反向迭代.py b/4.迭代器与生成器/5.反向迭代.py new file mode 100644 index 0000000..a2481f8 --- /dev/null +++ b/4.迭代器与生成器/5.反向迭代.py @@ -0,0 +1,31 @@ + + +if __name__ == "__main__": + a = [1, 2, 3, 4] + # python自带的reversed函数搞定了反向迭代,其本质时调用了__reversed__方法 + for i in reversed(a): + print(i) + + for i in a.__reversed__(): + print(i) + + # 如果想要实现类的反向迭代,可以在实现__iter__()方法的同时搞一个__reversed__()方法 + class Countdown: + def __init__(self, start): + self.start = start + + def __iter__(self): + n = self.start + while n > 1: + yield n + n -= 1 + + def __reversed__(self): + n = 1 + while n <= self.start: + yield n + n += 1 + + a = Countdown(10) + for i in reversed(a): + print(i) diff --git a/4.迭代器与生成器/6.定义带有额外状态的生成器函数.py b/4.迭代器与生成器/6.定义带有额外状态的生成器函数.py new file mode 100644 index 0000000..7110cec --- /dev/null +++ b/4.迭代器与生成器/6.定义带有额外状态的生成器函数.py @@ -0,0 +1,38 @@ +from collections import deque + +if __name__ == "__main__": + # 在学到了生成器函数以后,有一个巨大陷阱那就是尝试用生成器函数解决一切问题 + # 当生成器函数需要与外界交互的时候,就会让它非常复杂 + # 所以生成器我们就让它安心生成,在需要记录或者与外界交互的时候,写一个生成器类 + class FbbIterClass: + def __init__(self,nums, history_len=3): + self.nums = nums + self.history = deque(maxlen=history_len) + + def __iter__(self): + a0, a1 = 1, 1 + index = 0 + while index < self.nums: + self.history.append(a0) + yield a0 + a0, a1 = a1, a0 + a1 + index += 1 + + fbbs = FbbIterClass(5) + + # 先用生成器生成,结果在生成的过程中会存入历史队列 + for i in fbbs: + print(i) + + # 当然,在使用for之外的方法进行迭代时,需要额外套一层iter来转发请求到__iter__() + fbb_iter = iter(fbbs) + print(fbb_iter.__next__()) + print(fbb_iter.__next__()) + print(fbb_iter.__next__()) + print(fbb_iter.__next__()) + print(fbb_iter.__next__()) + + # 这种方法的好处是,可以在迭代时随时暴露一些东西给外部程序,比如属性和状态 + for line in fbbs.history: + print("history{}".format(line)) + diff --git a/4.迭代器与生成器/7.对迭代器做切片操作.py b/4.迭代器与生成器/7.对迭代器做切片操作.py new file mode 100644 index 0000000..b96c172 --- /dev/null +++ b/4.迭代器与生成器/7.对迭代器做切片操作.py @@ -0,0 +1,26 @@ +from itertools import islice + +if __name__ == '__main__': + # 在程序生成过程中,我们有时候需要做一些无限生成器,这些生成器往往需要切片使用 + def infinity_iterator(start): + while True: + yield start + start += 1 + + # 无限生成器 + for i in infinity_iterator(0): + print(i) + break + + # 想要对无限生成器进行切片,我们就需要使用itertools里的islice函数,记得这个函数也是左闭右开 + iterator= infinity_iterator(0) + iter_10_20 = islice(infinity_iterator(0), 10, 21) + for i in iter_10_20: + print(i) + # 注意!!!islice操作是通过丢弃索引序列前的元素实现的,返回的是一个一次性迭代器,被迭代过后元素就被消耗掉无了 + print("再次使用迭代") + for i in iter_10_20: + print(i) + + + diff --git a/4.迭代器与生成器/8.跳过可迭代对象中的前一部分元素.py b/4.迭代器与生成器/8.跳过可迭代对象中的前一部分元素.py new file mode 100644 index 0000000..e4a275d --- /dev/null +++ b/4.迭代器与生成器/8.跳过可迭代对象中的前一部分元素.py @@ -0,0 +1,22 @@ +from itertools import dropwhile, islice + +if __name__ == '__main__': + words = ["#look", "#into", "#my", "eyes", "look", "into", "my", "eyes", + "the", "eyes", "the", "eyes", "the", "eyes", "not", "around", "the", + "eyes", "don't", "look", "around", "the", "eyes", "look", "into", + "my", "eyes", "you're", "under"] + + # 想要对可迭代对象进行过滤,也可以使用dropwhile函数,比如这里我想过滤掉前几行有#的东西 + for i in dropwhile(lambda x : x.startswith('#'), words): + print(i) + + # dropwhile会在迭代时对函数进行匹配,将符合条件的值丢弃,直到出现第一个符合条件的值后停止工作返回所有剩余内容 + + # 如果你知道需要跳过几个元素,那可以直接用islice进行切片操作来过滤 + + items = ['a', 'b', 'c', 1, 2, 3] + # None表示[3:] + for i in islice(items, 3, None): + print(i) + + # 当然,使用列表的过滤方法也可以删除一些不要的东西,但是这样的删除是全局的,而不是仅在开头生效 diff --git a/4.迭代器与生成器/9.迭代所有的排列组合.py b/4.迭代器与生成器/9.迭代所有的排列组合.py new file mode 100644 index 0000000..17e67aa --- /dev/null +++ b/4.迭代器与生成器/9.迭代所有的排列组合.py @@ -0,0 +1,23 @@ +from itertools import permutations, combinations + +if __name__ == '__main__': + # 有时候我们想对可迭代对象中的元素进行排列组合,这个时候我们就不得不靠for循环写出很不python的代码 + # itertools库帮助我们解决了这个困扰 + items= ['a', 'b', 'c', 'd', 'e'] + + # permutations会列出所有的排列,如果指定排列长度,那就会输出该长度下所有的排列 + for p in permutations(items): + print(p) + + for p in permutations(items, 3): + print(p) + + # 如果想要得到特定长度的组合,那就可以使用combination函数 + for c in combinations(items, 3): + print(c) + + # 上面这种组合是不放回的组合,如果想产生放回元素的组合,可以使用combinations_with_replacement函数 + from itertools import combinations_with_replacement as cwr + for c in cwr(items, 3): + print(c) + diff --git a/5.文件与IO/1.somefile.txt b/5.文件与IO/1.somefile.txt new file mode 100644 index 0000000..bcdec74 --- /dev/null +++ b/5.文件与IO/1.somefile.txt @@ -0,0 +1 @@ +Hello Python File diff --git a/5.文件与IO/1.读写文本数据.py b/5.文件与IO/1.读写文本数据.py new file mode 100644 index 0000000..c7892bf --- /dev/null +++ b/5.文件与IO/1.读写文本数据.py @@ -0,0 +1,47 @@ + + +if __name__ == "__main__": + path = "5.文件与IO/1.somefile.txt" + + # 如果想要读取文件,那open函数是唯一的选择,它有很多模式: + # # 只读组, 一般用于配置信息 + # open(path, 'rb') + # open(path, 'rt') + # + # # 只写覆盖模式,将指针指向文件头,一般用于文件输出或覆盖旧内容 + # open(path, 'wb') + # open(path, 'wt') + # + # # 只写追加模式,将指针从默认的文件头指向文件尾 + # open(path, 'ab') + # open(path, 'at') + + # 如果上面的任何一种模式后面有+,那么将会变成读写模式 + + # 在打开文件时,可以指定文件的编码格式,默认编码模式可以使用sys库进行查询 + import sys + + print(sys.getdefaultencoding()) + + # 当我们想打开文件的时候,不建议直接使用上面的open函数,因为这样需要每次记住手动关闭打开的文件,像这样 + # 当然,二进制写入不需要指定encoding, 正常来说需要指定encoding='utf-8' + a = open(path, 'wb') + a.write(b'Hello Python File\n') + a.close() + + # 我们可以使用with语句来为文件创建一个上下文环境,在程序离开with的程序段以后,会自动关闭文件 + with open(path, 'rb') as f: + # 记住,如果是二进制写入,解出来的时候要用utf-8解码一下 + print(f.read()) + + # 可以看到文件已经被关闭了 + print(f.closed) + + # 在UNIX和Windows上,换行符不太相同,UNIX是\n,而windows是\r\n + # Python在读取文件时做了内置的转换,统一换行符为\n,如果不需要这种转换,可以设置newline='' + open(path, 'rb', newline='') + + # 如果遇见解码错误,只需要更换encoding就行, 也可以设置错误解决方案字段errors + open(path, 'rb', encoding='utf-8', errors='replace') + + diff --git a/5.文件与IO/10.data b/5.文件与IO/10.data new file mode 100644 index 0000000..0295e35 Binary files /dev/null and b/5.文件与IO/10.data differ diff --git a/5.文件与IO/10.对二进制文件做内存映射.py b/5.文件与IO/10.对二进制文件做内存映射.py new file mode 100644 index 0000000..416a1ff --- /dev/null +++ b/5.文件与IO/10.对二进制文件做内存映射.py @@ -0,0 +1,44 @@ +import os +import mmap + +if __name__ == '__main__': + + def memory_map(file_path, access=mmap.ACCESS_WRITE): + size = os.path.getsize(file_path) + fild_data = os.open(file_path, os.O_RDWR) + + return mmap.mmap(fild_data, size, access=access) + + SIZE = 1000000 + # with open(r'5.文件与IO/10.data', 'wb') as f: + # f.seek(SIZE - 1) + # f.write(b'\x00') + + m = memory_map('5.文件与IO/10.data') + print(len(m)) + m[0:11] = b'Hello World' + m.close() + + with open(r'5.文件与IO/10.data', 'rb') as f: + print(f.read(11)) + + # 当然,这个函数得益于他的open内嵌,也能在with的上下文中打开并自动关闭 + with memory_map('5.文件与IO/10.data') as m: + print(len(m)) + print(m.closed) + + # 当然,如果你想只读,可以在access里设置成mmp.ACCESS_READ, + # 如果只想把文件拷贝到内存而不是直接在文件里修改,mmp.ACCESS_COPY非常有用 + # mmap和memoryview又有所不同 + m = memory_map('5.文件与IO/10.data') + v = memoryview(m).cast('I') + v[0] = 7 + m[0:4] = b'\x07\x01\x00\x00' + + print(m[0:4]) + print(v[0]) + # 可以看出来,memoryview在做转换的时候用的是小端存储 + print(int.from_bytes(m[0:4], byteorder='little')) + print(int.from_bytes(m[0:4], byteorder='big')) + v.release() + m.close() diff --git a/5.文件与IO/11.处理路径.py b/5.文件与IO/11.处理路径.py new file mode 100644 index 0000000..28b021c --- /dev/null +++ b/5.文件与IO/11.处理路径.py @@ -0,0 +1,20 @@ +import os + +if __name__ == '__main__': + path = '/Users/beazley/Data/data.csv' + + # 如果我们想要得到地址的最后一部分,可以使用basename字段 + print(os.path.basename(path)) + + # 前面的部分作为文件夹,可以使用dirname读出来 + print(os.path.dirname(path)) + + # 如果想要拼接多个路径,则可以使用join + print(os.path.join('suka', 'blyet', os.path.basename(path))) + + # 想要提取出文件名,可以使用splitext + print(os.path.splitext(os.path.basename(path))) + + # 也可以使用expanduser展开上级目录,默认接控制台下的路径 + path = "~\practice" + print(os.path.expanduser(path)) diff --git a/5.文件与IO/12.检测文件是否存在.py b/5.文件与IO/12.检测文件是否存在.py new file mode 100644 index 0000000..6db32ca --- /dev/null +++ b/5.文件与IO/12.检测文件是否存在.py @@ -0,0 +1,22 @@ +import os + +if __name__ == '__main__': + # 如果想知道文件是否存在,可以使用os.path.exists + # 这个函数返回一个布尔值,如果存在就返回T,否则返回F + print(os.path.exists("file_not_exists.txt")) + print(os.path.exists("5.文件与IO/1.somefile.txt")) + + # 判断是不是一个文件,可以使用isfile方法 + print(os.path.isfile("5.文件与IO/1.somefile.txt")) + # 判断是不是一个文件夹,可以使用isdir方法 + print(os.path.isdir("5.文件与IO")) + # 判断是不是一个软连接,可以使用islink方法 + print(os.path.isdir("5.文件与IO")) + # 想要知道islink的快捷方式指向的地址,可以使用realpath方法 + print(os.path.realpath("5.文件与IO")) + + # os.path模块可以解决大部分路径问题,包括切片、判断、拼接等,唯一注意的就是记得给python程序访问路径的权限 + + # 如果要得到大小和修改日期,也有如下解决方案: + os.path.getsize("5.文件与IO/1.somefile.txt") + os.path.getmtime("5.文件与IO/1.somefile.txt") \ No newline at end of file diff --git a/5.文件与IO/13.获取目录内容的列表.py b/5.文件与IO/13.获取目录内容的列表.py new file mode 100644 index 0000000..4342510 --- /dev/null +++ b/5.文件与IO/13.获取目录内容的列表.py @@ -0,0 +1,9 @@ +from os import listdir + +if __name__ == '__main__': + # 如果想要得到路径下所有文件的列表,那么我们可以使用listdir方法 + print(listdir("5.文件与IO")) + + # listdir函数返回一个文件列表 + print(type(listdir())) + diff --git a/5.文件与IO/14.绕过文件名编码.py b/5.文件与IO/14.绕过文件名编码.py new file mode 100644 index 0000000..bdc2451 --- /dev/null +++ b/5.文件与IO/14.绕过文件名编码.py @@ -0,0 +1,9 @@ +import sys + +if __name__ == "__main__": + # 默认情况下,文件名是根据系统编码来进行编码的 + print(sys.getdefaultencoding()) + + # 如果你想要无视这种编码规则,可以使用字节串来指定文件名 + with open(b'test.txt', 'w') as f: + print(f.read()) \ No newline at end of file diff --git a/5.文件与IO/15.打印无法解码的文件名.py b/5.文件与IO/15.打印无法解码的文件名.py new file mode 100644 index 0000000..8d844f1 --- /dev/null +++ b/5.文件与IO/15.打印无法解码的文件名.py @@ -0,0 +1,14 @@ + + +if __name__ == "__main__": + # 有时候,可能会出现一些标准情况下打印会出现问题的文件名,他们不遵循系统的默认编码方式 + # 这时候如果print这些文件名,那么py会因为无法解码而将字符映射到一个代理编码表,显然print无法处理这些代理为空的新建编码 + # 解决的方案是建一个转换函数 + def bad_filename(filename): + return repr(filename)[1: -1] + + name = '' + try: + print(name) + except UnicodeEncodeError: + print(bad_filename(name)) \ No newline at end of file diff --git a/5.文件与IO/16.为已经打开的文件修改编码方式.py b/5.文件与IO/16.为已经打开的文件修改编码方式.py new file mode 100644 index 0000000..2b68e5f --- /dev/null +++ b/5.文件与IO/16.为已经打开的文件修改编码方式.py @@ -0,0 +1,40 @@ +import urllib.request +import io +import gzip + +import requests + +if __name__ == "__main__": + req = urllib.request.Request('http://www.python.org/') + # 网页返回有时候会被压缩,用这句话让服务器帮我们解压完发过来,别给.gz + req.add_header('Accept-Encoding', 'gzip,deflate') + req.AutomaticDecompression='DecompressionMethods.GZip' + u = urllib.request.urlopen(req) + # 如果想将u以utf-8的方式添加编码,可以使用io.TextIOWrapper对它进行封装 + text = io.TextIOWrapper(u, encoding='utf-8') + print(text.read(10)) + + # 如果文件已经以文本形式打开,想要更换编码层,可以先将原有的编码层移除并替换 + text = io.TextIOWrapper(text.detach(), encoding='latin-1') + print(text.read(10)) + + # Python打开文件时,将文件分为三层,源文件、源文件的二进制缓存buffer和解码器 + f = open("5.文件与IO/1.somefile.txt", 'rt') + # 解码器层 + print(f) + # 二进制缓存 + print(f.buffer) + # 源文件 + print(f.buffer.raw) + f.close() + + # 如果想要改变decoder,最好的办法就是使用detach()函数返回上一层的buffer,然后再用io.TextIOWraper封装 + f = open("5.文件与IO/1.somefile.txt", 'rt') + print(f) + b = f.detach() + print(b) + # 返回buffer层后再对buffer重编码 + new = io.TextIOWrapper(b, encoding='latin-1', errors='xmlcharrefreplace') + print(new) + + diff --git a/5.文件与IO/17.try.txt b/5.文件与IO/17.try.txt new file mode 100644 index 0000000..99b7bf5 --- /dev/null +++ b/5.文件与IO/17.try.txt @@ -0,0 +1 @@ +cakae \ No newline at end of file diff --git a/5.文件与IO/17.将字节数据写入文本文件.py b/5.文件与IO/17.将字节数据写入文本文件.py new file mode 100644 index 0000000..6294472 --- /dev/null +++ b/5.文件与IO/17.将字节数据写入文本文件.py @@ -0,0 +1,12 @@ +import io + +if __name__ == "__main__": + file = "5.文件与IO/17.try.txt" + + # 上一章我们知道了文件打开后都长什么样子,想要往文件里写字节数据怎么办?直接往buffer里怼就完了、 + # 这样可以绕过文件的编码层 + f = open(file, "w") + f.buffer.write(b"write") + + + f.close() \ No newline at end of file diff --git a/5.文件与IO/18.将已有的文件描述符包装为文件对象.pyi b/5.文件与IO/18.将已有的文件描述符包装为文件对象.pyi new file mode 100644 index 0000000..8546d2f --- /dev/null +++ b/5.文件与IO/18.将已有的文件描述符包装为文件对象.pyi @@ -0,0 +1,10 @@ +import os + +if __name__ == "__main__": + # open函数除了可以对文件对象进行操作以外,也可以打开一些由系统创建的文件描述符 + fd = os.open("5.文件与IO/17.try.txt", os.O_WRONLY | os.O_CREAT) + + # 当文件被关闭时,这些由系统创建的IO管道也会随之关闭 + f = open(fd, 'wt') + f.write("ca") + f.close() diff --git a/5.文件与IO/19.创建临时文件和目录.py b/5.文件与IO/19.创建临时文件和目录.py new file mode 100644 index 0000000..ac0b883 --- /dev/null +++ b/5.文件与IO/19.创建临时文件和目录.py @@ -0,0 +1,27 @@ +from tempfile import TemporaryFile, NamedTemporaryFile, TemporaryDirectory + +from bottle import delete + +if __name__ == '__main__': + with TemporaryFile('w+t') as f: + f.write("hello") + f.write("temp_file") + + f.seek(0) + data = f.read() + + print(data) + print(f.closed) + + # 如果想要对临时文件命名,使用NamedTemporaryFile,如果不想文件或文件夹在关闭后自动删除,请使用delete=False + # 跑完记得手动干掉它 + with NamedTemporaryFile('w+t', delete=False) as f: + print("file name is : " + f.name) + + # 你甚至可以创建一个临时文件夹 + with TemporaryDirectory() as d: + print(d) + + # 对临时文件,可以指定前缀后缀和文件夹,记住,文件夹一定要真的存在 + with NamedTemporaryFile(prefix="PPP", suffix=".txt", dir=d) as f: + print("file name is : " + f.name) diff --git a/5.文件与IO/2.somefile.txt b/5.文件与IO/2.somefile.txt new file mode 100644 index 0000000..062dadf --- /dev/null +++ b/5.文件与IO/2.somefile.txt @@ -0,0 +1 @@ +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 \ No newline at end of file diff --git a/5.文件与IO/2.将输出重定向到文件中.py b/5.文件与IO/2.将输出重定向到文件中.py new file mode 100644 index 0000000..63349d3 --- /dev/null +++ b/5.文件与IO/2.将输出重定向到文件中.py @@ -0,0 +1,10 @@ + + +if __name__ == "__main__": + # 如果想要将东西输出到文件中,可以在print函数中指定file字段 + # print函数的默认结尾是\n,如果想要替换这个默认结尾,可以在end字段设置 + path = r"5.文件与IO/2.somefile.txt" + with open(path, "at") as f: + for i in range(10): + print(i, file=f, end=' ') + print("Done") diff --git a/5.文件与IO/20.与串口进行通信.py b/5.文件与IO/20.与串口进行通信.py new file mode 100644 index 0000000..036a744 --- /dev/null +++ b/5.文件与IO/20.与串口进行通信.py @@ -0,0 +1 @@ +# 这部分是硬件通信相关内容,先不学 \ No newline at end of file diff --git a/5.文件与IO/21.test.bin b/5.文件与IO/21.test.bin new file mode 100644 index 0000000..f873359 Binary files /dev/null and b/5.文件与IO/21.test.bin differ diff --git a/5.文件与IO/21.序列化Python对象.py b/5.文件与IO/21.序列化Python对象.py new file mode 100644 index 0000000..99d561c --- /dev/null +++ b/5.文件与IO/21.序列化Python对象.py @@ -0,0 +1,23 @@ +import pickle + +class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + +if __name__ == '__main__': + # 对于一般的程序来说,可以使用pickle作为对象的序列化器 + a = Person('a', 20) + file = open("5.文件与IO/21.test.bin", 'wb') + # 将序列化对象a写入file + pickle.dump(a, file) + file.close() + + file = open("5.文件与IO/21.test.bin", 'rb') + # 从file读出序列化对象进行反序列化 + a = pickle.load(file) + print(a.name, a.age) + + # 值得一提的是,正在运行的线程被pickle打断后,会保存当前的状态, + # 当线程再次从文件中被读取,会继续之前的进度 diff --git a/5.文件与IO/3.以不同的分隔符或行结尾符完成打印.py b/5.文件与IO/3.以不同的分隔符或行结尾符完成打印.py new file mode 100644 index 0000000..df73602 --- /dev/null +++ b/5.文件与IO/3.以不同的分隔符或行结尾符完成打印.py @@ -0,0 +1,8 @@ + + +if __name__ == "__main__": + # 这是普通的打印 + print(1,2,3) + # 想要使用分隔符来隔开数字,可以在sep字段设置分隔符 + print(1,2,3, sep="..") + diff --git a/5.文件与IO/4.bin_file.bin b/5.文件与IO/4.bin_file.bin new file mode 100644 index 0000000..8533f94 Binary files /dev/null and b/5.文件与IO/4.bin_file.bin differ diff --git a/5.文件与IO/4.读写二进制数据.py b/5.文件与IO/4.读写二进制数据.py new file mode 100644 index 0000000..01d256a --- /dev/null +++ b/5.文件与IO/4.读写二进制数据.py @@ -0,0 +1,43 @@ +from base64 import decode + + +def write_bin_file(bin_path): + with open(bin_path, 'wb') as f: + f.write(b'Hello BinFile') + + +if __name__ == "__main__": + path = "5.文件与IO/4.bin_file.bin" + + write_bin_file(path) + # 如果想要打开二进制文件,那就需要做'rb'模式 + with open(path, 'rb') as f: + print(f.read()) + + # 在打印的时候也有差异,要记得先utf-8转码,不然出来的是ascii + a = b'Hello BinFile' + for i in a: + print(i, end=' ') + print() + + for i in a.decode('utf-8'): + print(i, end='') + print() + + # C语言结构体和数组这种自带缓冲区接口的东西,可以直接读写文件,而不用先用encoder编码 + import array + a = array.array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + # 往文件里写array + with open(path, 'wb') as f: + f.write(a) + + # 直接把bin array从文件里薅出来塞到b的缓冲区 + b = array.array('i', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + with open(path, 'rb') as f: + f.readinto(b) + + print(b) + + # 但是这么干有风险,要谨慎应对 + + diff --git a/5.文件与IO/5.missing_file.bin b/5.文件与IO/5.missing_file.bin new file mode 100644 index 0000000..710ebd4 --- /dev/null +++ b/5.文件与IO/5.missing_file.bin @@ -0,0 +1 @@ +missing file \ No newline at end of file diff --git a/5.文件与IO/5.对已不存在的文件执行写入操作.py b/5.文件与IO/5.对已不存在的文件执行写入操作.py new file mode 100644 index 0000000..cdc350d --- /dev/null +++ b/5.文件与IO/5.对已不存在的文件执行写入操作.py @@ -0,0 +1,17 @@ + + +if __name__ == "__main__": + # 如果想要将数据写入一个已经不在文件系统中的文件,可以使用open函数的x模式 + + try: + path = "5.文件与IO/4.bin_file.bin" + with open(path, 'xt') as f: + f.write("missing file") + # 可以看到,这个写入报了一个文件已存在的错误,如果我们用一个不存在的文件,那就会创建一个新文件成功写入 + except FileExistsError: + path = "5.文件与IO/5.missing_file.bin" + with open(path, 'xt') as f: + f.write("missing file") + + # 如果需要写入二进制文件,同理使用xb就行 + diff --git a/5.文件与IO/6.在字符串上执行IO操作.py b/5.文件与IO/6.在字符串上执行IO操作.py new file mode 100644 index 0000000..5180398 --- /dev/null +++ b/5.文件与IO/6.在字符串上执行IO操作.py @@ -0,0 +1,26 @@ +import io + +if __name__ == '__main__': + # io库里的StringIO和BytesIO提供了两个模拟文件的方法,本质应该是在内存里整了块缓冲区 + # StringIO提供了对字符串的虚拟IO,BytesIO则对应字节串 + # 这两个东西的操作和文件操作别无二致,就是不需要打开 + s = io.StringIO() + b = io.BytesIO() + + s.write("Hello_String_IO\n") + b.write(b'Hello BytesIO') + + print("This is POWER!!!!!", file=s, end='') + + print(s.getvalue()) + s.seek(0) + + print(s.read(4)) + + s.seek(0) + print(s.read()) + + print(b.getvalue()) + b.seek(0) + print(b.read(4)) + print(b.read()) diff --git a/5.文件与IO/7.读写压缩的数据文件.py b/5.文件与IO/7.读写压缩的数据文件.py new file mode 100644 index 0000000..835dc38 --- /dev/null +++ b/5.文件与IO/7.读写压缩的数据文件.py @@ -0,0 +1,18 @@ +import gzip, bz2 + +if __name__ == "__main__": + # 如果想要读写.gz和.bz2后缀的压缩文件,请使用上述两个包 + with gzip.open("file.gz", 'rt') as f: + text = f.read() + + + with bz2.open("file.bz2", 'rt') as f: + text2 = f.read() + + # 同样的,如果写入可以使用wt和wb来进行,这些包里的open函数支持和open函数相同的操作 + # 如果想要增加压缩比,那就需要调整compresslevel参数,最高是默认9,调低它以获取更高的性能 + + # 这些函数还支持对二进制打开的压缩文件做类似的管道操作 + f = open("somefile.gz", 'rb') + with gzip.open(f, 'rt') as g: + text3 = g.read() diff --git a/5.文件与IO/8.对固定大小的记录进行迭代.py b/5.文件与IO/8.对固定大小的记录进行迭代.py new file mode 100644 index 0000000..91472bb --- /dev/null +++ b/5.文件与IO/8.对固定大小的记录进行迭代.py @@ -0,0 +1,9 @@ +from functools import partial + +if __name__ == '__main__': + RECORD_SIZE = 32 + # partial函数主要起参数固定的作用,接受一个函数和一组预设参数,相当于给函数设置默认参数而不用修改函数 + with open('5.文件与IO/4.bin_file.bin', 'rb') as f: + records = partial(f.read, RECORD_SIZE) + for record in records(): + print(record, end='') diff --git a/5.文件与IO/9.bin_file.bin b/5.文件与IO/9.bin_file.bin new file mode 100644 index 0000000..1e3b6c0 --- /dev/null +++ b/5.文件与IO/9.bin_file.bin @@ -0,0 +1 @@ +Hello World \ No newline at end of file diff --git a/5.文件与IO/9.将二进制数据读到可变缓冲区中.py b/5.文件与IO/9.将二进制数据读到可变缓冲区中.py new file mode 100644 index 0000000..5703478 --- /dev/null +++ b/5.文件与IO/9.将二进制数据读到可变缓冲区中.py @@ -0,0 +1,44 @@ +import os.path +import io + +# 这是一个缓存区写入函数,目的是新建一个内存缓冲区,并把指定文件的内容写入 +def read_info_buffer(filename): + # 建立一个字节数组缓冲区 + buffer = bytearray(os.path.getsize(filename)) + with open(filename, 'rb') as file: + file.readinto(buffer) + + return buffer + +if __name__ == '__main__': + # with open('5.文件与IO/9.bin_file.bin', 'wb') as f: + # f.write(b'Hello World') + # 将文件内容写入缓冲区 + buf = read_info_buffer('5.文件与IO/9.bin_file.bin') + print(buf) + # 当然,缓冲区也可以有其他操作,比如切片 + buf[0: 5] = b'hello' + print(buf) + + # 当我们访问一个大文件时,如果里面分为2个字节的多块,就可以这样干 + record_size = 2 + buf2 = bytearray(record_size) + with open('5.文件与IO/9.bin_file.bin', 'rb') as f: + while True: + n = f.readinto(buf2) + print(buf2) + + if n < record_size: + break + + # 还有一个好玩的东西,内存映像memoryview,它允许我们直接操作对象的内存 + suka = bytearray(b'suka blyet') + # 获取suka的内存地址 + m = memoryview(suka) + # 将suka12~4位的地址给m2 + m2 = m[1:4] + print(suka) + # 对这些地址上的数据进行相应长度的修改 + m2[:] = b'abc' + # 可以看到这时候suka已经发生了变化 + print(suka) diff --git a/6.数据编码与处理/1.stocks.csv b/6.数据编码与处理/1.stocks.csv new file mode 100644 index 0000000..6a9992a --- /dev/null +++ b/6.数据编码与处理/1.stocks.csv @@ -0,0 +1 @@ +Symbol,Price,Date,Time,Change,Volume AA,39.48,6/11/2007,9:36am,-0.18,181800 AIG,71.38,6/11/2007,9:36am,-0.15,195500 AXP,62.58,6/11/2007,9:36am,-0.46,935000 BA,98.31,6/11/2007,9:36am,+0.12,104800 C,53.08,6/11/2007,9:36am,-0.25,360900 CAT,78.29,6/11/2007,9:36am,-0.23,225400 \ No newline at end of file diff --git a/6.数据编码与处理/1.读写CSV数据.py b/6.数据编码与处理/1.读写CSV数据.py new file mode 100644 index 0000000..67ed51b --- /dev/null +++ b/6.数据编码与处理/1.读写CSV数据.py @@ -0,0 +1,37 @@ +import csv +from collections import namedtuple + +if __name__ == "__main__": + # 想要读取csv数据,可以使用csv库 + with open("6.数据编码与处理/1.stocks.csv") as f: + f_csv = csv.reader(f) + headers = next(f_csv) + print(headers) + + for row in f_csv: + print(row) + + # 但是这样处理就非常不妙,所有的东西混在一起,变成奇奇怪怪的样子,不如使用named_tuple + f.seek(0) + + Row = namedtuple("Row", headers) + for row in f_csv: + print(row) + row = Row(*row) + print(row.Symbol) + + f.seek(0) + f_csv = csv.DictReader(f) + for row in f_csv: + print(row['Symbol']) + + # 如果想要插入一条数据,可以使用writer + rows = [ + ("BB", 123, "9/12/2024", "9:36AM", -12, 999) + ] + + with open("6.数据编码与处理/1.stocks.csv", 'w+') as f: + f_csv = csv.writer(f) + f_csv.writerows(rows) + + # 我的评价是不如pandas diff --git a/6.数据编码与处理/10.Base64编码和解码.py b/6.数据编码与处理/10.Base64编码和解码.py new file mode 100644 index 0000000..7eb2d5d --- /dev/null +++ b/6.数据编码与处理/10.Base64编码和解码.py @@ -0,0 +1,14 @@ +import base64 + +if __name__ == '__main__': + # 如果需要使用b64编码对二进制数据做编码和解码操作 + s = b'Hello World!' + # 可以使用base64的b64encoder和decoder + a = base64.b64encode(s) + print(a) + b = base64.b64decode(a) + print(b) + + # 和上一章一样,如果你需要unicode,需要再解码一次 + a = a.decode('ascii') + print(a) diff --git a/6.数据编码与处理/11.data.bin b/6.数据编码与处理/11.data.bin new file mode 100644 index 0000000..a540475 Binary files /dev/null and b/6.数据编码与处理/11.data.bin differ diff --git a/6.数据编码与处理/11.读写二进制结构的数组.py b/6.数据编码与处理/11.读写二进制结构的数组.py new file mode 100644 index 0000000..30f060c --- /dev/null +++ b/6.数据编码与处理/11.读写二进制结构的数组.py @@ -0,0 +1,39 @@ +from struct import Struct + +def write_records(records, format, f): + # 创建一个结构体 + record_struct = Struct(format) + # 将数组元素解包后打包放入文件 + for r in records: + f.write(record_struct.pack(*r)) + +# 简单来说,这是什么原理呢? +# 就是我知道我结构体的格式是什么样的,然后我造一个相同的结构体,然后把数据按照结构大小分块读出来,然后重新解包 +# 本质就是结构体的pack和unpack +def read_records(format, file): + record_struct = Struct(format) + # b''表示直到数据返回变成b''我们就收手 + chunks = iter(lambda: file.read(record_struct.size), b'') + + return (record_struct.unpack(chunk) for chunk in chunks) + +if __name__ == '__main__': + records = [ + (1, 2.3, 4.5), + (6, 7.8, 9.0), + (12, 13.4, 56.7) + ] + + # 首先我们造一个二进制结构数组文件 + # with open('6.数据编码与处理/11.data.bin', 'wb') as f: + # write_records(records, '', '!', '@')): + byte_order = format[0] + format = format[1:] + format = byte_order + format + setattr(self, field_name, StructField(format, offset)) + offset += struct.calcsize(format) + setattr(self, 'struct_size', offset) + + # 改进的structure类 + class Structure_v2(metaclass=StructureMeta): + def __init__(self, bytedata): + self._buffer = memoryview(bytedata) + + # 类的方法,在实例化以后才能被调用 + @classmethod + def from_file(cls, f): + return cls(f.read(cls.struct_size)) + + # 经过修改之后我们只需要告诉类字段名称和格式就行了 + class PolyHeader_v2(Structure_v2): + _fields_ = [ + ('', '!', '@')): + byte_order = format[0] + format = format[1:] + format = byte_order + format + setattr(self, field_name, StructField(format, offset)) + offset += struct.calcsize(format) + setattr(self, 'struct_size', offset) + + + # 改进的structure类 + class NestedStruct: + def __init__(self, name, struct_type, offset): + self.name = name + self.struct_type = struct_type + self.offset = offset + + def __get__(self, instance, cls): + if instance is None: + return self + else: + data = instance._buffer[self.offset: self.offset + self.struct_type.struct_size] + result = self.struct_type(data) + setattr(instance, self.name, result) + return result + + # 改进的structure类,基础方法在init里设置memoryview来进行懒加载 + class Structure_v3(metaclass=StructureMeta_v2): + def __init__(self, bytedata): + self._buffer = memoryview(bytedata) + + # 类的方法,在实例化以后才能被调用 + @classmethod + def from_file(cls, f): + return cls(f.read(cls.struct_size)) + + class Point(Structure_v3): + _fields_ = [ + ('Json + def serialize_instance(obj): + d = {'__class__': type(obj).__name__} + d.update(vars(obj)) + return d + + # JsonStr->类 + def unserialize_instance(dic): + cls_name = dic.pop('__class__', None) + if cls_name: + cls = classes[cls_name] + obj = cls.__new__(cls) + for k, v in dic.items(): + setattr(obj, k, v) + return obj + + else: + return dic + + p = Point(2, 3) + s = json.dumps(p, default=serialize_instance) + print(s) + a = json.loads(s, object_hook=unserialize_instance) + print(a.x, a.y) + + diff --git a/6.数据编码与处理/3.解析简单的XML文档.py b/6.数据编码与处理/3.解析简单的XML文档.py new file mode 100644 index 0000000..b2cdde2 --- /dev/null +++ b/6.数据编码与处理/3.解析简单的XML文档.py @@ -0,0 +1,17 @@ +from urllib.request import urlopen +from xml.etree.ElementTree import parse + +if __name__ == "__main__": + u = urlopen('http://planet.python.org/rss20.xml') + # 使用xml.etree.ElementTree的parse方法来解析文档 + doc = parse(u) + print(doc) + + # 可以使用.iterfind来对解析出来的文档进行迭代,找到所有指定名称的节点 + for item in doc.iterfind('channel/item'): + # 使用findtext在节点中寻找需要的数据 + title = item.findtext('title') + date = item.findtext('pubDate') + link = item.findtext('link') + + print(title, date, link, sep="\n") diff --git a/6.数据编码与处理/4.以增量方式解析大型XML文件.py b/6.数据编码与处理/4.以增量方式解析大型XML文件.py new file mode 100644 index 0000000..fd0320b --- /dev/null +++ b/6.数据编码与处理/4.以增量方式解析大型XML文件.py @@ -0,0 +1,45 @@ +from xml.etree.ElementTree import iterparse +from urllib.request import urlopen + + +# 在尤其关注节省内存时,牺牲运行速度,使用增量生成的方式读取XML文档 +def parse_and_remove(filename, path): + # 将节点路径划分 + path_parts = path.split('/') + # 通过迭代的方式解析XML文件 + doc = iterparse(filename, events=('start', 'end')) + # 跳过根节点 + next(doc) + + # 设置存储栈 + tag_stack = [] + elem_stack = [] + + for event, elem in doc: + # iterparse会输出event和elem元组,表示状态和元素 + if event == 'start': + # 如果检测状态是start就入栈 + tag_stack.append(elem.tag) + elem_stack.append(elem) + elif event == 'end': + # 如果检测状态是end就出栈 + # print(tag_stack, path_parts) + + # 如果路径和需要寻找的标签相同,就生成并从栈中抹除 + if tag_stack == path_parts: + yield elem + elem_stack[-2].remove(elem) + try: + tag_stack.pop() + elem_stack.pop() + except IndexError: + pass + + + +if __name__ == '__main__': + u = urlopen('http://planet.python.org/rss20.xml') + data = parse_and_remove(u, 'channel/item/title') + # print(data) + for d in data: + print(d.text) diff --git a/6.数据编码与处理/5.将字典转换为XML.py b/6.数据编码与处理/5.将字典转换为XML.py new file mode 100644 index 0000000..3bd1fcc --- /dev/null +++ b/6.数据编码与处理/5.将字典转换为XML.py @@ -0,0 +1,21 @@ +from xml.etree.ElementTree import Element + + +def dict_to_xml(tag, d): + # 创建一个element实例 + elem = Element(tag) + # 将字典键值对拆开填入 + for k, v in d.items(): + child = Element(k) + child.text = str(v) + elem.append(child) + + return elem + +if __name__ == '__main__': + s = {"name": 'GOOG', + "shares": 100, + "price": 490.1} + + e = dict_to_xml("stock", s) + print(e) diff --git a/6.数据编码与处理/6.test.xml b/6.数据编码与处理/6.test.xml new file mode 100644 index 0000000..c646ec1 --- /dev/null +++ b/6.数据编码与处理/6.test.xml @@ -0,0 +1,23 @@ + + + 14791 + Clark & Balmoral + + 22 + North Bound +
North Bound
+
+ 22 +
+        5 MIN
+        Howard
+        1378
+        22
+    
+
+        15 MIN
+        Howard
+        1867
+        22
+    
+
\ No newline at end of file diff --git a/6.数据编码与处理/6.test_result.xml b/6.数据编码与处理/6.test_result.xml new file mode 100644 index 0000000..0b108a1 --- /dev/null +++ b/6.数据编码与处理/6.test_result.xml @@ -0,0 +1,17 @@ + + + 14791 + Clark & Balmoral + This is a test
+        5 MIN
+        Howard
+        1378
+        22
+    
+
+        15 MIN
+        Howard
+        1867
+        22
+    
+
\ No newline at end of file diff --git a/6.数据编码与处理/6.解析、修改和重写XML.py b/6.数据编码与处理/6.解析、修改和重写XML.py new file mode 100644 index 0000000..090570d --- /dev/null +++ b/6.数据编码与处理/6.解析、修改和重写XML.py @@ -0,0 +1,22 @@ +from xml.etree.ElementTree import parse, Element + +if __name__ == '__main__': + doc = parse(r'6.数据编码与处理/6.test.xml') + # 得到XML的root + root = doc.getroot() + print(root) + + # 从root里移除一些从root里找到的节点 + root.remove(root.find('sri')) + root.remove(root.find('cr')) + + # 得到某个具体节点的下标 + print(root.getchildren().index(root.find('nm'))) + + # 创建一个节点,并在根节点下插入 + e = Element('spam') + e.text = 'This is a test' + root.insert(2, e) + + # 保存为XML文件 + doc.write(r'6.数据编码与处理/6.test_result.xml', xml_declaration=True) diff --git a/6.数据编码与处理/7.exp.xml b/6.数据编码与处理/7.exp.xml new file mode 100644 index 0000000..c1952ce --- /dev/null +++ b/6.数据编码与处理/7.exp.xml @@ -0,0 +1,14 @@ + + + David Beazley + + + + Hello World + + +

Hello World!

+ + +
+
\ No newline at end of file diff --git a/6.数据编码与处理/7.用命名空间来解析XML文档.py b/6.数据编码与处理/7.用命名空间来解析XML文档.py new file mode 100644 index 0000000..2b7dbbe --- /dev/null +++ b/6.数据编码与处理/7.用命名空间来解析XML文档.py @@ -0,0 +1,34 @@ +from xml.etree.ElementTree import parse + +class XMLNameSpace: + def __init__(self, **kwargs): + self.namespaces = {} + for name, url in kwargs.items(): + self.register(name, url) + + def register(self, name, url): + self.namespaces[name] = '{'+url+'}' + + def __call__(self, path): + return path.format_map(self.namespaces) + +if __name__ == '__main__': + # 如果是一个有命名空间的XML文件,那么很明显传统的读取方法会变得繁琐 + doc = parse('6.数据编码与处理/7.exp.xml') + print(doc.findtext('author')) + print(doc.find('content')) + + # 由于html不在全局命名空间下,所以找不到这个东西 + print(doc.find('content/html')) + + # 在指定了命名空间的所有层级下,都要使用{}来标识命名空间 + print(doc.find('content/{http://www.w3.org/1999/xhtml}html')) + # 记住,我说的是,所有层级 + # 不起效 + print(doc.find('content/{http://www.w3.org/1999/xhtml}html/head')) + # 正常生效 + print(doc.find('content/{http://www.w3.org/1999/xhtml}html/{http://www.w3.org/1999/xhtml}head')) + + # 这样就很甜蜜曹丹,好麻烦,而且还要手动输入命名空间,不如写个类让它自己解析了 + ns = XMLNameSpace(html="http://www.w3.org/1999/xhtml") + print(doc.find(ns('content/{html}html'))) diff --git a/6.数据编码与处理/8.db_test_sqlite3.db b/6.数据编码与处理/8.db_test_sqlite3.db new file mode 100644 index 0000000..a41d867 Binary files /dev/null and b/6.数据编码与处理/8.db_test_sqlite3.db differ diff --git a/6.数据编码与处理/8.同关系型数据库进行交互.py b/6.数据编码与处理/8.同关系型数据库进行交互.py new file mode 100644 index 0000000..b6e6ede --- /dev/null +++ b/6.数据编码与处理/8.同关系型数据库进行交互.py @@ -0,0 +1,33 @@ +import sqlite3 + +if __name__ == "__main__": + # 在Python中,数据库的输入和输出使用如下元组来表示 + stocks = [ + ('GooG', 100, 490.1), + ('AAPL', 50, 545.75), + ('FB', 150, 7.45), + ('HPQ', 75, 33.2) + ] + + # 首先我们需要创建一个数据库连接 + db = sqlite3.connect('8.db_test_sqlite3.db') + print(db) + + # 然后创建一个游标与数据库进行交互 + c = db.cursor() + # c.execute('create table portfolio (symbol text, shares integer, price real)') + # db.commit() + + # 使用execute和executemany命令与数据库进行交互 + # 务必使用?作为占位符让SQL执行替换,否则这就是SQL注入攻击的漏洞 + c.executemany('insert into portfolio values (?, ?, ?)', stocks) + db.commit() + + for row in db.execute('select * from portfolio'): + print(row) + + c.close() + db.close() + + # 好消息是,大部分的网络框架在需要数据库时都做了ORM封装,不用这么麻烦的去写代码调动数据库了 + diff --git a/6.数据编码与处理/9.编码和解码十六进制数字.py b/6.数据编码与处理/9.编码和解码十六进制数字.py new file mode 100644 index 0000000..2d2e52a --- /dev/null +++ b/6.数据编码与处理/9.编码和解码十六进制数字.py @@ -0,0 +1,21 @@ +import base64, binascii + +if __name__ == '__main__': + s = b'Hello World!' + # 将字节串以十六进制编码 + h = binascii.b2a_hex(s) + print(h) + + # 将数据从十六进制解码成字节串 + b = binascii.a2b_hex(h) + print(b) + + # 同理,在熟悉的base64模块我们也能实现同样的功能 + h = base64.b16encode(s) + print(h) + b = base64.b16decode(h) + print(b) + + # 个人感觉base64的API更加干净 + # 对了,如果想要将数据以unicode输出,可以使用一次decode + print(h.decode('ascii')) diff --git a/7.函数/1.编写可接受任意数量参数的函数.py b/7.函数/1.编写可接受任意数量参数的函数.py new file mode 100644 index 0000000..edc6966 --- /dev/null +++ b/7.函数/1.编写可接受任意数量参数的函数.py @@ -0,0 +1,16 @@ + + +# 要编写一个接受任意数量参数的函数,可以使用* +def avg(first, *rest): + return (first + sum(rest))/(len(rest) + 1) + +# 如果是关键字类型的参数,那就需要使用** +def make_element(name, value, **attrs): + for item in attrs.items(): + print("'{} = {}'".format(*item)) + return 0 + +# *与**有使用上的限制,它们必须在所有参数的最后,**必须在*后面 +def anyargs(*args, **kwargs): + print(args) + print(kwargs) \ No newline at end of file diff --git a/7.函数/10.1.补课,协程.py b/7.函数/10.1.补课,协程.py new file mode 100644 index 0000000..b62230d --- /dev/null +++ b/7.函数/10.1.补课,协程.py @@ -0,0 +1,24 @@ +# 这里我们要介绍一下协程的机制,协程是在单个线程内让两个函数交替执行的一种形式 +# 以著名问题生产者和消费者作为基底进行讨论 +def consumer(): + return_str = '' + while True: + print(1) + n = yield return_str + if not n: + return + print("消费者正在消费{}".format(n)) + return_str = "200 OK" + +def producer(c): + next(c) + n = 0 + while n < 5: + n = n + 1 + print("生产者正在生产{}".format(n)) + return_str = c.send(n) + print("消费者收到消息返回{}".format(return_str)) + c.close() + +con = consumer() +producer(con) diff --git a/7.函数/10.在回调函数中携带额外的状态.py b/7.函数/10.在回调函数中携带额外的状态.py new file mode 100644 index 0000000..b05899c --- /dev/null +++ b/7.函数/10.在回调函数中携带额外的状态.py @@ -0,0 +1,43 @@ +def apply_async(func, args, *, callback): + result = func(*args) + callback(result) + +def print_result(result): + print(result) + +def add(x, y): + return x + y + +# 如果只是简单的调用回调函数,那直接用就可以了 +apply_async(add, (1, 2), callback=print_result) +apply_async(add, ('hello', 'world'), callback=print_result) + +# 但是,如果我想要知道这个函数被调用了几次,携带这些保存的信息返回,可以这样改写: +def make_handler(): + sequence = 0 + def handler(result): + nonlocal sequence + sequence += 1 + print('[{}] Got: {}'.format(sequence, result)) + return handler +handler = make_handler() +apply_async(add, (1, 2), callback=handler) +apply_async(add, (1, 2), callback=handler) + + +# 当然我们也能用协程去搞定它: +# 协程的复习在10.1中,单步调试你就看懂了 +def make_handler_v2(): + sequence = 0 + while True: + result = yield + sequence += 1 + print('[{}] Got: {}'.format(sequence, result)) +handler = make_handler_v2() +next(handler) +apply_async(add, (1, 2), callback=handler.send) +apply_async(add, (1, 2), callback=handler.send) +apply_async(add, (1, 2), callback=handler.send) + + + diff --git a/7.函数/11.内联回调函数.py b/7.函数/11.内联回调函数.py new file mode 100644 index 0000000..238671f --- /dev/null +++ b/7.函数/11.内联回调函数.py @@ -0,0 +1,48 @@ +# 如果我们像上一节一样写一堆小函数在代码里,相信我你很快就会暴毙 +# 我们希望我们的内敛回调看起来比较正常 +# 我们有这样一个函数来调用回调: +def apply_async(func, args, *, callback): + result = func(*args) + callback(result) + +from queue import Queue +from functools import wraps + +class Async: + def __init__(self, func, args): + self.func = func + self.args = args + +def inline_async(func): + @wraps(func) + def wrapper(*args): + f = func(*args) + result_queue = Queue() + result_queue.put(None) + while True: + result = result_queue.get() + try: + a = f.send(result) + apply_async(a.func, a.args, callback=f.send) + except StopIteration: + break + return wrapper + +def add(x, y): + return x + y + +@inline_async +def test(): + r = yield Async(add, (2, 3)) + print(r) + r = yield Async(add, ('hello', 'world')) + print(r) + for n in range(5): + r = yield Async(add, (n, n)) + print(r) + print("Bye") + + +# 这个东西比较高级,一般不使用这种方法,会让代码可读性变差 +# 拒绝个人炫技,回归可读本质,从你我做起 +test() \ No newline at end of file diff --git a/7.函数/12.访问定义在闭包内的变量.py b/7.函数/12.访问定义在闭包内的变量.py new file mode 100644 index 0000000..95d9952 --- /dev/null +++ b/7.函数/12.访问定义在闭包内的变量.py @@ -0,0 +1,69 @@ +# 我们有时候可以用函数来拓展闭包,使闭包获得类似类的效果 +def sample(): + n = 0 + + def func(): + print("n=", n) + + def get_n(): + return n + + def set_n(value): + # nonlocal让我们可以访问闭包内的变量,有点类似类里的self + nonlocal n + n = value + # 我们能这么做的本质原因还是因为Python里的万物皆类特性 + func.get_n = get_n + func.set_n = set_n + return func + + +f = sample() +f() +# 可以看到这里并没有出现代码提示,因为基类没有包含这些东西,它们是在动态编译的时候加入的 +f.set_n(10) +f() +f.get_n() + +import sys +# 比如下面有个类 +class ClosureInstance: + def __init__(self, locals=None): + if locals is None: + locals = sys._getframe(1).f_locals + self.__dict__.update((key, value) for key, value in locals.items() if callable(value)) + + def __len__(self): + return self.__dict__['__len__']() + +# 这是用这个类实现的栈实例 +def Stack(): + items = [] + + def push(item): + items.append(item) + + def pop(): + items.pop() + + def __len__(): + return len(items) + + return ClosureInstance() + +# 这是正常的写法 +class Stack2: + def __init__(self): + self.items = [] + + def push(self, item): + self.items.append(item) + + def pop(self): + return self.items.pop() + + def __len__(self): + return len(self.items) + +# 从执行来说,闭包的写法比正常写法块8%,因为不需要访问额外的self变量 +# 不!要!这!么!做!除非有非得这样干的理由,否则别这样搞,老老实实写类,这样的闭包只有类的功能而没有继承多态这些类属性 \ No newline at end of file diff --git a/7.函数/2.编写只接受关键字参数的函数.py b/7.函数/2.编写只接受关键字参数的函数.py new file mode 100644 index 0000000..b6c2ca8 --- /dev/null +++ b/7.函数/2.编写只接受关键字参数的函数.py @@ -0,0 +1,13 @@ + + +# 在*参数和**参数之间,可以加入普通参数,这种普通参数被称为keyword_only参数 +def recv(maxsize, *, block): + print("recv msg") + pass + +# 这类参数只能被指定输入,示例如下: +recv(1024, True) # 报错TypeError +recv(1024, block=True) # 正确 + + + diff --git a/7.函数/3.将元数据信息附加到函数参数上.py b/7.函数/3.将元数据信息附加到函数参数上.py new file mode 100644 index 0000000..b9a1938 --- /dev/null +++ b/7.函数/3.将元数据信息附加到函数参数上.py @@ -0,0 +1,5 @@ +# 有时候我们会想要将函数参数加上类型提示来方便其他人快速熟悉函数,那么可以这样做: + +def test_function(x:int, y:int)->int: + x = x**2 + return x+y \ No newline at end of file diff --git a/7.函数/4.从函数中返回多个值.py b/7.函数/4.从函数中返回多个值.py new file mode 100644 index 0000000..c60f77c --- /dev/null +++ b/7.函数/4.从函数中返回多个值.py @@ -0,0 +1,6 @@ + +# 如果需要从函数中返回多个值,只需要返回一个元组即可 +def fun(): + return 1, 2, 3 + +a, b, c = fun() diff --git a/7.函数/5.定义带有默认参数的函数.py b/7.函数/5.定义带有默认参数的函数.py new file mode 100644 index 0000000..21ac115 --- /dev/null +++ b/7.函数/5.定义带有默认参数的函数.py @@ -0,0 +1,37 @@ +# 定义函数时,如果想要传入可选的参数,可以给参数加上默认值 +def span(a, b=42): + print(a, b) + +span(1) +span(1, 2) + +# 如果给参数指定默认值为None,那么该参数就可以传入可变容器 +def span_v2(a, b=None): + print(a, b) +span_v2(1, [1, 2]) + +# 但是如果你不想设置一个默认值,如果b没有传入参数那就提示,那么你需要给一个obj +_no_value = object() +def span_v3(a, b=_no_value): + if b is _no_value: + print("no b") + +span_v3(1) + +# 注意,函数的默认参数只会在函数首次被调用的时候绑定一次,函数第一次被调用后默认参数就不可变了 +x = 3 +def su_ka(a = x): + print(a) +su_ka(1) +x = 4 +su_ka() + +# 不要用【】这种地址型数据做默认参数,因为它真的可以在过程中被修改 +x = [] +def su_ka(a = x): + print(a) +su_ka() +x.append(1) +su_ka() + +# 上面使用object作为空参数传入也有这方面的考量,因为用户的输入是不可预知的,而obj几乎不可能被用户创建 diff --git a/7.函数/6.定义匿名或内联函数.py b/7.函数/6.定义匿名或内联函数.py new file mode 100644 index 0000000..1b8fe7d --- /dev/null +++ b/7.函数/6.定义匿名或内联函数.py @@ -0,0 +1,8 @@ +# 如果我们需要一个短小精悍的函数,那么lambda函数是唯一的选择 + +add = lambda x, y: x + y +print(add(2,3)) + +# 就比如在sort函数里 +names = ["David Beazley", "Brain Jones", "Raymond Hettinger", "Ned Batchelder"] +print(sorted(names, key=lambda name: name.split()[-1].lower())) diff --git a/7.函数/7.在匿名函数中绑定变量的值.py b/7.函数/7.在匿名函数中绑定变量的值.py new file mode 100644 index 0000000..394d347 --- /dev/null +++ b/7.函数/7.在匿名函数中绑定变量的值.py @@ -0,0 +1,30 @@ + + + +if __name__ == "__main__": + # lambda函数中的变量如果被赋值了是可以动态修改的,只要在每次调用之前修改,就能实现变化 + x = 1 + a = lambda y: x + y + print(a(10)) + x = x + 9 + print(a(10)) + + x = x + 10 + print(a(10)) + + + # 如果你希望和正常函数一样在定义的时候绑死变量的值,那么你需要这样做 + b = lambda y, t=x: t + y + print(b(10)) + x = x-1 + print(b(10)) + + # 比如有个比较聪明的函数 + func = [lambda x : x + n for n in range(5)] + for f in func: + print(f(0)) + + func2 = [lambda x, n=n: x + n for n in range(5)] + for f in func2: + print(f(0)) + diff --git a/7.函数/8.让有n个参数的可调用对象以较少的参数被调用.py b/7.函数/8.让有n个参数的可调用对象以较少的参数被调用.py new file mode 100644 index 0000000..81494f9 --- /dev/null +++ b/7.函数/8.让有n个参数的可调用对象以较少的参数被调用.py @@ -0,0 +1,31 @@ +import math +from functools import partial + + +# 假设我们有这样一个函数 +def spam(a, b, c, d): + print(a, b, c, d) + +if __name__ == '__main__': + # 这个函数的输入参数有一大堆,但是其中几个是固定的,那就有用了 + s1 = partial(spam, 1) #固定a=1 + s2 = partial(spam, d=2) # 固定d=2 + s3 = partial(spam, 3, 3, d=3) # 固定a=3 b=3 d=3 + + s1(2,3,4) + s2(1,2,3) + s3(1) + + # partial返回的是一个被填充了部分参数的函数,参数数量等于被包装的函数的剩余参数个数 + + # 这个函数可以用来适配一些参数数量受限的复用场景, 比如sorted函数的key参数仅支持单参数函数 + points = [(1, 2), (3, 4), (5, 6), (7, 8)] + + def dis (p1, p2): + x1, y1 = p1 + x2, y2 = p2 + return math.hypot(x2-x1, y2-y1) + + pt = (4, 3) + s = partial(dis, pt) + print(sorted(points, key=s)) diff --git a/7.函数/9.用函数替代只有单个方法的类.py b/7.函数/9.用函数替代只有单个方法的类.py new file mode 100644 index 0000000..4872f10 --- /dev/null +++ b/7.函数/9.用函数替代只有单个方法的类.py @@ -0,0 +1,16 @@ +from urllib.request import urlopen + +class UrlTemplate: + def __init__(self, template): + self.template = template + + def open(self, **kwargs): + return urlopen(self.template.format_map(kwargs)) + +# 对于这个类,我们可以使用闭包来进行重写 +def url_template(template): + def opener(**kwargs): + + return urlopen(template.format_map(kwargs)) + return opener + diff --git a/8.类与对象/1.修改实例的字符串表示.py b/8.类与对象/1.修改实例的字符串表示.py new file mode 100644 index 0000000..8396dad --- /dev/null +++ b/8.类与对象/1.修改实例的字符串表示.py @@ -0,0 +1,23 @@ +# 想要修改实例的字符串表示,可以在__str__和__repr__方法里做实现 +class Pair: + def __init__(self,a,b): + self.a = a + self.b = b + + def __repr__(self): + return 'Pair({0.a}, {0.b})'.format(self) + + def __str__(self): + return '({0.a}, {0.b})'.format(self) + +# format格式复习: {元组下标:填充元素 填充方法(<^>) 填充长度 数据格式(+-.nf d e % 等)} + +# 在下面的示例中,!r表示使用__repr__做输出,!s表示使用__str__做输出,这样就不用担心!s和!r顶掉数据格式化字符串的位置了 +p =Pair(3, 4) +print("P is {!r}".format(p)) +print("P is {!s}".format(p)) + +# 如果输出不太妙,或者没有输出的方法,就会用<>返回你一段文本,比如文件的返回 +path = "5.文件与IO/1.somefile.txt" +f = open(path,"r") +print(f) diff --git a/8.类与对象/10.让属性具有惰性求值能力.py b/8.类与对象/10.让属性具有惰性求值能力.py new file mode 100644 index 0000000..ae7af3a --- /dev/null +++ b/8.类与对象/10.让属性具有惰性求值能力.py @@ -0,0 +1,38 @@ +# 在之前的课程中,我们学会了给类的属性加property来让它们惰性求值 +# 但是我们想要它求了一次以后就把值储存起来,下次调用就可以节省算力了 +# 我们可以使用描述符类来完成这个操作 +class lazyProperty: + def __init__(self, func): + self.func = func + + def __get__(self, instance, cls): + if instance is None: + return self + else: + value = self.func(instance) + # 将原来类中的方法覆写删除,变成固定的数字 + setattr(instance, self.func.__name__, value) + return value + +# 我们可以这样使用它 +import math +class Circle: + def __init__(self, radius): + self.radius = radius + + @lazyProperty + def area(self): + print("computing area") + return math.pi * self.radius ** 2 + + @lazyProperty + def perimeter(self): + return 2 * self.radius + + +c = Circle(4) +print(c.area) +print(c.area) + +# 这样相当于将实例中的方法替换为某个可替换的值,代价就是属性不再由计算得出,仅作为数据读取 +# 这种方法可以用在一些只计算一次的实例上,来节省计算 diff --git a/8.类与对象/11.简化数据结构的初始化过程.py b/8.类与对象/11.简化数据结构的初始化过程.py new file mode 100644 index 0000000..1a02465 --- /dev/null +++ b/8.类与对象/11.简化数据结构的初始化过程.py @@ -0,0 +1,59 @@ +# 有时候你编写了一堆类当数据结构用,但是懒得写一堆init函数,这时候可以将初始化数据结构的init函数归纳到一个公共基类中 + +class Structure: + _fields = [] + def __init__(self, *args, **kwargs): + if len(args) > len(self._fields): + raise TypeError("Expect {} arguments, but only {}".format(len(self._fields), len(args))) + + # 处理顺序输入的参数 + for name, value in zip(self._fields, args): + setattr(self, name, value) + + # 处理关键字参数 + for name in args[len(self._fields):]: + setattr(self, name, kwargs.pop(name)) + + # 处理多余的关键字参数 + if kwargs: + raise TypeError("Invalid arguments {}".format(",".join(kwargs))) + + +class lazy: + def __init__(self, func): + self.func = func + + def __get__(self, instance, cls): + if instance is None: + return self + else: + value = self.func(instance) + setattr(instance, self.func.__name__, value) + return value + +# 这时候我们会发现你只需要指定参数就行了,异常的好使 +import math +if __name__ == '__main__': + + class Stock(Structure): + _fields = ["name", "shares", "price"] + __slots__ = _fields + + class Points(Structure): + _fields = ["x", "y"] + __slots__ = _fields + + + class Circle(Structure): + _fields = ["radius"] + __slots__ = _fields + @lazy + def area(self): + return math.pi * self.radius ** 2 + + s = Stock('ACME', 50, 91.1) + p = Points(2,3) + c = Circle(4.5) + print(c.area) + print(c.area) + diff --git a/8.类与对象/2.自定义字符串的输出格式.py b/8.类与对象/2.自定义字符串的输出格式.py new file mode 100644 index 0000000..7b5f153 --- /dev/null +++ b/8.类与对象/2.自定义字符串的输出格式.py @@ -0,0 +1,26 @@ +_formats = { + 'ymd': '{d.year}-{d.month}-{d.day}', + 'mdy': '{d.month}-{d.day}-{d.year}', + 'dym': '{d.day}-{d.month}-{d.year}' +} + +# 通过重写类的__format__方法,我们可以让类在被填充的时候变成任何样子 +class Date: + def __init__(self, year, month, day): + self.year = year + self.month = month + self.day = day + + def __format__(self, code): + if code == '': + code = 'ymd' + fmt = _formats[code] + return fmt.format(d=self) + +d = Date(2024, 11, 11) +format(d) +format(d, 'mdy') + +"The Date is {:ymd}".format(d) +"The Date is {:dym}".format(d) + diff --git a/8.类与对象/3.让对象支持上下文管理协议.py b/8.类与对象/3.让对象支持上下文管理协议.py new file mode 100644 index 0000000..687b60f --- /dev/null +++ b/8.类与对象/3.让对象支持上下文管理协议.py @@ -0,0 +1,58 @@ +# 有时候我们想让类支持with触发的上下文管理协议实现自动启动和关闭 +# 这时候就要实现类的__enter__和__exit__方法 + +from socket import socket, AF_INET, SOCK_STREAM +# 比如下面这个网络连接类 +class LazyConnection: + def __init__(self, address, family=AF_INET, type=SOCK_STREAM): + self.address = address + self.family = family + self.type = type + self.sock = None + + def __enter__(self): + if self.sock is not None: + raise RuntimeError + self.sock = socket(self.family, self.type) + self.sock.connect(self.address) + print("链接被拉起") + return self.sock + + def __exit__(self, exc_ty, exc_va, tb): + self.sock.close() + self.sock = None + print("链接关闭") + +from functools import partial +conn = LazyConnection(('www.python.org', 80)) +with conn as s: + # 链接被拉起 + s.send(b'GET /index.html HTTP/1.0\r\n') + s.send(b'Host: www.python.org\r\n') + s.send(b'\r\n') + resp = b''.join(iter(partial(s.recv, 1024), b'')) + # 链接被关闭 + +# 但是这个类只能使用单层的with,如果有多个with嵌套,它就会失效 +# 可以对原来的类进行一次修改,做一个工厂类,每次从一个栈中存取一个链接 +class LazyConnectionV2(): + def __init__(self, address, family=AF_INET, type=SOCK_STREAM): + self.address = address + self.family = family + self.type = type + # 单个的sock被替换成空列表 + self.connections = [] + + def __enter__(self): + # 建立一个socket链接 + sock = socket(self.family, self.type) + sock.connect(self.address) + # 将链接压入栈中 + self.connections.append(sock) + print("链接被拉起") + return sock + + def __exit__(self, exc_ty, exc_va, tb): + # 将最近被压入栈的链接弹出并关闭 + self.connections.pop().close() + print("链接关闭") \ No newline at end of file diff --git a/8.类与对象/4.创建大量实例时如何节省内存.py b/8.类与对象/4.创建大量实例时如何节省内存.py new file mode 100644 index 0000000..182006e --- /dev/null +++ b/8.类与对象/4.创建大量实例时如何节省内存.py @@ -0,0 +1,11 @@ +# 如果你需要创建大量的实例,那么你可以在类的定义中增加__slots__属性来让类不创建__dict__字典 + +class Date: + __slots__ = ('year', 'month', 'day') + def __init__(self, year, month, day): + self.year = year + self.month = month + self.day = day + +# 这会让类围绕着__slots__属性进行构建,副作用就是这个类被写死了,我们无法为它的实例添加新的属性 +# 使用了__slots__的类会失去多重继承的用法,请确保只在被当作数据结构的类中使用该方法 \ No newline at end of file diff --git a/8.类与对象/5.将名称封装到类中.py b/8.类与对象/5.将名称封装到类中.py new file mode 100644 index 0000000..0ade7a3 --- /dev/null +++ b/8.类与对象/5.将名称封装到类中.py @@ -0,0 +1,46 @@ +# 有时候我们需要一些private属性,但是python在类中并没有做访问控制 +# 我们遵循一些基本的编程规则来让大家达成共识 +# 1.别碰单下划线开头的东西 +class A: + def __init__(self): + self._internal=0 + self.public = 1 + + def public_method(self): + print("It's a public method") + + def _private_method(self): + print("It's a private method") + +# 这是人为制约的标准,请务必遵守它,访问_变量的行为是粗鲁的 +# 如果你想要让某个方法在继承时不被覆写,那么可以使用__ +# 在继承时,__开头的方法会被重整成_类名__方法名的形式 + +class B: + def __init__(self): + self.__private=0 + + def __private_method(self): + print("这是B的私有方法") + + def public_method(self): + print("这是B的公有方法") + +class C(B): + def __init__(self): + super().__init__() + self.__private = 1 + + # 重新定义C的方法 + def __private_method(self): + print("这是C的私有方法") + + # 可以看到B的私有方法没有被覆写,而是被重整成了_B__private_method + def public_method(self): + self.__private_method() + super()._B__private_method() +c = C() +c.public_method() + +# 总结,如果只是私有名称,那么可以使用_;如果私有名称还需要对子类隐藏,那就使用__ +# 在与保留关键字冲突时,可以使用 命名_ 的方式来避免名称冲突同时与私有数据区分开 diff --git a/8.类与对象/6.创建可管理的属性.py b/8.类与对象/6.创建可管理的属性.py new file mode 100644 index 0000000..15ea71f --- /dev/null +++ b/8.类与对象/6.创建可管理的属性.py @@ -0,0 +1,104 @@ +# 如果需要自定义对属性的访问,最好的办法就是将属性定义为一个property + +# 在这个类中,我们将first_name的getter方法作为一个property +# 在初始化调用时,其实调用了setter方法,跳过了first_name直接访问了_first_name +class Person: + def __init__(self, first_name): + self.first_name = first_name + + @property + def first_name(self): + return self._first_name + + @first_name.setter + def first_name(self, first_name): + if not isinstance(first_name, str): + raise TypeError('First name must be str') + self._first_name = first_name + + @first_name.deleter + def first_name(self): + raise AttributeError('First name cannot be deleted') + +# property的重要特性就是根据访问的不同形式,自动触发getter setter deleter +p = Person("Susan") +print(p.first_name) +p.first_name = "blyet" +print(p.first_name) +# del p.first_name + +# 对于已经存在的get和set方法,也可以使用property变成内置形式 +class Person2: + def __init__(self, first_name): + self.set_first_name(first_name) + + def get_first_name(self): + return self._first_name + + def set_first_name(self, first_name): + if not isinstance(first_name, str): + raise TypeError('First name must be str') + self._first_name = first_name + + def delete_first_name(self): + raise AttributeError('First name cannot be deleted') + + name = property(get_first_name, set_first_name, delete_first_name) + +p = Person2("Susan") +print(p.name) +p.name = "blyet" +print(p.name) +# del p.name + +# 这种方法一般用于确定要在访问属性的时候对它进行一些额外操作的时候,其他场景下尽量不要这么做,这会让程序变慢 + +# property也可以用来做一些类似懒加载的实时计算属性 +import math +class Circle: + def __init__(self, radius): + self.radius = radius + + @property + def area(self): + return math.pi * self.radius ** 2 + + @property + def perimeter(self): + return 2 * self.radius + +c = Circle(5) +print(c.radius) +print(c.area) +print(c.perimeter) + +# 虽然这样确实很优雅,但是在Python被大型程序集成的时候,还是使用传统getter和setter好 +# 另外,不要在类里写大量的property!!!!,这会让代码膨胀且可读性变差,会变得难以维护,像下面这样 +class Person3: + def __init__(self, first_name, last_name): + self.first_name = first_name + self.last_name = last_name + + @property + def first_name(self): + return self._first_name + + @first_name.setter + def first_name(self, first_name): + if not isinstance(first_name, str): + raise TypeError('First name must be str') + + self._first_name = first_name + + # 不要这样重复写property! + @property + def last_name(self): + return self._last_name + + @last_name.setter + def last_name(self, last_name): + if not isinstance(last_name, str): + raise TypeError('Last name must be str') + self._last_name = last_name + + diff --git a/8.类与对象/7.调用父类中的方法.py b/8.类与对象/7.调用父类中的方法.py new file mode 100644 index 0000000..c2cb4c7 --- /dev/null +++ b/8.类与对象/7.调用父类中的方法.py @@ -0,0 +1,119 @@ +# 要调用父类中的方法,可以使用super完成 +class A: + def spam(self): + print('A.spam') + +class B(A): + def spam(self): + print('B.spam') + super().spam() + +# super的另一种常用方法是调用父类的__init__方法来确保父类被正确初始化了 +class A: + def __init__(self): + self.x = 0 + +class B(A): + def __init__(self): + super().__init__() + self.y = 1 + +# 还有一种情况就是,你覆写了类中的方法,此时你可以调用super函数来使用原方法 +class Proxy: + def __init__(self, obj): + self._obj = obj + + def __getattr__(self, name): + return getattr(self._obj, name) + + def __setattr__(self, name, value): + if name.startwith('_'): + super().__setattr__(name, value) + else: + setattr(self._obj, name, value) + +# Python中的类继承 +# 有些人有坏习惯,在子类中直接调用父类的方法,这在大部分时候能行 +class Base: + def __init__(self): + print('Base.__init__') + +class A(Base): + def __init__(self): + Base.__init__(self) + print('A.__init__') + +# 但是如果这涉及到多重继承,那就会导致暴毙现象 +class BaseV2: + def __init__(self): + print('Base.__init__') + +class A1(BaseV2): + def __init__(self): + BaseV2.__init__(self) + print('A1.__init__') + +class A2(BaseV2): + def __init__(self): + BaseV2.__init__(self) + print('A2.__init__') + +class B1(A1, A2): + def __init__(self): + A1.__init__(self) + A2.__init__(self) + print('B1.__init__') + +# 上面的继承关系就是标准的钻石继承,如果运行代码就会出现神奇魔法 +b1 = B1() +# 哇!Base的__init__函数被调用了两次!这在一些场景下可能会导致麻烦,但如果使用super(),那就不会有这么多事了 + +class BaseV3: + def __init__(self): + print('Base.__init__') + +class B1(BaseV3): + def __init__(self): + super().__init__() + print('B1.__init__') + +class B2(BaseV3): + def __init__(self): + super().__init__() + print('B2.__init__') + +class C1(B1, B2): + def __init__(self): + super().__init__() + print('C1.__init__') +# 这样的继承是健康的,每个init函数只被调用了一次 +c1 = C1() + +# 这是为啥嘞?每个类Python都会计算得出一个方法解析顺序列表MRO,在这个列表中会简单的对所有基类进行线性排列 +print(C1.__mro__) +# MRO列表又是怎么确定的呢?Python使用了一种叫C3线性化处理的技术,这是一种归并排序,且满足三个约束 +# 1.先检查子类再检查父类; 2.多个父类时按照列表顺序依次检查; 3.如果下一个待选的类出现了两个合法的选择,就从第一个父类中选取 +# C3有点麻烦,但是计算的时候可以使用Python2.2更新的新式类MRO方法: +# 从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个,之后再根据去重前类的出现顺序排序 + +# 使用super函数时,会从MRO列表中的下一个类中搜索方法,只要每一个重定义过的方法都使用了super(),super就能保证让这个方法只被调用一次 +# super(a, b)函数的本质是获取类a在类b的MRO列表中a的下一个类 +# 但是这样也可能出现以下意想不到的孝bug +class D: + def spam(self): + print('D.spam') + super().spam() + +class D1: + def spam(self): + print('D1.spam') + +class D2(D, D1): + pass + +d = D2() +d.spam() + +# 这是由于MRO列表导致的super函数bug,这三个类的MRO列表应该是[D2, D, D1], super去找了D1中的方法 +# 要避免遇到麻烦,最好遵守下面两点 +# 1.确保所有的类中都实现了相同签名的调用; 2.确保你要调用的方法在顶层被实现(例子中为D2) diff --git a/8.类与对象/8.在子类中扩展属性.py b/8.类与对象/8.在子类中扩展属性.py new file mode 100644 index 0000000..ab01ac3 --- /dev/null +++ b/8.类与对象/8.在子类中扩展属性.py @@ -0,0 +1,89 @@ +# 我们有时候想在子类中定义一些父类没有的属性进行属性拓展 +class Person: + def __init__(self, name): + self.name = name + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + if not isinstance(name, str): + raise TypeError('name must be str') + else: + self._name = name + + @name.deleter + def name(self): + raise AttributeError('name cannot be deleted') + +# 我们从Person中继承,并对name属性的功能进行拓展 +class SubPerson(Person): + @property + def name(self): + print("Getting name...") + return super().name + + @name.setter + def name(self, value): + print("Setting name to {}...".format(value)) + super(SubPerson, SubPerson).name.__set__(self, value) + + @name.deleter + def name(self): + print("Deleting name...") + super(SubPerson, SubPerson).name.__delete__(self) + +# s = SubPerson("Guido") +# s.name +# s.name = "Larry" +# s.name = 42 + + +# 如果只是想拓展属性的其中一个方法,可以使用如下代码实现 +# 只修改setter +class SubPerson2(Person): + # 指定父类中属性的某种方法 + @Person.name.setter + def name(self, value): + print("Setting name to {}...".format(value)) + super(SubPerson2, SubPerson2).name.__set__(self, value) + +s2 = SubPerson2("Guido2") +s2.name = "Larry" + +#只修改getter +class SubPerson3(Person): + # 我们也可以这样操作 + @Person.name.getter + def name(self): + print('Getting name .....') + return super().name + +s3 = SubPerson3("Guido3") +s3.name +s3.name = "Larry" +s3.name + +# Python3让我们太舒服了,super的本质是super(类名, self),这些参数被省略了,它的意思其实是从self的MRO列表里寻找类名的下一个位置 +# 所以这里的 super(SubPerson, SubPerson).name 其实是在SubPerson的MRO列表中寻找SubPerson的下一个类,用途是找到Person + +# 万万记住,这种方式一次只能修改一个方法,如果一次修改多个,生效的只有最后被定义的那个 +#比如这里生效的只有setter +class SubPerson4(Person): + # 我们也可以这样操作 + @Person.name.getter + def name(self): + print('Getting name .....') + return super(SubPerson4, SubPerson4).name.__get__(self) + + @Person.name.setter + def name(self, value): + print("Setting name to {}...".format(value)) + super(SubPerson4, SubPerson4).name.__set__(self, value) + +s4 = SubPerson4("Guido4") +s4.name +s4.name = "Larry" +s4.name diff --git a/8.类与对象/9.创建一种新形式的类属性或实例属性.py b/8.类与对象/9.创建一种新形式的类属性或实例属性.py new file mode 100644 index 0000000..c14e4f6 --- /dev/null +++ b/8.类与对象/9.创建一种新形式的类属性或实例属性.py @@ -0,0 +1,65 @@ +# 如果想要创建一个新形式的实例属性,可以以描述符类的形式定义其功能 +# 所谓描述符就是以特殊方法__get__; __set__; __delete__的形式实现了三个核心属性的访问操作 +class Integer: + # 存储名称 + def __init__(self,name): + self.name = name + + # 如果没有输入目标对象,就返回自身,如果输入了获取目标对象的__dict__属性里key是name的条目 + def __get__(self, instance, cls): + # 这里的描述比较复杂 + # ,如果instance是空的,说明访问的是类,那么我们就要返回类本身 + # 如果instance不是空的,那说明是实例对象,需要返回实例的字段 + if instance is None: + return self + else: + return instance.__dict__[self.name] + + # 如果设置输入不是整数,就报错.否则往目标对象的__dict__属性中写入{name: value} + def __set__(self, instance, value): + if not isinstance(value, int): + raise TypeError('value must be an integer') + print("Set {} to {}".format(self.name, value)) + instance.__dict__[self.name] = value + + # 将key为name的属性从目标对象的__dict__中移除 + def __delete__(self, instance): + del instance.__dict__[self.name] + +class Point: + # 先设置x和y(其实是初始化self.x和self.y) + x = Integer('x') + y = Integer('y') + + # 实例化的时候调用 + def __init__(self,x,y): + # 调用Integer的__set__方法来给元素赋值 + self.x = x + self.y = y + +p = Point(2,3) +p.x +p.y +# 很明显,如果我首先初始化一个b,然后对b使用__set__方法指定p作为插入对象,那这个类就有麻烦了 +b = Integer('b') +print("\n{:-^18}".format("这里有脏东西")) +b.__set__(p, 3) +print("{:-^18}\n".format("这里有脏东西")) +# 这说明一件事,如果对实例的__dict__属性进行操作,那么我们就可以搞到一个动态类 + +# 初始化一定要在实例化之前进行,否则就只能用拙劣的__set__方法了,这就比较丑陋了 +class Point: + # 实例化的时候调用 + def __init__(self, x, y): + # 先设置x和y + self.x = Integer('x') + self.y = Integer('y') + # 调用Integer的__set__方法来给元素赋值 + self.x.__set__(self, x) + self.y.__set__(self, y) + +p = Point(2,3) +p.x +p.y + +# 当然,这样做会很爽,但如果你只需要给某个特定类中的一种属性加东西,最好还是使用property重写