數(shù)據(jù)結(jié)構(gòu) 各種查找算法

CD-Python-JY-1809班項(xiàng)目階段教學(xué)內(nèi)容

開(kāi)篇 - 就業(yè)形勢(shì)分析

  1. 就業(yè)方向

    • Python后端開(kāi)發(fā)工程師(Python基礎(chǔ)添寺、Django防症、Flask珠月、Tornado扩淀、Sanic、RESTful啤挎、MySQL驻谆、Redis卵凑、MongoDB、ElasticSearch/Solr)
      • Web應(yīng)用服務(wù)器 / 游戲后端服務(wù)器 / 移動(dòng)端數(shù)據(jù)接口 / 系統(tǒng)支撐平臺(tái)
    • Python爬蟲(chóng)開(kāi)發(fā)工程師(Python基礎(chǔ)胜臊、常用標(biāo)準(zhǔn)庫(kù)和三方庫(kù)勺卢、Scrapy/PySpider、Selenium/Appium象对、Redis黑忱、MySQL/MongoDB、前端相關(guān)知識(shí)勒魔、HTTP(S)甫煞、TCP/IP、Charles/Fiddler/Wireshark)
    • Python量化交易開(kāi)發(fā)工程師(扎實(shí)的Python功底冠绢、數(shù)據(jù)結(jié)構(gòu)和算法抚吠、金融知識(shí)、數(shù)字貨幣)
    • Python數(shù)據(jù)分析工程師(Python基礎(chǔ)弟胀、NumPy/SciPy楷力、Pandas、Matplotlib孵户、機(jī)器學(xué)習(xí)算法)
    • Python自動(dòng)化測(cè)試工程師(Python基礎(chǔ)弥雹、軟件測(cè)試基礎(chǔ)、Linux延届、Shell剪勿、Selenium / Robot Framework、JMeter / LoadRunner / QTP方庭、CI)
    • Python自動(dòng)化運(yùn)維工程師(Python基礎(chǔ)厕吉、Linux、Shell械念、Docker头朱、paramiko、Fabric龄减、Ansible项钮、Saltstack、Puppet希停、PlayBook烁巫、Zabbix)
    • Python云平臺(tái)開(kāi)發(fā)工程師(扎實(shí)的Python功底、OpenStack宠能、CloudStack亚隙、Ovirt、KVM违崇、微服務(wù)架構(gòu)阿弃、Docker诊霹、K8S)
  2. 面試加分項(xiàng)

    • 有自己的Github開(kāi)源項(xiàng)目和博客。

    • 有分布式項(xiàng)目/微服務(wù)架構(gòu)相關(guān)經(jīng)驗(yàn)渣淳。(Nginx脾还、LVS、Keepalived入愧、Zookeeper鄙漏、Docker)

    • 有項(xiàng)目性能調(diào)優(yōu)和安全相關(guān)經(jīng)驗(yàn)。(AB砂客、WebBench泥张、SysBench、JMeter鞠值、LoadRunner媚创、QTP)

    • 有使用行業(yè)工具和中間件的經(jīng)驗(yàn)。(Redis彤恶、FastDFS钞钙、RabbitMQ、Zabbix声离、Ansible芒炼、Nagios、ElasticSearch/Solr)

    • 熟悉前端開(kāi)發(fā)相關(guān)的知識(shí)术徊。(jQuery本刽、Bootstrap、Vue.js赠涮、AngularJS子寓、React)

    • 有其他語(yǔ)言開(kāi)發(fā)經(jīng)驗(yàn)(項(xiàng)目技術(shù)棧遷移能力)。(Java笋除、C/C++)

    • 有大數(shù)據(jù)開(kāi)發(fā)相關(guān)經(jīng)驗(yàn)斜友。(HDFS、YARN垃它、MapReduce鲜屏、HBase、Hive国拇、Mahout洛史、Pig、Spark贝奇、ZooKeeper)

      [圖片上傳失敗...(image-cc06ef-1558321309426)]

第01-03天:白板編程練習(xí)

所謂白板編程練習(xí)就是在紙上寫(xiě)代碼虹菲,這個(gè)是大多數(shù)面試的必要環(huán)節(jié),也是最容易被忽略的東西掉瞳。下面羅列的內(nèi)容是面試的筆試環(huán)節(jié)中出現(xiàn)頻率較高的問(wèn)題毕源,同時(shí)對(duì)相關(guān)知識(shí)點(diǎn)進(jìn)行了梳理。

  1. 數(shù)據(jù)結(jié)構(gòu)和算法

    • 算法:解決問(wèn)題的方法和步驟

    • 評(píng)價(jià)算法的好壞:漸近時(shí)間復(fù)雜度和漸近空間復(fù)雜度陕习。

    • 漸近時(shí)間復(fù)雜度的大O標(biāo)記:

      • O(c) - 常量時(shí)間復(fù)雜度 - 布隆過(guò)濾器 / 哈希存儲(chǔ)
      • O(log_2n) - 對(duì)數(shù)時(shí)間復(fù)雜度 - 折半查找(二分查找)
      • O(n) - 線性時(shí)間復(fù)雜度 - 順序查找 / 桶排序
      • O(n*log_2n) - 對(duì)數(shù)線性時(shí)間復(fù)雜度 - 高級(jí)排序算法(歸并排序霎褐、快速排序)
      • O(n^2) - 平方時(shí)間復(fù)雜度 - 簡(jiǎn)單排序算法(選擇排序、插入排序该镣、冒泡排序)
      • O(n^3) - 立方時(shí)間復(fù)雜度 - Floyd算法 / 矩陣乘法運(yùn)算
      • O(2^n) - 幾何級(jí)數(shù)時(shí)間復(fù)雜度 - 漢諾塔
      • O(n!) - 階乘時(shí)間復(fù)雜度 - 旅行經(jīng)銷商問(wèn)題 - NP

      [圖片上傳失敗...(image-baa54a-1558321309426)]

      [圖片上傳失敗...(image-146654-1558321309426)]

    • 排序算法(選擇冻璃、冒泡和歸并)和查找算法(順序和折半)

      def select_sort(origin_items, comp=lambda x, y: x < y):
          """簡(jiǎn)單選擇排序"""
          items = origin_items[:]
          for i in range(len(items) - 1):
              min_index = i
              for j in range(i + 1, len(items)):
                  if comp(items[j], items[min_index]):
                      min_index = j
              items[i], items[min_index] = items[min_index], items[i]
          return items
      
      def bubble_sort(origin_items, comp=lambda x, y: x > y):
          """高質(zhì)量冒泡排序(攪拌排序)"""
          items = origin_items[:]
          for i in range(len(items) - 1):
              swapped = False
              for j in range(i, len(items) - 1 - i):
                  if comp(items[j], items[j + 1]):
                      items[j], items[j + 1] = items[j + 1], items[j]
                      swapped = True
              if swapped:
                  swapped = False
                  for j in range(len(items) - 2 - i, i, -1):
                      if comp(items[j - 1], items[j]):
                          items[j], items[j - 1] = items[j - 1], items[j]
                          swapped = True
              if not swapped:
                  break
          return items
      
      def merge_sort(items, comp=lambda x, y: x <= y):
          """歸并排序(分治法)"""
          if len(items) < 2:
              return items[:]
          mid = len(items) // 2
          left = merge_sort(items[:mid], comp)
          right = merge_sort(items[mid:], comp)
          return merge(left, right, comp)
      
      
      def merge(items1, items2, comp):
          """合并(將兩個(gè)有序的列表合并成一個(gè)有序的列表)"""
          items = []
          index, index2 = 0, 0
          while index1 < len(items1) and index2 < len(items2):
              if comp(items1[index1], items2[index2]):
                  items.append(items1[index1])
                  index1 += 1
              else:
                  items.append(items2[index2])
                  index2 += 1
          items += items1[index1:]
          items += items2[index2:]
          return items
      
      def seq_search(items, key):
          """順序查找"""
          for index, item in enumerate(items):
              if item == key:
                  return index
          return -1
      
      def bin_search(items, key):
          """折半查找"""
          start, end = 0, len(items) - 1
          while start <= end:
              mid = (start + end) // 2
              if key > items[mid]:
                  start = mid + 1
              elif key < items[mid]:
                  end = mid - 1
              else:
                  return mid
          return -1
      
    • 使用生成式(推導(dǎo)式)語(yǔ)法生成列表、集合和字典损合。

      例子1:將一個(gè)句子中每個(gè)單詞放到列表中且每個(gè)單詞首字母大寫(xiě)省艳。

      sentence = 'i love this world'
      [x.capitalize() for x in sentence.split()]
      

      例子2:用字典中股票價(jià)格大于100元的股票構(gòu)造一個(gè)新的字典。

      prices = {
          'AAPL': 191.88,
          'GOOG': 1186.96,
          'IBM': 149.24,
          'ORCL': 48.44,
          'ACN': 166.89,
          'FB': 208.09,
          'SYMC': 21.29
      }
      prices2 = {key: value for key, value in prices.items() if value > 100}
      print(prices2)
      

      例子3:嵌套的列表 - 通過(guò)列表保存多個(gè)學(xué)生多門課程的成績(jī)嫁审。

      names = ['關(guān)羽', '張飛', '趙云', '馬超', '黃忠']
      courses = ['語(yǔ)文', '數(shù)學(xué)', '英語(yǔ)']
      # 錄入五個(gè)學(xué)生三門課程的成績(jī)
      # 錯(cuò)誤的做法 - 參考http://pythontutor.com/visualize.html#mode=edit
      # scores = [[0] * len(courses)] * len(names)
      # 正確的做法
      scores = [[0] * len(courses) for _ in range(len(names))]
      for row, name in enumerate(names):
          for col, course in enumerate(courses):
              scores[row][col] = float(input(f'請(qǐng)輸入{name}的{course}成績(jī): '))
              print(scores)
      

      Python Tutor - VISUALIZE CODE AND GET LIVE HELP

    • heapq(優(yōu)先隊(duì)列)跋炕、itertools(迭代工具)等的用法

      """
      從列表中找出最大的或最小的N個(gè)元素
      """
      import heapq
      
      list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92]
      list2 = [
          {'name': 'IBM', 'shares': 100, 'price': 91.1},
          {'name': 'AAPL', 'shares': 50, 'price': 543.22},
          {'name': 'FB', 'shares': 200, 'price': 21.09},
          {'name': 'HPQ', 'shares': 35, 'price': 31.75},
          {'name': 'YHOO', 'shares': 45, 'price': 16.35},
          {'name': 'ACME', 'shares': 75, 'price': 115.65}
      ]
      print(heapq.nlargest(3, list1))
      print(heapq.nsmallest(3, list1))
      print(heapq.nlargest(2, list2, key=lambda x: x['price']))
      print(heapq.nlargest(2, list2, key=lambda x: x['shares']))
      
      """
      迭代工具 - 排列 / 組合 / 笛卡爾積
      """
      import itertools
      
      itertools.permutations('ABCD')
      itertools.combinations('ABCDE', 3)
      itertools.product('ABCD', '123')
      
    • collections模塊下的高性能容器

      • deque:與list類似,但底層基于雙向鏈表實(shí)現(xiàn)律适。當(dāng)需要在中間或頭部插入元素時(shí)辐烂,deque比list快得多;當(dāng)需要進(jìn)行隨機(jī)訪問(wèn)時(shí)捂贿,deque比list要慢纠修。
      • defaultdict:與dict類似,但是可以為新的鍵指定默認(rèn)值的創(chuàng)建工廠厂僧,避免編寫(xiě)額外的代碼來(lái)初始化鍵值對(duì)扣草,比dict的setdefault方法更為高效。
      • namedtuple:與元組類似颜屠,但是可以為每個(gè)成員指定名字辰妙。
      from collections import namedtuple
      
      
      Card = namedtuple('Card', ['suite', 'face'])
      card = Card('紅桃', 5)
      print(card.suite, card.face)
      
      """
      找出序列中出現(xiàn)次數(shù)最多的元素 - Counter類
      """
      from collections import Counter
      
      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'
      ]
      counter = Counter(words)
      print(counter.most_common(3))
      
      """
      可以指定元素順序的字典 - OrderedDict 
      """
      from collections import OrderedDict
      
      
      fruits = {'banana': 3, 'apple': 4, 'pear': 1, 'orange': 2}
      print(OrderedDict(sorted(fruits.items(), key=lambda x: x[0])))
      print(OrderedDict(sorted(fruits.items(), key=lambda x: x[1])))
      print(OrderedDict(sorted(fruits.items(), key=lambda x: len(x[0]))))
      
    • 常用算法:

      • 窮舉法 - 又稱為暴力破解法,對(duì)所有的可能性進(jìn)行驗(yàn)證汽纤,直到找到正確答案上岗。
      • 貪婪法 - 在對(duì)問(wèn)題求解時(shí),總是做出在當(dāng)前看來(lái)是最好的選擇蕴坪,不追求最優(yōu)解肴掷,快速找到滿意解。
      • 分治法 - 把一個(gè)復(fù)雜的問(wèn)題分成兩個(gè)或更多的相同或相似的子問(wèn)題背传,再把子問(wèn)題分成更小的子問(wèn)題呆瞻,直到可以直接求解的程度,最后將子問(wèn)題的解進(jìn)行合并得到原問(wèn)題的解径玖。
      • 回溯法 - 回溯法又稱為試探法痴脾,按選優(yōu)條件向前搜索,當(dāng)搜索到某一步發(fā)現(xiàn)原先選擇并不優(yōu)或達(dá)不到目標(biāo)時(shí)梳星,就退回一步重新選擇赞赖。
      • 動(dòng)態(tài)規(guī)劃 - 基本思想也是將待求解問(wèn)題分解成若干個(gè)子問(wèn)題滚朵,先求解并保存這些子問(wèn)題的解,避免產(chǎn)生大量的重復(fù)運(yùn)算前域。

      窮舉法例子:百錢百雞和五人分魚(yú)辕近。

      # 公雞5元一只 母雞3元一只 小雞1元三只
      # 用100元買100只雞 問(wèn)公雞/母雞/小雞各多少只
      for x in range(20):
          for y in range(33):
              z = 100 - x - y
              if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0:
                  print(x, y, z)
      
      # A、B匿垄、C移宅、D、E五人在某天夜里合伙捕魚(yú) 最后疲憊不堪各自睡覺(jué)
      # 第二天A第一個(gè)醒來(lái) 他將魚(yú)分為5份 扔掉多余的1條 拿走自己的一份
      # B第二個(gè)醒來(lái) 也將魚(yú)分為5份 扔掉多余的1條 拿走自己的一份
      # 然后C椿疗、D漏峰、E依次醒來(lái)也按同樣的方式分魚(yú) 問(wèn)他們至少捕了多少條魚(yú)
      fish = 1
      while True:
          total = fish
          enough = True
          for _ in range(5):
              if (total - 1) % 5 == 0:
                  total = (total - 1) // 5 * 4
              else:
                  enough = False
                  break
          if enough:
              print(fish)
              break
          fish += 1
      

      貪婪法例子:假設(shè)小偷有一個(gè)背包,最多能裝20公斤贓物届榄,他闖入一戶人家浅乔,發(fā)現(xiàn)如下表所示的物品。很顯然痒蓬,他不能把所有物品都裝進(jìn)背包童擎,所以必須確定拿走哪些物品,留下哪些物品攻晒。

      名稱 價(jià)格(美元) 重量(kg)
      電腦 200 20
      收音機(jī) 20 4
      175 10
      花瓶 50 2
      書(shū) 10 1
      油畫(huà) 90 9
      """
      貪婪法:在對(duì)問(wèn)題求解時(shí)顾复,總是做出在當(dāng)前看來(lái)是最好的選擇,不追求最優(yōu)解鲁捏,快速找到滿意解芯砸。
      輸入:
      20 6
      電腦 200 20
      收音機(jī) 20 4
      鐘 175 10
      花瓶 50 2
      書(shū) 10 1
      油畫(huà) 90 9
      """
      class Thing(object):
          """物品"""
      
          def __init__(self, name, price, weight):
              self.name = name
              self.price = price
              self.weight = weight
      
          @property
          def value(self):
              """價(jià)格重量比"""
              return self.price / self.weight
      
      
      def input_thing():
          """輸入物品信息"""
          name_str, price_str, weight_str = input().split()
          return name_str, int(price_str), int(weight_str)
      
      
      def main():
          """主函數(shù)"""
          max_weight, num_of_things = map(int, input().split())
          all_things = []
          for _ in range(num_of_things):
              all_things.append(Thing(*input_thing()))
          all_things.sort(key=lambda x: x.value, reverse=True)
          total_weight = 0
          total_price = 0
          for thing in all_things:
              if total_weight + thing.weight <= max_weight:
                  print(f'小偷拿走了{(lán)thing.name}')
                  total_weight += thing.weight
                  total_price += thing.price
          print(f'總價(jià)值: {total_price}美元')
      
      
      if __name__ == '__main__':
          main()
      

      分治法例子:快速排序

      """
      快速排序 - 選擇樞軸對(duì)元素進(jìn)行劃分给梅,左邊都比樞軸小右邊都比樞軸大
      """
      def quick_sort(origin_items, comp=lambda x, y: x <= y):
          items = origin_items[:]
          _quick_sort(items, 0, len(items) - 1, comp)
          return items
      
      
      def _quick_sort(items, start, end, comp):
          if start < end:
              pos = _partition(items, start, end, comp)
              _quick_sort(items, start, pos - 1, comp)
              _quick_sort(items, pos + 1, end, comp)
      
      
      def _partition(items, start, end, comp):
          pivot = items[end]
          i = start - 1
          for j in range(start, end):
              if comp(items[j], pivot):
                  i += 1
                  items[i], items[j] = items[j], items[i]
          items[i + 1], items[end] = items[end], items[i + 1]
          return i + 1
      

      回溯法例子:騎士巡邏假丧。

      """
      遞歸回溯法:叫稱為試探法,按選優(yōu)條件向前搜索动羽,當(dāng)搜索到某一步包帚,發(fā)現(xiàn)原先選擇并不優(yōu)或達(dá)不到目標(biāo)時(shí),就退回一步重新選擇运吓,比較經(jīng)典的問(wèn)題包括騎士巡邏渴邦、八皇后和迷宮尋路等。
      """
      import sys
      import time
      
      SIZE = 5
      total = 0
      
      
      def print_board(board):
          for row in board:
              for col in row:
                  print(str(col).center(4), end='')
              print()
      
      
      def patrol(board, row, col, step=1):
          if row >= 0 and row < SIZE and \
              col >= 0 and col < SIZE and \
              board[row][col] == 0:
              board[row][col] = step
              if step == SIZE * SIZE:
                  global total
                  total += 1
                  print(f'第{total}種走法: ')
                  print_board(board)
              patrol(board, row - 2, col - 1, step + 1)
              patrol(board, row - 1, col - 2, step + 1)
              patrol(board, row + 1, col - 2, step + 1)
              patrol(board, row + 2, col - 1, step + 1)
              patrol(board, row + 2, col + 1, step + 1)
              patrol(board, row + 1, col + 2, step + 1)
              patrol(board, row - 1, col + 2, step + 1)
              patrol(board, row - 2, col + 1, step + 1)
              board[row][col] = 0
      
      
      def main():
          board = [[0] * SIZE for _ in range(SIZE)]
          patrol(board, SIZE - 1, SIZE - 1)
      
      
      if __name__ == '__main__':
          main()
      

      動(dòng)態(tài)規(guī)劃例子1:斐波拉切數(shù)列拘哨。(不使用動(dòng)態(tài)規(guī)劃將會(huì)是幾何級(jí)數(shù)復(fù)雜度)

      """
      動(dòng)態(tài)規(guī)劃 - 適用于有重疊子問(wèn)題和最優(yōu)子結(jié)構(gòu)性質(zhì)的問(wèn)題
      使用動(dòng)態(tài)規(guī)劃方法所耗時(shí)間往往遠(yuǎn)少于樸素解法(用空間換取時(shí)間)
      """
      def fib(num, temp={}):
          """用遞歸計(jì)算Fibonacci數(shù)"""
          if num in (1, 2):
              return 1
          try:
              return temp[num]
          except KeyError:
              temp[num] = fib(num - 1) + fib(num - 2)
              return temp[num]
      
      """
      Python中的functools模塊有一個(gè)lru_cache裝飾器谋梭,也可以做類似的事情
      如果愿意也可以自己寫(xiě)一個(gè)類似的裝飾器來(lái)做同樣的事情,大家可以嘗試一下
      """
      from functools import lru_cache
      
      
      @lru_cache(maxsize=None)
      def fib(num):
          if num in (1, 2):
              return 1
          return fib(num - 1) + fib(num - 2)
      
      
      # 查看緩存命中次數(shù)
      # CacheInfo(hits=237, misses=120, maxsize=None, currsize=120)
      fib.cache_info()
      

      動(dòng)態(tài)規(guī)劃例子2:子列表元素之和的最大值倦青。(使用動(dòng)態(tài)規(guī)劃可以避免二重循環(huán))

      說(shuō)明:子列表指的是列表中索引(下標(biāo))連續(xù)的元素構(gòu)成的列表瓮床;列表中的元素是int類型,可能包含正整數(shù)、0隘庄、負(fù)整數(shù)踢步;程序輸入列表中的元素,輸出子列表元素求和的最大值峭沦,例如:

      輸入:1 -2 3 5 -3 2

      輸出:8

      輸入:0 -2 3 5 -1 2

      輸出:9

      輸入:-9 -2 -3 -5 -3

      輸出:-2

      def main():
          items = list(map(int, input().split()))
          size = len(items)
          overall, partial = {}, {}
          overall[size - 1] = partial[size - 1] = items[size - 1]
          for i in range(size - 2, -1, -1):
              partial[i] = max(items[i], partial[i + 1] + items[i])
              overall[i] = max(partial[i], overall[i + 1])
          print(overall[0])
      
      
      if __name__ == '__main__':
          main()
      
  2. 函數(shù)的使用方式

    • 將函數(shù)視為“一等公民”

      • 函數(shù)可以賦值給變量
      • 函數(shù)可以作為函數(shù)的參數(shù)
      • 函數(shù)可以作為函數(shù)的返回值
    • 高階函數(shù)的用法(filter贾虽、map以及它們的替代品)

      items1 = list(map(lambda x: x ** 2, filter(lambda x: x % 2, range(1, 10))))
      items2 = [x ** 2 for x in range(1, 10) if x % 2]
      
    • 位置參數(shù)逃糟、可變參數(shù)吼鱼、關(guān)鍵字參數(shù)、命名關(guān)鍵字參數(shù)

    • 參數(shù)的元信息(代碼可讀性問(wèn)題)

    • 匿名函數(shù)和內(nèi)聯(lián)函數(shù)的用法(lambda函數(shù))

      例子:一行代碼實(shí)現(xiàn)從m到n的整數(shù)(n \ge m)求和绰咽。

      (lambda m, n: functools.reduce(int.__add__, range(m, n + 1)))(1, 100)
      

      例子:一行代碼實(shí)現(xiàn)求階乘菇肃。

      (lambda num: functools.reduce(int.__mul__, range(1, num + 1), 1))(5)
      
    • 閉包和作用域問(wèn)題

      • Python搜索變量的LEGB順序(Local\rightarrowEmbedded\rightarrowGlobal\rightarrowBuilt-in)

      • globalnonlocal關(guān)鍵字的作用

        global:聲明或定義全局變量(要么直接使用現(xiàn)有的全局作用域的變量,要么定義一個(gè)變量放到全局作用域)取募。

        nonlocal:聲明使用嵌套作用域的變量(嵌套作用域必須存在該變量琐谤,否則報(bào)錯(cuò))。

    • 裝飾器函數(shù)(使用裝飾器和取消裝飾器)

      例子:輸出函數(shù)執(zhí)行時(shí)間的裝飾器玩敏。

      def record_time(func):
          """自定義裝飾函數(shù)的裝飾器"""
          
          @wraps(func)
          def wrapper(*args, **kwargs):
              start = time()
              result = func(*args, **kwargs)
              print(f'{func.__name__}: {time() - start}秒')
              return result
              
          return wrapper
      

      如果裝飾器不希望跟print函數(shù)耦合斗忌,可以編寫(xiě)帶參數(shù)的裝飾器。

      from functools import wraps
      from time import time
      
      
      def record(output):
          """自定義帶參數(shù)的裝飾器"""
         
         def decorate(func):
             
             @wraps(func)
             def wrapper(*args, **kwargs):
                 start = time()
                 result = func(*args, **kwargs)
                 output(func.__name__, time() - start)
                 return result
                  
             return wrapper
         
         return decorate
      
      from functools import wraps
      from time import time
      
      
      class Record():
          """自定義裝飾器類(通過(guò)__call__魔術(shù)方法使得對(duì)象可以當(dāng)成函數(shù)調(diào)用)"""
      
          def __init__(self, output):
              self.output = output
      
          def __call__(self, func):
      
              @wraps(func)
              def wrapper(*args, **kwargs):
                  start = time()
                  result = func(*args, **kwargs)
                  self.output(func.__name__, time() - start)
                  return result
      
              return wrapper
      

      說(shuō)明:由于對(duì)帶裝飾功能的函數(shù)添加了@wraps裝飾器旺聚,可以通過(guò)func.__wrapped__方式獲得被裝飾之前的函數(shù)或類來(lái)取消裝飾器的作用织阳。

      例子:用裝飾器來(lái)實(shí)現(xiàn)單例模式。

      from functools import wraps
      
      
      def singleton(cls):
          """裝飾類的裝飾器"""
          instances = {}
      
          @wraps(cls)
          def wrapper(*args, **kwargs):
              if cls not in instances:
                  instances[cls] = cls(*args, **kwargs)
              return instances[cls]
      
          return wrapper
      
      
      @singleton
      class President():
          """總統(tǒng)(單例類)"""
          pass
      

      說(shuō)明:上面的代碼中用到了閉包(closure)砰粹,不知道你是否已經(jīng)意識(shí)到了唧躲。還沒(méi)有一個(gè)小問(wèn)題就是,上面的代碼并沒(méi)有實(shí)現(xiàn)線程安全的單例碱璃,如果要實(shí)現(xiàn)線程安全的單例應(yīng)該怎么做呢弄痹?

      from functools import wraps
      
      
      def singleton(cls):
          """線程安全的單例裝飾器"""
          instances = {}
          locker = Lock()
      
          @wraps(cls)
          def wrapper(*args, **kwargs):
              if cls not in instances:
                  with locker:
                      if cls not in instances:
                          instances[cls] = cls(*args, **kwargs)
              return instances[cls]
      
          return wrapper
      
  3. 面向?qū)ο笙嚓P(guān)知識(shí)

    • 三大支柱:封裝、繼承嵌器、多態(tài)

      例子:工資結(jié)算系統(tǒng)肛真。

      """
      月薪結(jié)算系統(tǒng) - 部門經(jīng)理每月15000 程序員每小時(shí)200 銷售員1800底薪加銷售額5%提成
      """
      from abc import ABCMeta, abstractmethod
      
      
      class Employee(metaclass=ABCMeta):
          """員工(抽象類)"""
      
          def __init__(self, name):
              self.name = name
      
          @abstractmethod
          def get_salary(self):
              """結(jié)算月薪(抽象方法)"""
              pass
      
      
      class Manager(Employee):
          """部門經(jīng)理"""
      
          def get_salary(self):
              return 15000.0
      
      
      class Programmer(Employee):
          """程序員"""
      
          def __init__(self, name, working_hour=0):
              self.working_hour = working_hour
              super().__init__(name)
      
          def get_salary(self):
              return 200.0 * self.working_hour
      
      
      class Salesman(Employee):
          """銷售員"""
      
          def __init__(self, name, sales=0.0):
              self.sales = sales
              super().__init__(name)
      
          def get_salary(self):
              return 1800.0 + self.sales * 0.05
      
      
      emp_types = {'M': Manager, 'P': Programmer, 'S': Salesman}
      
      
      class EmployeeFactory(object):
          """創(chuàng)建員工的工廠(工廠模式 - 通過(guò)工廠實(shí)現(xiàn)對(duì)象使用者和對(duì)象之間的解耦合)"""
      
          @staticmethod
          def create(emp_type, *args, **kwargs):
              """創(chuàng)建員工"""
              emp = None
              if emp_type in emp_types:
                  emp = emp_types[emp_type](*args, **kwargs)
              return emp
      
      if __name__ == '__main__':
          emps = [
              EmployeeFactory.create('M', '曹操'), 
              EmployeeFactory.create('P', '荀彧', 120),
              EmployeeFactory.create('P', '郭嘉', 85), 
              EmployeeFactory.create('S', '典韋', 123000),
          ]
          for emp in emps:
              print('%s: %.2f元' % (emp.name, emp.get_salary()))
      
    • 類與類之間的關(guān)系

      • is-a關(guān)系:繼承
      • has-a關(guān)系:關(guān)聯(lián) / 聚合 / 合成
      • use-a關(guān)系:依賴
    • 對(duì)象的復(fù)制(深復(fù)制/深拷貝/深度克隆和淺復(fù)制/淺拷貝/影子克隆)

      from copy import copy, deepcopy
      
      items0 = [1, [2, 3], [[4, 5, 6], [7, 8, 9]]]
      
      # 看看下面這些輸出前后兩個(gè)值是否相等
      items1 = items0
      print(id(items0), id(items1))
      
      items2 = items1[:]
      print(id(items1), id(items2))
      print(id(items1[0]), id(items2[0]))
      print(id(items1[1]), id(items2[1]))
      print(id(items1[2][0]), id(items2[2][0]))
      print('-' * 25) 
      
      items3 = copy(items1)
      print(id(items1), id(items3))
      print(id(items1[0]), id(items3[0]))
      print(id(items1[1]), id(items3[1]))
      print(id(items1[2][0]), id(items3[2][0]))
      print('-' * 25) 
      
      items4 = deepcopy(items1)
      print(id(items1), id(items4))
      print(id(items1[0]), id(items4[0]))
      print(id(items1[1]), id(items4[1]))
      print(id(items1[2][0]), id(items4[2][0]))
      
    • 垃圾回收爽航、循環(huán)引用和弱引用

      Python使用了自動(dòng)化內(nèi)存管理蚓让,這種管理機(jī)制以引用計(jì)數(shù)為基礎(chǔ),同時(shí)也引入了標(biāo)記-清除分代收集兩種機(jī)制為輔的策略岳掐。

      typedef struct_object {
          /* 引用計(jì)數(shù) */
          int ob_refcnt;
          /* 對(duì)象指針 */
          struct_typeobject *ob_type;
      } PyObject;
      
      /* 增加引用計(jì)數(shù)的宏定義 */
      #define Py_INCREF(op)   ((op)->ob_refcnt++)
      /* 減少引用計(jì)數(shù)的宏定義 */
      #define Py_DECREF(op) \ //減少計(jì)數(shù)
          if (--(op)->ob_refcnt != 0) \
              ; \
          else \
              __Py_Dealloc((PyObject *)(op))
      

      導(dǎo)致引用計(jì)數(shù)+1的情況:

      • 對(duì)象被創(chuàng)建凭疮,例如a = 23
      • 對(duì)象被引用,例如b = a
      • 對(duì)象被作為參數(shù)串述,傳入到一個(gè)函數(shù)中执解,例如f(a)
      • 對(duì)象作為一個(gè)元素,存儲(chǔ)在容器中,例如list1 = [a, a]

      導(dǎo)致引用計(jì)數(shù)-1的情況:

      • 對(duì)象的別名被顯式銷毀衰腌,例如del a
      • 對(duì)象的別名被賦予新的對(duì)象新蟆,例如a = 24
      • 一個(gè)對(duì)象離開(kāi)它的作用域,例如f函數(shù)執(zhí)行完畢時(shí)右蕊,f函數(shù)中的局部變量(全局變量不會(huì))
      • 對(duì)象所在的容器被銷毀琼稻,或從容器中刪除對(duì)象

      引用計(jì)數(shù)可能會(huì)導(dǎo)致循環(huán)引用問(wèn)題,而循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄露饶囚,如下面的代碼所示帕翻。為了解決這個(gè)問(wèn)題,Python中引入了“標(biāo)記-清除”和“分代收集”萝风。在創(chuàng)建一個(gè)對(duì)象的時(shí)候嘀掸,對(duì)象被放在第一代中,如果在第一代的垃圾檢查中對(duì)象存活了下來(lái)规惰,該對(duì)象就會(huì)被放到第二代中睬塌,同理在第二代的垃圾檢查中對(duì)象存活下來(lái),該對(duì)象就會(huì)被放到第三代中歇万。

      # 循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄露 - Python除了引用技術(shù)還引入了標(biāo)記清理和分代回收
      # 在Python 3.6以前如果重寫(xiě)__del__魔術(shù)方法會(huì)導(dǎo)致循環(huán)引用處理失效
      # 如果不想造成循環(huán)引用可以使用弱引用
      list1 = []
      list2 = [] 
      list1.append(list2)
      list2.append(list1)
      

      以下情況會(huì)導(dǎo)致垃圾回收:

      • 調(diào)用gc.collect()
      • gc模塊的計(jì)數(shù)器達(dá)到閥值
      • 程序退出

      如果循環(huán)引用中兩個(gè)對(duì)象都定義了__del__方法揩晴,gc模塊不會(huì)銷毀這些不可達(dá)對(duì)象,因?yàn)間c模塊不知道應(yīng)該先調(diào)用哪個(gè)對(duì)象的__del__方法贪磺,這個(gè)問(wèn)題在Python 3.6中得到了解決硫兰。

      也可以通過(guò)weakref模塊構(gòu)造弱引用的方式來(lái)解決循環(huán)引用的問(wèn)題。

    • 魔法屬性和方法(請(qǐng)參考《Python魔法方法指南》)

      有幾個(gè)小問(wèn)題請(qǐng)大家思考:

      • 自定義的對(duì)象能不能使用運(yùn)算符做運(yùn)算缘挽?
      • 自定義的對(duì)象能不能放到set中瞄崇?能去重嗎?
      • 自定義的對(duì)象能不能作為dict的鍵壕曼?
      • 自定義的對(duì)象能不能使用上下文語(yǔ)法苏研?
    • 混入(Mixin)

      例子:自定義字典限制只有在指定的key不存在時(shí)才能在字典中設(shè)置鍵值對(duì)。

      class SetOnceMappingMixin():
          """自定義混入類"""
          __slots__ = ()
      
          def __setitem__(self, key, value):
              if key in self:
                  raise KeyError(str(key) + ' already set')
              return super().__setitem__(key, value)
      
      
      class SetOnceDict(SetOnceMappingMixin, dict):
          """自定義字典"""
          pass
      
      
      my_dict= SetOnceDict()
      try:
          my_dict['username'] = 'jackfrued'
          my_dict['username'] = 'hellokitty'
      except KeyError:
          pass
      print(my_dict)
      
    • 元編程和元類

      例子:用元類實(shí)現(xiàn)單例模式腮郊。

      import threading
      
      
      class SingletonMeta(type):
          """自定義元類"""
      
          def __init__(cls, *args, **kwargs):
              cls.__instance = None
              cls.__lock = threading.Lock()
              super().__init__(*args, **kwargs)
      
          def __call__(cls, *args, **kwargs):
              if cls.__instance is None:
                  with cls.__lock:
                      if cls.__instance is None:
                          cls.__instance = super().__call__(*args, **kwargs)
              return cls.__instance
      
      
      class President(metaclass=SingletonMeta):
          """總統(tǒng)(單例類)"""
          pass
      
    • 面向?qū)ο笤O(shè)計(jì)原則

      • 單一職責(zé)原則 (SRP)- 一個(gè)類只做該做的事情(類的設(shè)計(jì)要高內(nèi)聚)
      • 開(kāi)閉原則 (OCP)- 軟件實(shí)體應(yīng)該對(duì)擴(kuò)展開(kāi)發(fā)對(duì)修改關(guān)閉
      • 依賴倒轉(zhuǎn)原則(DIP)- 面向抽象編程(在弱類型語(yǔ)言中已經(jīng)被弱化)
      • 里氏替換原則(LSP) - 任何時(shí)候可以用子類對(duì)象替換掉父類對(duì)象
      • 接口隔離原則(ISP)- 接口要小而專不要大而全(Python中沒(méi)有接口的概念)
      • 合成聚合復(fù)用原則(CARP) - 優(yōu)先使用強(qiáng)關(guān)聯(lián)關(guān)系而不是繼承關(guān)系復(fù)用代碼
      • 最少知識(shí)原則(迪米特法則摹蘑,LoD)- 不要給沒(méi)有必然聯(lián)系的對(duì)象發(fā)消息

      說(shuō)明:上面加粗的字母放在一起稱為面向?qū)ο蟮?strong>SOLID原則。

    • GoF設(shè)計(jì)模式

      • 創(chuàng)建型模式:?jiǎn)卫伞⒐S衅鹿、建造者、原型
      • 結(jié)構(gòu)型模式:適配器过咬、門面(外觀)大渤、代理
      • 行為型模式:迭代器、觀察者掸绞、狀態(tài)泵三、策略

      例子:可插拔的哈希算法。

      class StreamHasher():
          """哈希摘要生成器(策略模式)"""
      
          def __init__(self, alg='md5', size=4096):
              self.size = size
              alg = alg.lower()
              self.hasher = getattr(__import__('hashlib'), alg.lower())()
      
          def __call__(self, stream):
              return self.to_digest(stream)
      
          def to_digest(self, stream):
              """生成十六進(jìn)制形式的摘要"""
              for buf in iter(lambda: stream.read(self.size), b''):
                  self.hasher.update(buf)
              return self.hasher.hexdigest()
      
      
      def main():
          """主函數(shù)"""
          hasher1 = StreamHasher()
          with open('Python-3.7.1.tgz', 'rb') as stream:
              print(hasher1.to_digest(stream))
          hasher2 = StreamHasher('sha1')
          with open('Python-3.7.1.tgz', 'rb') as stream:
              print(hasher2(stream))
      
      
      if __name__ == '__main__':
          main()
      
  4. 迭代器和生成器

    • 和迭代器相關(guān)的魔術(shù)方法(__iter____next__

    • 兩種創(chuàng)建生成器的方式(生成器表達(dá)式和yield關(guān)鍵字)

      def fib(num):
          """生成器"""
          a, b = 0, 1
          for _ in range(num):
              a, b = b, a + b
              yield a
         
         
      class Fib(object):
          """迭代器"""
          
          def __init__(self, num):
              self.num = num
              self.a, self.b = 0, 1
              self.idx = 0
         
          def __iter__(self):
              return self
      
          def __next__(self):
              if self.idx < self.num:
                  self.a, self.b = self.b, self.a + self.b
                  self.idx += 1
                  return self.a
              raise StopIteration()
      
  5. 并發(fā)編程

    Python中實(shí)現(xiàn)并發(fā)編程的三種方案:多線程、多進(jìn)程和異步I/O烫幕。并發(fā)編程的好處在于可以提升程序的執(zhí)行效率以及改善用戶體驗(yàn)俺抽;壞處在于并發(fā)的程序不容易開(kāi)發(fā)和調(diào)試,同時(shí)對(duì)其他程序來(lái)說(shuō)它并不友好较曼。

    • 多線程:Python中提供了Thread類并輔以Lock磷斧、Condition、Event捷犹、Semaphore和Barrier弛饭。Python中有GIL來(lái)防止多個(gè)線程同時(shí)執(zhí)行本地字節(jié)碼,這個(gè)鎖對(duì)于CPython是必須的伏恐,因?yàn)镃Python的內(nèi)存管理并不是線程安全的孩哑,因?yàn)镚IL的存在多線程并不能發(fā)揮CPU的多核特性。

      """
      面試題:進(jìn)程和線程的區(qū)別和聯(lián)系翠桦?
      進(jìn)程 - 操作系統(tǒng)分配內(nèi)存的基本單位 - 一個(gè)進(jìn)程可以包含一個(gè)或多個(gè)線程
      線程 - 操作系統(tǒng)分配CPU的基本單位
      并發(fā)編程(concurrent programming)
      1. 提升執(zhí)行性能 - 讓程序中沒(méi)有因果關(guān)系的部分可以并發(fā)的執(zhí)行
      2. 改善用戶體驗(yàn) - 讓耗時(shí)間的操作不會(huì)造成程序的假死
      """
      import glob
      import os
      import threading
      
      from PIL import Image
      
      PREFIX = 'thumbnails'
      
      
      def generate_thumbnail(infile, size, format='PNG'):
          """生成指定圖片文件的縮略圖"""
         file, ext = os.path.splitext(infile)
         file = file[file.rfind('/') + 1:]
         outfile = f'{PREFIX}/{file}_{size[0]}_{size[1]}.{ext}'
         img = Image.open(infile)
         img.thumbnail(size, Image.ANTIALIAS)
         img.save(outfile, format)
      
      
      def main():
          """主函數(shù)"""
         if not os.path.exists(PREFIX):
             os.mkdir(PREFIX)
         for infile in glob.glob('images/*.png'):
             for size in (32, 64, 128):
                  # 創(chuàng)建并啟動(dòng)線程
                 threading.Thread(
                     target=generate_thumbnail, 
                     args=(infile, (size, size))
                 ).start()
                 
      
      if __name__ == '__main__':
         main()
      

      多個(gè)線程競(jìng)爭(zhēng)資源的情況

      """
      多線程程序如果沒(méi)有競(jìng)爭(zhēng)資源處理起來(lái)通常也比較簡(jiǎn)單
      當(dāng)多個(gè)線程競(jìng)爭(zhēng)臨界資源的時(shí)候如果缺乏必要的保護(hù)措施就會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)亂
      說(shuō)明:臨界資源就是被多個(gè)線程競(jìng)爭(zhēng)的資源
      """
      import time
      import threading
      
      from concurrent.futures import ThreadPoolExecutor
      
      
      class Account(object):
          """銀行賬戶"""
      
          def __init__(self):
              self.balance = 0.0
              self.lock = threading.Lock()
      
          def deposit(self, money):
              # 通過(guò)鎖保護(hù)臨界資源
              with self.lock:
                  new_balance = self.balance + money
                  time.sleep(0.001)
                  self.balance = new_balance
      
      
      class AddMoneyThread(threading.Thread):
          """自定義線程類"""
      
          def __init__(self, account, money):
              self.account = account
              self.money = money
              # 自定義線程的初始化方法中必須調(diào)用父類的初始化方法
              super().__init__()
      
          def run(self):
              # 線程啟動(dòng)之后要執(zhí)行的操作
              self.account.deposit(self.money)
      
      def main():
          """主函數(shù)"""
          account = Account()
          # 創(chuàng)建線程池
          pool = ThreadPoolExecutor(max_workers=10)
          futures = []
          for _ in range(100):
              # 創(chuàng)建線程的第1種方式
              # threading.Thread(
              #     target=account.deposit, args=(1, )
              # ).start()
              # 創(chuàng)建線程的第2種方式
              # AddMoneyThread(account, 1).start()
              # 創(chuàng)建線程的第3種方式
              # 調(diào)用線程池中的線程來(lái)執(zhí)行特定的任務(wù)
              future = pool.submit(account.deposit, 1)
              futures.append(future)
          # 關(guān)閉線程池
          pool.shutdown()
          for future in futures:
              future.result()
          print(account.balance)
      
      
      if __name__ == '__main__':
          main()
      

      修改上面的程序,啟動(dòng)5個(gè)線程向賬戶中存錢胳蛮,5個(gè)線程從賬戶中取錢销凑,取錢時(shí)如果余額不足就暫停線程進(jìn)行等待。為了達(dá)到上述目標(biāo)仅炊,需要對(duì)存錢和取錢的線程進(jìn)行調(diào)度斗幼,在余額不足時(shí)取錢的線程暫停并釋放鎖,而存錢的線程將錢存入后要通知取錢的線程抚垄,使其從暫停狀態(tài)被喚醒蜕窿。可以使用threading模塊的Condition來(lái)實(shí)現(xiàn)線程調(diào)度呆馁,該對(duì)象也是基于鎖來(lái)創(chuàng)建的桐经,代碼如下所示:

      """
      多個(gè)線程競(jìng)爭(zhēng)一個(gè)資源 - 保護(hù)臨界資源 - 鎖(Lock/RLock)
      多個(gè)線程競(jìng)爭(zhēng)多個(gè)資源(線程數(shù)>資源數(shù)) - 信號(hào)量(Semaphore)
      多個(gè)線程的調(diào)度 - 暫停線程執(zhí)行/喚醒等待中的線程 - Condition
      """
      from concurrent.futures import ThreadPoolExecutor
      from random import randint
      from time import sleep
      
      import threading
      
      
      class Account():
          """銀行賬戶"""
      
          def __init__(self, balance=0):
              self.balance = balance
              lock = threading.Lock()
              self.condition = threading.Condition(lock)
      
          def withdraw(self, money):
              """取錢"""
              with self.condition:
                  while money > self.balance:
                      self.condition.wait()
                  new_balance = self.balance - money
                  sleep(0.001)
                  self.balance = new_balance
      
          def deposit(self, money):
              """存錢"""
              with self.condition:
                  new_balance = self.balance + money
                  sleep(0.001)
                  self.balance = new_balance
                  self.condition.notify_all()
      
      
      def add_money(account):
          while True:
              money = randint(5, 10)
              account.deposit(money)
              print(threading.current_thread().name, 
                    ':', money, '====>', account.balance)
              sleep(0.5)
      
      
      def sub_money(account):
          while True:
              money = randint(10, 30)
              account.withdraw(money)
              print(threading.current_thread().name, 
                    ':', money, '<====', account.balance)
              sleep(1)
      
      
      def main():
          account = Account()
          with ThreadPoolExecutor(max_workers=10) as pool:
              for _ in range(5):
                  pool.submit(add_money, account)
                  pool.submit(sub_money, account)
      
      
      if __name__ == '__main__':
          main()
      
    • 多進(jìn)程:多進(jìn)程可以有效的解決GIL的問(wèn)題,實(shí)現(xiàn)多進(jìn)程主要的類是Process浙滤,其他輔助的類跟threading模塊中的類似阴挣,進(jìn)程間共享數(shù)據(jù)可以使用管道、套接字等纺腊,在multiprocessing模塊中有一個(gè)Queue類畔咧,它基于管道和鎖機(jī)制提供了多個(gè)進(jìn)程共享的隊(duì)列。下面是官方文檔上關(guān)于多進(jìn)程和進(jìn)程池的一個(gè)示例揖膜。

      """
      多進(jìn)程和進(jìn)程池的使用
      多線程因?yàn)镚IL的存在不能夠發(fā)揮CPU的多核特性
      對(duì)于計(jì)算密集型任務(wù)應(yīng)該考慮使用多進(jìn)程
      time python3 example22.py
      real    0m11.512s
      user    0m39.319s
      sys     0m0.169s
      使用多進(jìn)程后實(shí)際執(zhí)行時(shí)間為11.512秒誓沸,而用戶時(shí)間39.319秒約為實(shí)際執(zhí)行時(shí)間的4倍
      這就證明我們的程序通過(guò)多進(jìn)程使用了CPU的多核特性,而且這臺(tái)計(jì)算機(jī)配置了4核的CPU
      """
      import concurrent.futures
      import math
      
      PRIMES = [
          1116281,
          1297337,
          104395303,
          472882027,
          533000389,
          817504243,
          982451653,
          112272535095293,
          112582705942171,
          112272535095293,
          115280095190773,
          115797848077099,
          1099726899285419
      ] * 5
      
      
      def is_prime(n):
          """判斷素?cái)?shù)"""
          if n % 2 == 0:
              return False
      
          sqrt_n = int(math.floor(math.sqrt(n)))
          for i in range(3, sqrt_n + 1, 2):
              if n % i == 0:
                  return False
          return True
      
      
      def main():
          """主函數(shù)"""
          with concurrent.futures.ProcessPoolExecutor() as executor:
              for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
                  print('%d is prime: %s' % (number, prime))
      
      
      if __name__ == '__main__':
          main()
      

      說(shuō)明:多線程和多進(jìn)程的比較壹粟。

      以下情況需要使用多線程:

      1. 程序需要維護(hù)許多共享的狀態(tài)(尤其是可變狀態(tài))拜隧,Python中的列表、字典、集合都是線程安全的虹蓄,所以使用線程而不是進(jìn)程維護(hù)共享狀態(tài)的代價(jià)相對(duì)較小犀呼。
      2. 程序會(huì)花費(fèi)大量時(shí)間在I/O操作上,沒(méi)有太多并行計(jì)算的需求且不需占用太多的內(nèi)存薇组。

      以下情況需要使用多進(jìn)程:

      1. 程序執(zhí)行計(jì)算密集型任務(wù)(如:字節(jié)碼操作外臂、數(shù)據(jù)處理、科學(xué)計(jì)算)律胀。
      2. 程序的輸入可以并行的分成塊宋光,并且可以將運(yùn)算結(jié)果合并。
      3. 程序在內(nèi)存使用方面沒(méi)有任何限制且不強(qiáng)依賴于I/O操作(如:讀寫(xiě)文件炭菌、套接字等)罪佳。
    • 異步處理:從調(diào)度程序的任務(wù)隊(duì)列中挑選任務(wù),該調(diào)度程序以交叉的形式執(zhí)行這些任務(wù)黑低,我們并不能保證任務(wù)將以某種順序去執(zhí)行赘艳,因?yàn)閳?zhí)行順序取決于隊(duì)列中的一項(xiàng)任務(wù)是否愿意將CPU處理時(shí)間讓位給另一項(xiàng)任務(wù)。異步任務(wù)通常通過(guò)多任務(wù)協(xié)作處理的方式來(lái)實(shí)現(xiàn)克握,由于執(zhí)行時(shí)間和順序的不確定蕾管,因此需要通過(guò)回調(diào)式編程或者future對(duì)象來(lái)獲取任務(wù)執(zhí)行的結(jié)果。Python 3通過(guò)asyncio模塊和awaitasync關(guān)鍵字(在Python 3.7中正式被列為關(guān)鍵字)來(lái)支持異步處理菩暗。

      """
      異步I/O - async / await
      """
      import asyncio
      
      
      def num_generator(m, n):
          """指定范圍的數(shù)字生成器"""
          yield from range(m, n + 1)
      
      
      async def prime_filter(m, n):
          """素?cái)?shù)過(guò)濾器"""
          primes = []
          for i in num_generator(m, n):
              flag = True
              for j in range(2, int(i ** 0.5 + 1)):
                  if i % j == 0:
                      flag = False
                      break
              if flag:
                  print('Prime =>', i)
                  primes.append(i)
      
              await asyncio.sleep(0.001)
          return tuple(primes)
      
      
      async def square_mapper(m, n):
          """平方映射器"""
          squares = []
          for i in num_generator(m, n):
              print('Square =>', i * i)
              squares.append(i * i)
      
              await asyncio.sleep(0.001)
          return squares
      
      
      def main():
          """主函數(shù)"""
          loop = asyncio.get_event_loop()
          future = asyncio.gather(prime_filter(2, 100), square_mapper(1, 100))
          future.add_done_callback(lambda x: print(x.result()))
          loop.run_until_complete(future)
          loop.close()
      
      
      if __name__ == '__main__':
          main()
      

      說(shuō)明:上面的代碼使用get_event_loop函數(shù)獲得系統(tǒng)默認(rèn)的事件循環(huán)掰曾,通過(guò)gather函數(shù)可以獲得一個(gè)future對(duì)象,future對(duì)象的add_done_callback可以添加執(zhí)行完成時(shí)的回調(diào)函數(shù)停团,loop對(duì)象的run_until_complete方法可以等待通過(guò)future對(duì)象獲得協(xié)程執(zhí)行結(jié)果旷坦。

      Python中有一個(gè)名為aiohttp的三方庫(kù),它提供了異步的HTTP客戶端和服務(wù)器佑稠,這個(gè)三方庫(kù)可以跟asyncio模塊一起工作秒梅,并提供了對(duì)Future對(duì)象的支持。Python 3.6中引入了async和await來(lái)定義異步執(zhí)行的函數(shù)以及創(chuàng)建異步上下文讶坯,在Python 3.7中它們正式成為了關(guān)鍵字番电。下面的代碼異步的從5個(gè)URL中獲取頁(yè)面并通過(guò)正則表達(dá)式的命名捕獲組提取了網(wǎng)站的標(biāo)題。

      import asyncio
      import re
      
      import aiohttp
      
      PATTERN = re.compile(r'\<title\>(?P<title>.*)\<\/title\>')
      
      
      async def fetch_page(session, url):
          async with session.get(url, ssl=False) as resp:
              return await resp.text()
      
      
      async def show_title(url):
          async with aiohttp.ClientSession() as session:
              html = await fetch_page(session, url)
              print(PATTERN.search(html).group('title'))
      
      
      def main():
          urls = ('https://www.python.org/',
                  'https://git-scm.com/',
                  'https://www.jd.com/',
                  'https://www.taobao.com/',
                  'https://www.douban.com/')
          loop = asyncio.get_event_loop()
          tasks = [show_title(url) for url in urls]
          loop.run_until_complete(asyncio.wait(tasks))
          loop.close()
      
      
      if __name__ == '__main__':
          main()
      

      說(shuō)明:異步I/O與多進(jìn)程的比較辆琅。

      當(dāng)程序不需要真正的并發(fā)性或并行性漱办,而是更多的依賴于異步處理和回調(diào)時(shí),asyncio就是一種很好的選擇婉烟。如果程序中有大量的等待與休眠時(shí)娩井,也應(yīng)該考慮asyncio,它很適合編寫(xiě)沒(méi)有實(shí)時(shí)數(shù)據(jù)處理需求的Web應(yīng)用服務(wù)器似袁。

      Python還有很多用于處理并行任務(wù)的三方庫(kù)洞辣,例如:joblib咐刨、PyMP等。實(shí)際開(kāi)發(fā)中扬霜,要提升系統(tǒng)的可擴(kuò)展性和并發(fā)性通常有垂直擴(kuò)展(增加單個(gè)節(jié)點(diǎn)的處理能力)和水平擴(kuò)展(將單個(gè)節(jié)點(diǎn)變成多個(gè)節(jié)點(diǎn))兩種做法定鸟。可以通過(guò)消息隊(duì)列來(lái)實(shí)現(xiàn)應(yīng)用程序的解耦合著瓶,消息隊(duì)列相當(dāng)于是多線程同步隊(duì)列的擴(kuò)展版本联予,不同機(jī)器上的應(yīng)用程序相當(dāng)于就是線程,而共享的分布式消息隊(duì)列就是原來(lái)程序中的Queue材原。消息隊(duì)列(面向消息的中間件)的最流行和最標(biāo)準(zhǔn)化的實(shí)現(xiàn)是AMQP(高級(jí)消息隊(duì)列協(xié)議)沸久,AMQP源于金融行業(yè),提供了排隊(duì)余蟹、路由卷胯、可靠傳輸日裙、安全等功能菱蔬,最著名的實(shí)現(xiàn)包括:Apache的ActiveMQ、RabbitMQ等托慨。

      Celery是Python編寫(xiě)的分布式任務(wù)隊(duì)列兼搏,它使用分布式消息進(jìn)行工作卵慰,可以基于RabbitMQ或Redis來(lái)作為后端的消息代理,這個(gè)內(nèi)容我們會(huì)在項(xiàng)目中講到佛呻。

第04天:團(tuán)隊(duì)開(kāi)發(fā)和項(xiàng)目選題

  1. 軟件過(guò)程模型

    • 經(jīng)典過(guò)程模型(瀑布模型)

      • 可行性分析(研究做還是不做),輸出《可行性分析報(bào)告》病线。
      • 需求分析(研究做什么)吓著,輸出《需求規(guī)格說(shuō)明書(shū)》和產(chǎn)品界面原型圖。
      • 概要設(shè)計(jì)和詳細(xì)設(shè)計(jì)送挑,輸出概念模型圖绑莺、物理模型圖、類圖惕耕、時(shí)序圖等纺裁。
      • 編碼 / 測(cè)試。
      • 上線 / 維護(hù)司澎。
    • 敏捷開(kāi)發(fā)(Scrum)

      • 產(chǎn)品的Backlog(用戶故事欺缘、產(chǎn)品原型)。
      • 計(jì)劃會(huì)議(評(píng)估挤安、預(yù)算谚殊、進(jìn)度)。
      • 日常開(kāi)發(fā)(站立會(huì)議蛤铜、番茄工作法嫩絮、結(jié)對(duì)編程丛肢、測(cè)試先行、代碼重構(gòu)……)剿干。
      • 修復(fù)bug(問(wèn)題描述蜂怎、重現(xiàn)步驟、測(cè)試人員置尔、被指派人)杠步。
      • 評(píng)審會(huì)議(Showcase)。
      • 回顧會(huì)議(當(dāng)前周期做得好和不好的地方)撰洗。

      補(bǔ)充:敏捷軟件開(kāi)發(fā)宣言

      • 個(gè)體和互動(dòng) 高于 流程和工具
      • 工作的軟件 高于 詳盡的文檔
      • 客戶合作 高于 合同談判
      • 響應(yīng)變化 高于 遵循計(jì)劃

    [圖片上傳失敗...(image-af7c78-1558321309426)]

    角色:產(chǎn)品所有者(決定做什么篮愉,能對(duì)需求拍板的人)、團(tuán)隊(duì)負(fù)責(zé)人(解決各種問(wèn)題差导,專注如何更好的工作试躏,屏蔽外部對(duì)開(kāi)發(fā)團(tuán)隊(duì)的影響)、開(kāi)發(fā)團(tuán)隊(duì)(項(xiàng)目執(zhí)行人員设褐,具體指開(kāi)發(fā)人員和測(cè)試人員)

    準(zhǔn)備工作:商業(yè)案例和資金颠蕴、合同、憧憬助析、初始產(chǎn)品需求犀被、初始發(fā)布計(jì)劃、入股外冀、組建團(tuán)隊(duì)

    敏捷團(tuán)隊(duì)通常人數(shù)為8-10人寡键。

    工作量估算:將開(kāi)發(fā)任務(wù)量化,包括原型雪隧、Logo設(shè)計(jì)西轩、UI設(shè)計(jì)、前端開(kāi)發(fā)等脑沿,盡量把每個(gè)工作分解到最小任務(wù)量藕畔,最小任務(wù)量標(biāo)準(zhǔn)為工作時(shí)間不能超過(guò)兩天,然后估算總體項(xiàng)目時(shí)間庄拇。把每個(gè)任務(wù)都貼在白板上面注服,白板上分三部分:to do(待完成)、in progress(進(jìn)行中)和done(已完成)措近。

  2. 項(xiàng)目團(tuán)隊(duì)組建

    • 團(tuán)隊(duì)的構(gòu)成和角色

      [圖片上傳失敗...(image-65edd-1558321309426)]

    • 編程規(guī)范和代碼審查(flake8溶弟、pylint)

      [圖片上傳失敗...(image-2c20d-1558321309426)]

    • Python中的一些“慣例”(請(qǐng)參考《Python慣例-如何編寫(xiě)Pythonic的代碼》

    • 影響代碼可讀性的原因:

      • 代碼注釋太少或者沒(méi)有注釋
      • 代碼破壞了語(yǔ)言的最佳實(shí)踐
      • 反模式編程(意大利面代碼、復(fù)制-黏貼編程熄诡、自負(fù)編程可很、……)
  3. 團(tuán)隊(duì)開(kāi)發(fā)工具介紹

    請(qǐng)參考《團(tuán)隊(duì)項(xiàng)目開(kāi)發(fā)》

項(xiàng)目選題和理解業(yè)務(wù)

  1. 選題范圍設(shè)定

    • CMS(用戶端):新聞聚合網(wǎng)站菜拓、問(wèn)答/分享社區(qū)瓣窄、影評(píng)/書(shū)評(píng)網(wǎng)站等。

    • MIS(用戶端+管理端):KMS纳鼎、KPI考核系統(tǒng)俺夕、HRS、CRM系統(tǒng)贱鄙、供應(yīng)鏈系統(tǒng)劝贸、倉(cāng)儲(chǔ)管理系統(tǒng)等。

    • App后臺(tái)(管理端+數(shù)據(jù)接口):二手交易類逗宁、報(bào)刊雜志類映九、小眾電商類、新聞資訊類瞎颗、旅游類件甥、社交類、閱讀類等哼拔。

    • 其他類型:自身行業(yè)背景和工作經(jīng)驗(yàn)引有、業(yè)務(wù)容易理解和把控。

  2. 需求理解倦逐、模塊劃分和任務(wù)分配

    • 需求理解:頭腦風(fēng)暴和競(jìng)品分析譬正。
    • 模塊劃分:畫(huà)思維導(dǎo)圖(XMind),每個(gè)模塊是一個(gè)枝節(jié)點(diǎn)檬姥,每個(gè)具體的功能是一個(gè)葉節(jié)點(diǎn)(用動(dòng)詞表述)导帝,需要確保每個(gè)葉節(jié)點(diǎn)無(wú)法再生出新節(jié)點(diǎn),確定每個(gè)葉子節(jié)點(diǎn)的重要性穿铆、優(yōu)先級(jí)和工作量。
    • 任務(wù)分配:由項(xiàng)目負(fù)責(zé)人根據(jù)上面的指標(biāo)為每個(gè)團(tuán)隊(duì)成員分配任務(wù)斋荞。

    [圖片上傳失敗...(image-c02b5b-1558321309426)]

  3. 制定項(xiàng)目進(jìn)度表(每日更新)

    模塊 功能 人員 狀態(tài) 完成 工時(shí) 計(jì)劃開(kāi)始 實(shí)際開(kāi)始 計(jì)劃結(jié)束 實(shí)際結(jié)束 備注
    評(píng)論 添加評(píng)論 王大錘 正在進(jìn)行 50% 4 2018/8/7 2018/8/7
    刪除評(píng)論 王大錘 等待 0% 2 2018/8/7 2018/8/7
    查看評(píng)論 白元芳 正在進(jìn)行 20% 4 2018/8/7 2018/8/7 需要進(jìn)行代碼審查
    評(píng)論投票 白元芳 等待 0% 4 2018/8/8 2018/8/8

第05天:數(shù)據(jù)庫(kù)設(shè)計(jì)和OOAD

概念模型和正向工程

  1. UML(統(tǒng)一建模語(yǔ)言)的類圖

    [圖片上傳失敗...(image-5a8dcf-1558321309426)]

  2. 通過(guò)模型創(chuàng)建表(正向工程)

    python manage.py makemigrations app
    python manage.py migrate
    

物理模型和反向工程

  1. PowerDesigner

    [圖片上傳失敗...(image-bab72e-1558321309426)]

  2. 通過(guò)數(shù)據(jù)表創(chuàng)建模型(反向工程)

    python manage.py inspectdb > app/models.py
    

第06-10天:使用Django開(kāi)發(fā)項(xiàng)目

說(shuō)明:具體內(nèi)容請(qǐng)參考《Django知識(shí)點(diǎn)概述》

項(xiàng)目開(kāi)發(fā)中的公共問(wèn)題

  1. 數(shù)據(jù)庫(kù)的配置(多數(shù)據(jù)庫(kù)荞雏、主從復(fù)制、數(shù)據(jù)庫(kù)路由)
  2. 緩存的配置(分區(qū)緩存平酿、鍵設(shè)置凤优、超時(shí)設(shè)置、主從復(fù)制蜈彼、故障恢復(fù)(哨兵))
  3. 日志的配置
  4. 分析和調(diào)試(Django-Debug-ToolBar)
  5. 好用的Python模塊(日期計(jì)算筑辨、圖像處理、數(shù)據(jù)加密幸逆、三方API)

REST API設(shè)計(jì)

  1. RESTful架構(gòu)
  2. API接口文檔的撰寫(xiě)(《網(wǎng)絡(luò)API接口設(shè)計(jì)》
  3. django-REST-framework的應(yīng)用

項(xiàng)目中的重點(diǎn)難點(diǎn)剖析

  1. 使用緩存緩解數(shù)據(jù)庫(kù)壓力 - Redis
  2. 使用消息隊(duì)列做解耦合和削峰 - Celery + RabbitMQ

第11-12天:測(cè)試和部署

單元測(cè)試

  1. 測(cè)試的種類
  2. 編寫(xiě)單元測(cè)試(unittest棍辕、pytest暮现、nose2、tox楚昭、ddt栖袋、……)
  3. 測(cè)試覆蓋率(coverage)

項(xiàng)目部署

說(shuō)明:請(qǐng)參考《項(xiàng)目部署上線指南》

  1. 部署前的準(zhǔn)備工作
    • 關(guān)鍵設(shè)置(SECRET_KEY / DEBUG / ALLOWED_HOSTS / 緩存 / 數(shù)據(jù)庫(kù))
    • HTTPS / CSRF_COOKIE_SECUR / SESSION_COOKIE_SECURE
    • 日志相關(guān)配置
  2. Linux常用命令回顧
  3. Linux常用服務(wù)的安裝和配置
  4. uWSGI/Gunicorn和Nginx的使用
    • Gunicorn和uWSGI的比較
      • 對(duì)于不需要大量定制化的簡(jiǎn)單應(yīng)用程序抚太,Gunicorn是一個(gè)不錯(cuò)的選擇塘幅,uWSGI的學(xué)習(xí)曲線比Gunicorn要陡峭得多,Gunicorn的默認(rèn)參數(shù)就已經(jīng)能夠適應(yīng)大多數(shù)應(yīng)用程序尿贫。
      • uWSGI支持異構(gòu)部署电媳。
      • 由于Nginx本身支持uWSGI,在線上一般都將Nginx和uWSGI捆綁在一起部署庆亡,而且uWSGI屬于功能齊全且高度定制的WSGI中間件匾乓。
      • 在性能上,Gunicorn和uWSGI其實(shí)表現(xiàn)相當(dāng)身冀。
  5. 虛擬化技術(shù)(Docker)

性能測(cè)試

說(shuō)明:具體內(nèi)容請(qǐng)參考《Django知識(shí)點(diǎn)概述》钝尸。

  1. AB的使用
  2. SQLslap的使用
  3. sysbench的使用

自動(dòng)化測(cè)試

  1. 使用Shell和Python進(jìn)行自動(dòng)化測(cè)試
  2. 使用Selenium實(shí)現(xiàn)自動(dòng)化測(cè)試
    • Selenium IDE
    • Selenium WebDriver
    • Selenium Remote Control
  3. 測(cè)試工具Robot Framework介紹

項(xiàng)目性能調(diào)優(yōu)

  1. 數(shù)據(jù)庫(kù)服務(wù)器性能調(diào)優(yōu) - 請(qǐng)參考《MySQL相關(guān)知識(shí)》
    • 軟硬件優(yōu)化
    • SQL優(yōu)化
    • 架構(gòu)優(yōu)化
      • 分庫(kù)分表
      • 主從復(fù)制,讀寫(xiě)分離
      • 集群架構(gòu)
  2. Web服務(wù)器性能優(yōu)化
    • Nginx負(fù)載均衡配置
    • Keepalived實(shí)現(xiàn)高可用
  3. 代碼性能調(diào)優(yōu)
    • 多線程
    • 異步化
  4. 靜態(tài)資源訪問(wèn)優(yōu)化
    • 云存儲(chǔ)
    • CDN

結(jié)束:項(xiàng)目答辯和簡(jiǎn)歷指導(dǎo)

  1. Showcase
  2. 簡(jiǎn)歷指導(dǎo)
  3. 面試話術(shù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搂根,一起剝皮案震驚了整個(gè)濱河市珍促,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剩愧,老刑警劉巖猪叙,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異仁卷,居然都是意外死亡穴翩,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門锦积,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芒帕,“玉大人,你說(shuō)我怎么就攤上這事丰介”丑。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵哮幢,是天一觀的道長(zhǎng)带膀。 經(jīng)常有香客問(wèn)我,道長(zhǎng)橙垢,這世上最難降的妖魔是什么垛叨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮柜某,結(jié)果婚禮上嗽元,老公的妹妹穿的比我還像新娘敛纲。我一直安慰自己,他們只是感情好还棱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布载慈。 她就那樣靜靜地躺著,像睡著了一般珍手。 火紅的嫁衣襯著肌膚如雪办铡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天琳要,我揣著相機(jī)與錄音寡具,去河邊找鬼。 笑死稚补,一個(gè)胖子當(dāng)著我的面吹牛童叠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播课幕,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼厦坛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了乍惊?” 一聲冷哼從身側(cè)響起杜秸,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎润绎,沒(méi)想到半個(gè)月后撬碟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莉撇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年呢蛤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棍郎。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡其障,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涂佃,到底是詐尸還是另有隱情静秆,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布巡李,位于F島的核電站,受9級(jí)特大地震影響扶认,放射性物質(zhì)發(fā)生泄漏侨拦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一辐宾、第九天 我趴在偏房一處隱蔽的房頂上張望狱从。 院中可真熱鬧膨蛮,春花似錦、人聲如沸季研。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)与涡。三九已至惹谐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間驼卖,已是汗流浹背氨肌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酌畜,地道東北人怎囚。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像桥胞,于是被迫代替她去往敵國(guó)和親恳守。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 寫(xiě)在前面的話 代碼中的# > 表示的是輸出結(jié)果 輸入 使用input()函數(shù) 用法 注意input函數(shù)輸出的均是字...
    FlyingLittlePG閱讀 2,734評(píng)論 0 8
  • 一贩虾、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 5,723評(píng)論 0 10
  • Python語(yǔ)言特性 1 Python的函數(shù)參數(shù)傳遞 看兩個(gè)如下例子催烘,分析運(yùn)行結(jié)果: 代碼一: a = 1 def...
    時(shí)光清淺03閱讀 471評(píng)論 0 0
  • Python語(yǔ)言特性 1 Python的函數(shù)參數(shù)傳遞 看兩個(gè)如下例子,分析運(yùn)行結(jié)果: 代碼一: a = 1 def...
    伊森H閱讀 3,048評(píng)論 0 15
  • 作者 姜蘇 33 入夜的石千峰整胃,依然是寂靜的颗圣。盡管在黑壓壓的山坳里,多了這么幾個(gè)屁使,從二十中林場(chǎng)到大林場(chǎng)之間不足百...
    姜蘇閱讀 175評(píng)論 0 0