Python 【基礎面試題】

前言

面試題僅作學習參考,學習者閱後也要用心鑽研其中的原理,重要知識須要系統學習、透徹學習,造成本身的知識鏈。如下五點建議但願對您有幫助,早日拿到一份心儀的offer。php

  1. 作好細節工做,細緻的人運氣不會差
  2. 展示特別能夠,但要創建在已充分展現實力的基礎上
  3. 真誠比圓滑重要,請真誠地回答問題
  4. 把握當下,考場外的表現能起的做用微乎其微
  5. 沒有經過不表明你不優秀,選人先考慮的是與崗位相匹配

 

Python 三程三器

  • 進程
    • 進程是資源分配的最小單位(內存、CPU、網絡、io)
    • 一個運行起來的程序就是一個進程
      • 什麼是程序(程序使咱們存儲在硬盤裏的代碼)
      • 硬盤(256G)、內存條(8G)
      • 當咱們雙擊一個圖標、打開程序的時候,實際上就是經過IO(讀寫)內存條裏面
      • 內存條就是咱們所指的資源
      • CPU分時
        • CPU比你的手速快多了,分時處理每一個線程,可是因爲太快然你以爲每一個線程都是獨佔cpu
        • cpu是計算,只有時間片到了,獲取cpu,線程真正執行
        • 當你想使用 網絡、磁盤等資源的時候,須要cpu的調度
    • 進程具備獨立的內存空間,因此沒有辦法相互通訊
      • 進程如何通訊
        • 進程queue(父子進程通訊)
        • pipe(同一程序下兩個進程通訊)
        • managers(同一程序下多個進程通訊)
        • RabbitMQ、redis等(不一樣程序間通訊)
    • 爲何須要進程池
      • 一次性開啓指定數量的進程
      • 若是有十個進程,有一百個任務,一次能夠處理多少個(一次性只能處理十個)
      • 防止進程開啓數量過多致使服務器壓力過大
  • 線程
    • 有了進程爲何還須要線程
      • 由於進程不能同一時間只能作一個事情
    • 什麼是線程
      • 線程是操做系統調度的最小單位
      • 線程是進程正真的執行者,是一些指令的集合(進程資源的擁有者)
      • 同一個進程下的讀多個線程共享內存空間,數據直接訪問(數據共享)
      • 爲了保證數據安全,必須使用線程鎖
    • GIL全局解釋器鎖
      • 在python全局解釋器下,保證同一時間只有一個線程運行
      • 防止多個線程都修改數據
    • 線程鎖(互斥鎖)
      • GIL鎖只能保證同一時間只能有一個線程對某個資源操做,但當上一個線程還未執行完畢時可能就會釋放GIL,其餘線程就能夠操做了
      • 線程鎖本質把線程中的數據加了一把互斥鎖
        • mysql中共享鎖 & 互斥鎖
          • mysql共享鎖:共享鎖,全部線程都能讀,而不能寫
          • mysql排它鎖:排它,任何線程讀取這個這個數據的權利都沒有
        • 加上線程鎖以後全部其餘線程,讀都不能讀這個數據
      • 有了GIL全局解釋器鎖爲何還須要線程鎖
        • 由於cpu是分時使用的
    • 死鎖定義
      • 兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去
    • 多線程
      • 爲何要使用多線程?
        • 線程在程序中是獨立的、併發的執行流。與分隔的進程相比,進程中線程之間的隔離程度要小,它們共享內存、文件句柄
        • 和其餘進程應有的狀態。
        • 由於線程的劃分尺度小於進程,使得多線程程序的併發性高。進程在執行過程之中擁有獨立的內存單元,而多個線程共享
        • 內存,從而極大的提高了程序的運行效率。
        • 線程比進程具備更高的性能,這是因爲同一個進程中的線程都有共性,多個線程共享一個進程的虛擬空間。線程的共享環境
        • 包括進程代碼段、進程的共有數據等,利用這些共享的數據,線程之間很容易實現通訊。
        • 操做系統在建立進程時,必須爲改進程分配獨立的內存空間,並分配大量的相關資源,但建立線程則簡單得多。所以,使用多線程來實現併發比使用多進程的性能高得要多。
      • 總結起來,使用多線程編程具備以下幾個優勢:?
        • 進程之間不能共享內存,但線程之間共享內存很是容易。
        • 操做系統在建立進程時,須要爲該進程從新分配系統資源,但建立線程的代價則小得多。所以使用多線程來實現多任務併發執行比使用多進程的效率高
        • python語言內置了多線程功能支持,而不是單純地做爲底層操做系統的調度方式,從而簡化了python的多線程編程。
import threading
import time
def sayhi(num): #定義每一個線程要運行的函數
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(10):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()
for循環同時啓動多個線程
    • join/setDaemon
      • 當一個進程啓動以後,會默認產生一個主線程,由於線程是程序執行流的最小單元,當設置多線程時,主線程會建立多個子線程,在python中,默認狀況下(其實就是setDaemon(False)),主線程執行完本身的任務之後,就退出了,此時子線程會繼續執行本身的任務,直到本身的任務結束。
      • 當咱們使用setDaemon(True)方法,設置子線程爲守護線程時,主線程一旦執行結束,則所有線程所有被終止執行,可能出現的狀況就是,子線程的任務尚未徹底執行結束,就被迫中止。此時join的做用就凸顯出來了,join所完成的工做就是線程同步,即主線程任務結束以後,進入阻塞狀態,一直等待其餘的子線程執行結束以後,主線程在終止。
      • join有一個timeout參數:
        • 當設置守護線程時,含義是主線程對於子線程等待timeout的時間將會殺死該子線程,最後退出程序。因此說,若是有10個子線程,所有的等待時間就是每一個timeout的累加和。簡單的來講,就是給每一個子線程一個timeout的時間,讓他去執行,時間一到,無論任務有沒有完成,直接殺死。
        • 沒有設置守護線程時,主線程將會等待timeout的累加和這樣的一段時間,時間一到,主線程結束,可是並無殺死子線程,子線程依然能夠繼續執行,直到子線程所有結束,程序退出。線程池
        • 對於任務數量不斷增長的程序,每有一個任務就生成一個線程,最終會致使線程數量的失控,例如,整站爬蟲,假設初始只有一個連接a,那麼,這個時候只啓動一個線程,運行以後,獲得這個連接對應頁面上的b,c,d,,,等等新的連接,做爲新任務,這個時候,就要爲這些新的連接生成新的線程,線程數量暴漲。在以後的運行中,線程數量還會不停的增長,徹底沒法控制。因此,對於任務數量不端增長的程序,固定線程數量的線程池是必要的。
import threading
import time
start_time = time.time()

def sayhi(num): #定義每一個線程要運行的函數
    print("running on number:%s" %num)
    time.sleep(3)
t_objs = []    #將進程實例對象存儲在這個列表中
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()          #啓動一個線程,程序不會阻塞
    t_objs.append(t)
print(threading.active_count())    #打印當前活躍進程數量
for t in t_objs: #利用for循環等待上面50個進程所有結束
    t.join()     #阻塞某個程序
print(threading.current_thread())    #打印執行這個命令進程

print("----------------all threads has finished.....")
print(threading.active_count())
print('cost time:',time.time() - start_time)
join()實現全部線程都執行結束後再執行主線程
import threading
import time
start_time = time.time()
def sayhi(num): #定義每一個線程要運行的函數
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.setDaemon(True)  #把當前線程變成守護線程,必須在t.start()前設置
    t.start()          #啓動一個線程,程序不會阻塞
print('cost time:',time.time() - start_time)
setDaemon()守護線程,主線程退出時,須要子線程隨主線程退出
import requests
from concurrent.futures import ThreadPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google頁面會卡住,知道頁面超時後這個進程才結束
    'http://dig.chouti.com/',          #chouti頁面內容會直接返回,不會等待Google頁面的返回
]

pool = ThreadPoolExecutor(10)            # 建立一個線程池,最多開10個線程
for url in url_list:
    pool.submit(fetch_request,url)       # 去線程池中獲取一個線程,線程去執行fetch_request方法

pool.shutdown(True)  
線程實現併發
  • 協程
    • 什麼是協程
      • 協程微線程,纖程,本質是一個單線程
      • 協程能在單線程處理高併發
        • 線程遇到I/O操做會等待、阻塞,協程遇到I/O會自動切換(剩下的只有CPU操做)
        • 線程的狀態保存在CPU的寄存器和棧裏而協程擁有本身的空間,因此無需上下文切換的開銷,因此快、
      • 爲甚麼協程可以遇到I/O自動切換
        • 協程有一個gevent模塊(封裝了greenlet模塊),遇到I/O自動切換
    • 協程缺點
      • 沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上
      • 線程阻塞(Blocking)操做(如IO時)會阻塞掉整個程序
    • 協程最大的優勢
      • 不只是處理高併發(單線程下處理高併發)
      • 特別節省資源(500日活,用php寫須要兩百多態機器,可是golang只須要二十多太機器)
        • 200多臺機器一年
        • 二十多天機器一年
    • 協程爲什麼能處理大併發
      • greeenlet遇到I/O手動切換
      • 協程遇到I/O操做就切換,其實Gevent模塊僅僅是對greenlet的封裝,將I/O的手動切換變成自動切換
      • 協程之因此快是由於遇到I/O操做就切換(最後只有CPU運算)
      • 這裏先演示用greenlet實現手動的對各個協程之間切換
      • 其實Gevent模塊僅僅是對greenlet的再封裝,將I/O間的手動切換變成自動切換
from greenlet import greenlet



def test1():

    print(12)       #4 gr1會調用test1()先打印12

    gr2.switch()    #5 而後gr2.switch()就會切換到gr2這個協程

    print(34)       #8 因爲在test2()切換到了gr1,因此gr1又從上次中止的位置開始執行

    gr2.switch()    #9 在這裏又切換到gr2,會再次切換到test2()中執行



def test2():

    print(56)       #6 啓動gr2後會調用test2()打印56

    gr1.switch()    #7 而後又切換到gr1

    print(78)       #10 切換到gr2後會接着上次執行,打印78



gr1 = greenlet(test1)    #1 啓動一個協程gr1

gr2 = greenlet(test2)    #2 啓動第二個協程gr2

gr1.switch()             #3 首先gr1.switch() 就會去執行gr1這個協程
greeenlet遇到I/O手動切換
      • Gevent遇到I/O自動切換
      • Gevent是一個第三方庫,能夠經過gevent實現併發同步或異步編程,Gevent原理是隻要是遇到I/O操做就自動切換下一個協程
      • Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程
      • 在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程
      • Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。
      • Gevent原理是隻要遇到I/O操做就會自動切換到下一個協程
from urllib import request

import gevent,time

from gevent import monkey

monkey.patch_all()      #把當前程序全部的I/O操做給我單獨作上標記



def f(url):

    print('GET: %s' % url)

    resp = request.urlopen(url)

    data = resp.read()

    print('%d bytes received from %s.' % (len(data), url))



#1 併發執行部分

time_binxing = time.time()

gevent.joinall([

        gevent.spawn(f, 'https://www.python.org/'),

        gevent.spawn(f, 'https://www.yahoo.com/'),

        gevent.spawn(f, 'https://github.com/'),

])

print("並行時間:",time.time()-time_binxing)



#2 串行部分

time_chuanxing = time.time()

urls = [

        'https://www.python.org/',

        'https://www.yahoo.com/',

        'https://github.com/',

                                        ]

for url in urls:

    f(url)

print("串行時間:",time.time()-time_chuanxing)



# 注:爲何要在文件開通使用monkey.patch_all()

# 1. 由於有不少模塊在使用I / O操做時Gevent是沒法捕獲的,因此爲了使Gevent可以識別出程序中的I / O操做。

# 2. 就必須使用Gevent模塊的monkey模塊,把當前程序全部的I / O操做給我單獨作上標記

# 3.使用monkey作標記僅用兩步便可:

      第一步(導入monkey模塊):  from gevent import monkey

      第二步(聲明作標記)    :   monkey.patch_all()
使用Gevent實現併發下載網頁與串行下載網頁時間比較
  • Gevent實現簡單的自動切換小例子
  • 注:在Gevent模仿I/O切換的時候,只要遇到I/O就會切換,哪怕gevent.sleep(0)也要切換一次
import gevent



def func1():

    print('\033[31;1m第一次打印\033[0m')

    gevent.sleep(2)          # 爲何用gevent.sleep()而不是time.sleep()由於是爲了模仿I/O

    print('\033[31;1m第六次打印\033[0m')



def func2():

    print('\033[32;1m第二次打印\033[0m')

    gevent.sleep(1)

    print('\033[32;1m第四次打印\033[0m')



def func3():

    print('\033[32;1m第三次打印\033[0m')

    gevent.sleep(1)

    print('\033[32;1m第五次打印\033[0m')



gevent.joinall([            # 將要啓動的多個協程放到event.joinall的列表中,便可實現自動切換

    gevent.spawn(func1),    # gevent.spawn(func1)啓動這個協程

    gevent.spawn(func2),

    gevent.spawn(func3),

])



# 運行結果:

# 第一次打印

# 第二次打印

# 第三次打印

# 第四次打印

# 第五次打印

# 第六次打印
自動切換小例子
    • select、epool、pool
      • I/O的實質是什麼?
      • I/O的實質是將硬盤中的數據,或收到的數據實現從內核態 copy到 用戶態的過程
      • 本文討論的背景是Linux環境下的network IO。
      • 好比微信讀取本地硬盤的過程
        • 微信進程會發送一個讀取硬盤的請求----》操做系統
        • 只有內核纔可以讀取硬盤中的數據---》數據返回給微信程序(看上去就好像是微信直接讀取)
      • 用戶態 & 內核態
        • 系統空間分爲兩個部分,一部分是內核態,一部分是用戶態的部分
        • 內核態:內核態的空間資源只有操做系統可以訪問
        • 用戶態:咱們寫的普通程序使用的空間

    • select
      • 只能處理1024個鏈接(每個請求均可以理解爲一個鏈接)
      • 不能告訴用戶程序,哪個鏈接是活躍的
    • pool
      • 只是取消了最大1024個活躍的限制
      • 不能告訴用戶程序,哪個鏈接是活躍的
    • epool
      • 不只取消了1024這個最大鏈接限制
      • 並且能告訴用戶程序哪個是活躍的

    • 猴子補丁
      • monkey patch指的是在運行時動態替換,通常是在startup的時候.
      • 用過gevent就會知道,會在最開頭的地方gevent.monkey.patch_all();把標準庫中的thread/socket等給替換掉.這樣咱們在後面使用socket的時候能夠跟日常同樣使用,無需修改任何代碼,可是它變成非阻塞的了
  • 裝飾器
    • 什麼是裝飾器
      • 裝飾器本質是函數,用來給其餘函數添加新的功能
      • 特色:不修改調用方式、不修改源代碼
    • 裝飾器的應用場景
      • 用戶認證,判斷用戶是否登陸
      • 計算函數運行時間(算是一個功能、在項目裏用的很少)
      • 插入日誌的時候
      • redis緩存
    • 爲何使用裝飾器
      • 結合應用場景說需求
    • 如何使用裝飾器
      • 裝飾器求函數運行時間
    • Python 閉包
      • 當一個嵌套函數在其外部區域引用了一個值時,該嵌套函數就是一個閉包。其意義就是會記錄這個值。
def A(x):
    def B():
        print(x)
    return B
 deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)      #run test1
        stop_time = time.time()
        print("running time is %s"%(stop_time-start_time))
    return deco

# @timer     # test1=timer(test1)
def test1():
    time.sleep(3)
    print("in the test1")
test1()
裝飾器求函數運行時間
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import time
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func args:", *args, **kwargs)
            print('運行前')
            func(*args, **kwargs)
            print('運行後')
        return wrapper
    return outer_wrapper

@auth(auth_type="local") # home = wrapper()
def home():
    print("welcome to home  page")
    return "from home"
home()
三級裝飾器
  • 生成器
    • 什麼是生成器
      • 生成器就是一個特殊的迭代器
      • 一個有yield關鍵字的函數就是一個生成器
        • 生成器是這樣一個函數,它記住上一次返回時在函數體中的位置。
        • 對生成器函數的第二次(或第 n 次)調用跳轉至該函數中間,而上次調用的全部局部變量都保持不變。
        • yield簡單說來就是一個生成器,這樣函數它記住上次返 回時在函數體中的位置。對生成器第 二次(或n 次)調用跳轉至該函 次)調用跳轉至該函數。
    • 生成器哪些場景應用
      • 生成器是一個概念,咱們日常寫代碼可能用的並很少,可是python源碼大量使用
      • 好比咱們tornado框架就是基於 生成器+協程
      • 在咱們代碼中使用舉例
      • 好比咱們要生成一百萬個數據,若是用生成器很是節省空間,用列表浪費大量空間
import time
t1 = time.time()
g = (i for i in range(100000000))
t2 = time.time()
lst = [i for i in range(100000000)]
t3 = time.time()
print('生成器時間:',t2 - t1)  # 生成器時間: 0.0
print('列表時間:',t3 - t2)    # 列表時間: 5.821957349777222
    • 爲何使用生成器
      • 節省空間
      • 高效
    • 如何使用
#!/usr/bin/python
# -*- coding: utf-8 -*-
def read_big_file_v(fname):
    block_size = 1024 * 8
    with open(fname,encoding="utf8") as fp:
        while True:
            chunk = fp.read(block_size)
            # 當文件沒有更多內容時,read 調用將會返回空字符串 ''
            if not chunk:
                break
            print(chunk)
path = r'C:\aaa\luting\edc-backend\tttt.py'
read_big_file_v(path)
  • 迭代器
    • 什麼是迭代器
      • 迭代器是訪問集合內元素的一種方法
      • 老是從集合內第一個元素訪問,直到全部元素都被訪問過結束,當調用 __next__而元素返回會引起一個,StopIteration異常
    • 有兩個方法:_iter_ _next_
      • _iter_ : 返回迭代器自身
      • _next_: 返回下一個元素
    • 可迭代對象
      • 內置iter方法的,都是可迭代的對象。 list是可迭代對象,dict是可迭代對象,set也是可迭代對象
    • 定義
      • 迭代器:可迭代對象執行iter方法,獲得的結果就是迭代器,迭代器對象有next方法
      • 它是一個帶狀態的對象,他能在你調用next()方法的時候返回容器中的下一個值,任何實現了iter和next()方法的對象都是迭代器,iter返回迭代器自身,next返回容器中的下一個值,若是容器中沒有更多元素了,則拋出StopIteration異常
      •     

Python 面向對象

  • 什麼是面向對象
    • 使用模板的思想,將世界完事萬物使用對象來表示一個類型
  • 方法
    • 靜態方法
      • 特色:名義上歸類管理,實際上不能訪問類或者變量中的任意屬性或者方法
      • 做用:讓咱們代碼清晰,更好管理
      • 調用方式: 既能夠被類直接調用,也能夠經過實例調用
    • 類方法
      • 做用:無需實例化直接被類調用
      • 特性:類方法只能訪問類變量,不能訪問實例變量
      • 類方法使用場景:當咱們還未建立實例,可是須要調用類中的方法
      • 調用方式:既能夠被類直接調用,也能夠經過實例調用
    • 屬性方法
      • 屬性方法把一個方法變成一個屬性,隱藏了實現細節,調用時沒必要加括號直接d.eat便可調用self.eat()方
    • 魔法方法
      • 單例模式
        • 單例模式:永遠用一個對象得實例,避免新建太多實例浪費資源
        • 實質:使用__new__方法新建類對象時先判斷是否已經創建過,若是建過就使用已有的對象
        • 使用場景:若是每一個對象內部封裝的值都相同就能夠用單例模式
class Foo(object):
   instance = None
   def __init__(self):
      self.name = 'alex'

   def __new__(cls, *args, **kwargs):
      if Foo.instance:
         return Foo.instance
      else:
         Foo.instance = object.__new__(cls,*args,**kwargs)
         return Foo.instance

obj1 = Foo()       # obj1和obj2獲取的就是__new__方法返回的內容
obj2 = Foo()
print(obj1,obj2)   # 運行結果: <__main__.Foo object at 0x00D3B450>    
<__main__.Foo object at 0x00D3B450>
# 運行結果說明: # 這能夠看到咱們新建的兩個Foo()對象內存地址相同,說明使用的•同一個類,沒有重複創建類
    • __new__
      • 產生一個實例
    • __init__
      • 產生一個對象
    • __del__
      • 析構方法,刪除無用的內存對象(當程序結束會自動執行析構方法)

  • 特性
    • 封裝
      • 對類中屬性和方法進行一種封裝,隱藏了實現細節
    • 繼承
      • 之類繼承父類後,就具備了父類的全部屬性和方法,先繼承,後重寫
    • 多態
      • 一種接口,多種表現形狀
      • 中國人、和美國人都能講話,調用中國人類講中文,調用美國人類講英文
    • 新式類經典類的區別
      • pythn3不管新式類仍是經典類都是用 廣度優先
      • python2中,新式類:廣度優先,經典類:深度優先

class D:
    def talk(self):
        print('D')

class B(D):
    pass
    # def talk(self):
    #     print('B')

class C(D):
    pass
    def talk(self):
        print('C')

class A(B,C):
    pass
    # def talk(self):
    #     print('A')

a = A()
a.talk()
代碼
  • 屬性
    • 公有屬性(類變量)
      • 若是函數、方法或者屬性的名稱沒有以兩個下劃線開始,則爲公有屬性;
    • 普通屬性(實例變量)
      • 以self做爲前綴的屬性;
    • 私有屬性
      • 函數、方法或者屬性的名稱以兩個下劃線開始,則爲私有類型;
    • 局部變量
      • 類的方法中定義的變量沒有使用self做爲前綴聲明,則該變量爲局部變量;
  • 反射
    • hasattr: 判斷當前類是否有這個方法
    • getattr: 經過字符串反射出這個方法的內存地址
    • setattr:將當前類添加一個方法
    • delatrr: 刪除實例屬性

 

Python 常識概念

  • 深淺拷貝
    • 底層原理
      • 淺copy: 無論多麼複雜的數據結構,淺拷貝都只會copy一層
      • deepcopy : 深拷貝會徹底複製原變量相關的全部數據,在內存中生成一套徹底同樣的內容,咱們對這兩個變量中任意一個修改都不會影響其餘變量

    •  可變與不可變數據的區別
      • 可變類型(mutable):列表,字典
      • 不可變類型(unmutable):數字,字符串,元組
      • 這裏的可變不可變,是指內存中的那塊內容(value)是否能夠被改變。若是是不可變類型,在對對象自己操做的時候,必須在內存中新申請一塊區域(由於老區域#不可變#)。若是是可變類型,對對象操做的時候,不須要再在其餘地方申請內存,只須要在此對象後面連續申請(+/-)便可,也就是它的address會保持不變,但區域會變長或者變短。
      • copy.copy() 淺拷貝
      • copy.deepcopy() 深拷貝
      • 淺拷貝是新建立了一個跟原對象同樣的類型,可是其內容是對原對象元素的引用。這個拷貝的對象自己是新的,但內容不是。拷貝序列類型對象(列表\元組)時,默認是淺拷貝。
  • 垃圾回收機制
    • 引用計數
      • 原理
        • 當一個對象的引用被建立或者複製時,對象的引用計數加1;當一個對象的引用被銷燬時,對象的引用計數減1.
        • 當對象的引用計數減小爲0時,就意味着對象已經再沒有被使用了,能夠將其內存釋放掉。
      • 優勢
        • 引用計數有一個很大的優勢,即實時性,任何內存,一旦沒有指向它的引用,就會被當即回收,而其餘的垃圾收集技術必須在某種特殊條件下才能進行無效內存的回收。
      • 缺點
        • 引用計數機制所帶來的維護引用計數的額外操做與Python運行中所進行的內存分配和釋放,引用賦值的次數是成正比的,
        • 顯然比其它那些垃圾收集技術所帶來的額外操做只是與待回收的內存數量有關的效率要低。
        • 同時,由於對象之間相互引用,每一個對象的引用都不會爲0,因此這些對象所佔用的內存始終都不會被釋放掉。
    • 標記清楚
      • 它分爲兩個階段:第一階段是標記階段,GC會把全部的活動對象打上標記,第二階段是把那些沒有標記的對象非活動對象進行回收。
      • 對象之間經過引用(指針)連在一塊兒,構成一個有向圖
      • 從根對象(root object)出發,沿着有向邊遍歷對象,可達的(reachable)對象標記爲活動對象,不可達的對象就是要被清除的非活動對象。
      • 根對象就是全局變量、調用棧、寄存器。

      • 在上圖中,能夠從程序變量直接訪問塊1,而且能夠間接訪問塊2和3,程序沒法訪問塊4和5
      • 第一步將標記塊1,並記住塊2和3以供稍後處理。
      • 第二步將標記塊2,第三步將標記塊3,但不記得塊2,由於它已被標記。
      • 掃描階段將忽略塊1,2和3,由於它們已被標記,但會回收塊4和5。
    • 分代回收
      • 分代回收是創建在標記清除技術基礎之上的,是一種以空間換時間的操做方式。
      • Python將內存分爲了3「代」,分別爲年輕代(第0代)、中年代(第1代)、老年代(第2代)
      • 他們對應的是3個鏈表,它們的垃圾收集頻率與對象的存活時間的增大而減少。
      • 新建立的對象都會分配在年輕代,年輕代鏈表的總數達到上限時,Python垃圾收集機制就會被觸發
      • 把那些能夠被回收的對象回收掉,而那些不會回收的對象就會被移到中年代去,依此類推
      • 老年代中的對象是存活時間最久的對象,甚至是存活於整個系統的生命週期內。
  • TCP/UDP
    • TCP/UDP比較
      • TCP面向鏈接(如打電話要先撥號創建鏈接);UDP是無鏈接的,即發送數據以前不須要創建鏈接
      • TCP提供可靠的服務,也就是說,經過TCP鏈接傳送的數據,無差錯,不丟失,不重複,且按序到達;UDP盡最大努力交付,即不保證可靠交付
      • TCP經過校驗和,重傳控制,序號標識,滑動窗口、確認應答實現可靠傳輸。
      • UDP具備較好的實時性,工做效率比TCP高,適用於對高速傳輸和實時性有較高的通訊或廣播通訊。
      • 每一條TCP鏈接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通訊
      • TCP對系統資源要求較多,UDP對系統資源要求較少。
      • 注:UDP通常用於即時通訊(QQ聊天 對數據準確性和丟包要求比較低,但速度必須快),在線視頻等
    • 三次揮手四次握手
      • 三次握手:
        • 第一次握手:創建鏈接時,客戶端發送SYN包到服務器,其中包含客戶端的初始序號seq=x,並進入SYN_SENT狀態,等待服務器確認。
        • 第二次握手:服務器收到請求後,必須確認客戶的數據包。同時本身也發送一個SYN包,即SYN+ACK包,此時服務器進入SYN_RECV狀態。
        • 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送一個序列號(seq=x+1),確認號爲ack(客戶端)=y+1,此包發送完畢,
        • 客戶端和服務器進入ESTAB_LISHED(TCP鏈接成功)狀態,完成三次握手。
      • 四次揮手:
        • 第一次揮手:首先,客戶端發送一個FIN,用來關閉客戶端到服務器的數據傳送,而後等待服務器的確認。其中終止標誌位FIN=1,序列號seq=u。
        • 第二次揮手:服務器收到這個FIN,它發送一個ACK,確認ack爲收到的序號加一。
        • 第三次揮手:關閉服務器到客戶端的鏈接,發送一個FIN給客戶端。
        • 第四次揮手:客戶端收到FIN後,併發回一個ACK報文確認,並將確認序號seq設置爲收到序號加一。首先進行關閉的一方將執行主動關閉,而另外一方執行被動關閉。
    • 相關的問題
      • 爲何須要三次握手?
        • 目的:爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤。主要防止資源的浪費。
        • 具體過程:當客戶端發出第一個鏈接請求報文段時並無丟失,而是在某個網絡節點出現了長時間的滯留,以致於延誤了鏈接請求在某個時間以後纔到達服務器。這應該是一個早已失效的報文段。可是服務器在收到此失效的鏈接請求報文段後,覺得是客戶端的一個新請求,因而就想客戶端發出了確認報文段,贊成創建鏈接。假設不採用三次握手,那麼只要服務器發出確認後,新的鏈接就能夠創建了。可是因爲客戶端沒有發出創建鏈接的請求,所以不會管服務器的確認,也不會向服務器發送數據,但服務器卻覺得新的運輸鏈接已經創建,一直在等待,因此,服務器的資源就白白浪費掉了。
      • 若是在TCP第三次握手中的報文段丟失了會出現什麼狀況?
        • 客戶端會認爲此鏈接已創建,若是客戶端向服務器發送數據,服務器將以RST包響應,這樣就能感知到服務器的錯誤了。
      • 爲何要四次揮手?
        • 爲了保證在最後斷開的時候,客戶端可以發送最後一個ACK報文段可以被服務器接收到。若是客戶端在收到服務器給它的斷開鏈接的請求以後,迴應完服務器就直接斷開鏈接的話,若服務器沒有收到迴應就沒法進入CLOSE狀態,因此客戶端要等待兩個最長報文段壽命的時間,以便於服務器沒有收到請求以後從新發送請求。
        • 防止「已失效的鏈接請求報文」出如今鏈接中,在釋放鏈接的過程當中會有一些無效的滯留報文,這些報文在通過2MSL的時間內就能夠發送到目的地,不會滯留在網絡中。這樣就能夠避免在下一個鏈接中出現上一個鏈接的滯留報文了。
      • 爲何TCP鏈接的時候是3次?2次不能夠嗎?
        • 由於須要考慮鏈接時丟包的問題,若是隻握手2次,第二次握手時若是服務端發給客戶端的確認報文段丟失,此時服務端已經準備好了收發數(能夠理解服務端已經鏈接成功)據,而客戶端一直沒收到服務端的確認報文,因此客戶端就不知道服務端是否已經準備好了(能夠理解爲客戶端未鏈接成功),這種狀況下客戶端不會給服務端發數據,也會忽略服務端發過來的數據。
        • 若是是三次握手,即使發生丟包也不會有問題,好比若是第三次握手客戶端發的確認ack報文丟失,服務端在一段時間內沒有收到確認ack報文的話就會從新進行第二次握手,也就是服務端會重發SYN報文段,客戶端收到重發的報文段後會再次給服務端發送確認ack報文。
      • 爲何TCP鏈接的時候是3次,關閉的時候倒是4次?
        • 由於只有在客戶端和服務端都沒有數據要發送的時候才能斷開TCP。而客戶端發出FIN報文時只能保證客戶端沒有數據發了,服務端還有沒有數據發客戶端是不知道的。而服務端收到客戶端的FIN報文後只能先回復客戶端一個確認報文來告訴客戶端我服務端已經收到你的FIN報文了,但我服務端還有一些數據沒發完,等這些數據發完了服務端才能給客戶端發FIN報文(因此不能一次性將確認報文和FIN報文發給客戶端,就是這裏多出來了一次)。
      • 爲何客戶端發出第四次揮手的確認報文後要等2MSL的時間才能釋放TCP鏈接?
        • 這裏一樣是要考慮丟包的問題,若是第四次揮手的報文丟失,服務端沒收到確認ack報文就會重發第三次揮手的報文,這樣報文一去一回最長時間就是2MSL,因此須要等這麼長時間來確認服務端確實已經收到了。
      • 若是已經創建了鏈接,可是客戶端忽然出現故障了怎麼辦?
        • TCP設有一個保活計時器,客戶端若是出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求後都會從新復位這個計時器,時間一般是設置爲2小時,若兩小時尚未收到客戶端的任何數據,服務器就會發送一個探測報文段,之後每隔75秒鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認爲客戶端出了故障,接着就關閉鏈接。

高階函數html

    • map
      • 第一個參數接收一個函數名,第二個參數接收一個可迭代對象
      • 利用map,lambda表達式將全部偶數元素加100
# -*- coding:utf8 -*-
l1= [11,22,33,44,55]
ret = map(lambda x:x-100 if x % 2 != 0 else x + 100,l1)
print(list(ret))
# 運行結果: [-89, 122, -67, 144, -45]

# lambda x:x-100 if x % 2 != 0 else x + 100
# 若是 "if x % 2 != 0" 條件成立返回 x-100
# 不成立:返回 x+100
def F(x):
    if x%2 != 0:
        return x-100
    else:
        return x+100
ret = map(F,l1)
print(list(ret))
    • reduce
      • 字符串反轉
      • 把上一次的執行結果單作參數返回
# -*- coding:utf8 -*-
'''使用reduce將字符串反轉'''
s = 'Hello World'
from functools import reduce

result = reduce(lambda x,y:y+x,s)
# # 一、第一次:x=H,y=e  => y+x = eH
# # 二、第二次:x=l,y=eH  => y+x = leH
# # 三、第三次:x=l,y=leH  => y+x = lleH
print( result )      # dlroW olleH
    • filter
      • filter()函數能夠對序列作過濾處理,就是說可使用一個自定的函數過濾一個序列,把序列的每一項傳到自定義的過濾函數裏處理,並返回結果作過濾。
      • 最終一次性返回過濾後的結果。
        • filter()函數有兩個參數:
          • 第一個,自定函數名,必須的
          • 第二個,須要過濾的列,也是必須的
      • 利用 filter、lambda表達式 獲取l1中元素小於33的全部元素 l1 = [11, 22, 33, 44, 55]
l1= [11,22,33,44,55]
a = filter(lambda x: x<33, l1)
print(list(a))
# -*- coding:utf8 -*-
def F(x):
    if x<33:
        return x
b = filter(F,l1)
print(list(b))
    • sorted
      • 經典面試題只 列表排序
students = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
print( sorted(students, key=lambda s: s[2], reverse=False) )    # 按年齡排序
# 結果:[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

def f(x):
    # ('john', 'A', 15)
    return x[2]
print( sorted(students, key=f, reverse=False) )    # 按年齡排序
      • 對字典的value排序
d = {'k1':1, 'k3': 3, 'k2':2}
# d.items() = [('k1', 1), ('k3', 3), ('k2', 2)]
a = sorted(d.items(), key=lambda x: x[1])
print(a)            # [('k1', 1), ('k2', 2), ('k3', 3)]
      • 兩個列表編一個字典
L1 = ['k1','k2','k3']
L2 = ['v1','v2','v3']
print( list(zip(L1,L2)))
# zip(L1,L2) : [('k1', 'v1'), ('k2', 'v2'), ('k3', 'v3')]
# dict( [('k1', 'v1'), ('k2', 'v2'), ('k3', 'v3')] )  = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
      • 匿名函數
(x,y,z):
    return x+y+z

f = lambda x:x if x % 2 != 0 else x + 100
print(f(10))                    # 110
      • 三元運算
name = 'Tom' if 1 == 1 else 'fly'
print(omname)
# 運行結果: T
  • 讀寫文件
    • ReadLine():逐行讀取,適合讀大文件
    • Readlines():一次性讀取全部文件, 將文件按行讀取成列表
    • read():指定讀取指定大小的文件(默認一次讀取全部)
    • 經典面試題:如今有一個5G的文件,用python寫入另外一個文件裏
    • 咱們使用了一個 while 循環來讀取文件內容,每次最多讀取 8kb 大小
    • 這樣能夠避免以前須要拼接一個巨大字符串的過程,把內存佔用下降很是多
#!/usr/bin/python
# -*- coding: utf-8 -*-
def read_big_file_v(fname):
    block_size = 1024 * 8
    with open(fname,encoding="utf8") as fp:
        while True:
            chunk = fp.read(block_size)
            # 當文件沒有更多內容時,read 調用將會返回空字符串 ''
            if not chunk:
                break
            print(chunk)
path = r'C:\aaa\luting\edc-backend\tttt.py'
read_big_file_v(path)
  • 經常使用模塊
    • subprocess模塊
      • subprocess原理以及經常使用的封裝函數
        • 運行python的時候,咱們都是在建立並運行一個進程。像Linux進程那樣,一個進程能夠fork一個子進程,並讓這個子進程exec另一個程序
        • 在Python中,咱們經過標準庫中的subprocess包來fork一個子進程,並運行一個外部的程序。
        • subprocess包中定義有數個建立子進程的函數,這些函數分別以不一樣的方式建立子進程,因此咱們能夠根據須要來從中選取一個使用
        • 另外subprocess還提供了一些管理標準流(standard stream)和管道(pipe)的工具,從而在進程間使用文本通訊。
#一、返回執行狀態:0 執行成功
retcode = subprocess.call(['ping', 'www.baidu.com', '-c5'])

#二、返回執行狀態:0 執行成功,不然拋異常
subprocess.check_call(["ls", "-l"])

#三、執行結果爲元組:第1個元素是執行狀態,第2個是命令結果
>>> ret = subprocess.getstatusoutput('pwd')
>>> ret
(0, '/test01')

#四、返回結果爲 字符串 類型
>>> ret = subprocess.getoutput('ls -a')
>>> ret
'.\n..\ntest.py'


#五、返回結果爲'bytes'類型
>>> res=subprocess.check_output(['ls','-l'])
>>> res.decode('utf8')
'總用量 4\n-rwxrwxrwx. 1 root root 334 11月 21 09:02 test.py\n'
subprocess經常使用函數
subprocess.check_output(['chmod', '+x', filepath])
subprocess.check_output(['dos2unix', filepath])
將dos格式文件轉換成unix格式
    • subprocess.Popen()
      • 實際上,上面的幾個函數都是基於Popen()的封裝(wrapper),這些封裝的目的在於讓咱們容易使用子進程
      • 當咱們想要更個性化咱們的需求的時候,就要轉向Popen類,該類生成的對象用來表明子進程
      • 與上面的封裝不一樣,Popen對象建立後,主程序不會自動等待子進程完成。咱們必須調用對象的wait()方法,父進程纔會等待 (也就是阻塞block)
      • 從運行結果中看到,父進程在開啓子進程以後並無等待child的完成,而是直接運行print。
#一、先打印'parent process'不等待child的完成
import subprocess
child = subprocess.Popen(['ping','-c','4','www.baidu.com'])
print('parent process')

#二、後打印'parent process'等待child的完成
import subprocess
child = subprocess.Popen('ping -c4 www.baidu.com',shell=True)
child.wait()
print('parent process')
child.wait()等待子進程執行
child.poll()          # 檢查子進程狀態
child.kill()            # 終止子進程
child.send_signal()       # 向子進程發送信號
child.terminate()           # 終止子進程
    • subprocess.PIPE 將多個子進程的輸入和輸出鏈接在一塊兒
      • subprocess.PIPE實際上爲文本流提供一個緩存區。child1的stdout將文本輸出到緩存區,隨後child2的stdin從該PIPE中將文本讀取走
      • child2的輸出文本也被存放在PIPE中,直到communicate()方法從PIPE中讀取出PIPE中的文本。
      • 注意:communicate()是Popen對象的一個方法,該方法會阻塞父進程,直到子進程完成
import subprocess
#下面執行命令等價於: cat /etc/passwd | grep root
child1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["grep","root"],stdin=child1.stdout, stdout=subprocess.PIPE)
out = child2.communicate()               #返回執行結果是元組
print(out)
#執行結果: (b'root:x:0:0:root:/root:/bin/bash\noperator:x:11:0:operator:/root:/sbin/nologin\n', None)
分步執行cat /etc/passwd | grep root命
import subprocess

list_tmp = []
def main():
    p = subprocess.Popen(['ping', 'www.baidu.com', '-c5'], stdin = subprocess.PIPE, stdout = subprocess.PIPE)
    while subprocess.Popen.poll(p) == None:
        r = p.stdout.readline().strip().decode('utf-8')
        if r:
            # print(r)
            v = p.stdout.read().strip().decode('utf-8')
            list_tmp.append(v)
main()
print(list_tmp[0])
獲取ping命令執行結果
    • paramiko模塊(詳細介紹
      • Paramiko模塊做用
        • 若是須要使用SSH從一個平臺鏈接到另一個平臺,進行一系列的操做時,
        • 好比:批量執行命令,批量上傳文件等操做,paramiko是最佳工具之一。
        • paramiko是用python語言寫的一個模塊,遵循SSH2協議,支持以加密和認證的方式,進行遠程服務器的鏈接
        • 因爲使用的是python這樣的可以跨平臺運行的語言,因此全部python支持的平臺,如Linux, Solaris, BSD,MacOS X, Windows等,paramiko均可以支持
        • 若是須要使用SSH從一個平臺鏈接到另一個平臺,進行一系列的操做時,paramiko是最佳工具之一
        • 如今若是須要從windows服務器上下載Linux服務器文件:
          • a. 使用paramiko能夠很好的解決以上問題,它僅須要在本地上安裝相應的軟件(python以及PyCrypto)
          • b. 對遠程服務器沒有配置要求,對於鏈接多臺服務器,進行復雜的鏈接操做特別有幫助。
    • re模塊(詳細介紹

python

mysql

compile(pattern[, flags])git

根據正則表達式字符串建立模式對象github

search(pattern, string[, flags])golang

在字符串中尋找模式面試

match(pattern, 經常使用模塊[, flags])正則表達式

在字符串的開始處匹配模式redis

split(pattern, string[, maxsplit=0])

根據模式的匹配項來分割字符串

findall(pattern, string)

列出字符串中模式的全部匹配項並以列表返回

sub(pat, repl, string[, count=0])

將字符串中全部pat的匹配項用repl替換

escape(string)

將字符串中全部特殊正則表達式字符轉義

  • Python2和Python3的區別
    • 不等於<>比較運算符,python3不識別,pyhon2.7中!=和<>都能運行。
    • print函數的使用,python3必須加括號,python2加不加都行。
    • python2 的默認編碼是ASCII,python3的默認編碼是UTF-8。
    • python3字符串解碼後會在內存裏自動轉換成Unicode,而python2不會。若是在文件頭指定了解碼編碼,python2和python3都會按指定解碼,全部系統都支持Unicode,因此python3只要指定對了解碼編碼,在哪一個系統上均可以正常顯示,python2若是不是gbk編碼的,解碼後windous就會是亂碼。
    • python2中有Unicode數據類型,python3中沒有,字符串都是Unicode格式的str數據類型。
    • 用戶輸入不一樣,python3中只有input()輸出都是str和python2中的raw_input()同樣,而python2中也有input(),輸入字符串要帶引號,數字輸出相應的數字類型
    • python2之前沒有布爾型,0表示False,用1表示True;Python3 把 True 和 False 定義成關鍵字,它們的值仍是 1 和 0,能夠和數字運算。
    • python2的除法中不是浮點數則只返回商,python3除法返回值正常。
    • python3運行程序能夠識別相同目錄下普通文件夾中的模塊,python2只能識別文件夾標識後的包中的模塊。
    • 建立類時,python2分爲經典類和新式類,新式類就是繼承object的類,經典類是沒有繼承的類,而python3中所有是新式類,默認繼承object。在屬性查找時,經典類查找方式爲深度優先,新式類是廣度優先。僅python3中有類的mro函數方法,輸出繼承父類的順序列表。
  • with(上下文管理)
    • 上下文管理器對象存在的目的是管理 with 語句,就像迭代器的存在是爲了管理 for 語句同樣。
    • with 語句的目的是簡化 try/finally 模式。這種模式用於保證一段代碼運行完畢後執行某項操做,即使那段代碼因爲異常、 return 語句或 sys.exit() 調用而停止,也會執行指定的操做。 finally 子句中的代碼一般用於釋放重要的資源,或者還原臨時變動的狀態。
    • ==上下文管理器協議包含enter和exit兩個方法==。 with 語句開始運行時,會在上下文管理器對象上調用enter方法。 with 語句運行結束後,會在上下文管理器對象上調用exit方法,以此扮演 finally 子句的角色。
    • ==執行 with 後面的表達式獲得的結果是上下文管理器對象,把值綁定到目標變量上(as 子句)是在上下文管理器對象上調用enter方法的結果==。with 語句的 as 子句是可選的。對 open 函數來講,必須加上 as子句,以便獲取文件的引用。不過,有些上下文管理器會返回 None,由於沒什麼有用的對象能提供給用戶。
  • is和==比較
    • 基本要素
      • id(身份標識)
      • type(數據類型)
      • value(值)。
    • is和==都是對對象進行比較判斷做用的,但對對象比較判斷的內容並不相同。
    • ==比較操做符和is同一性運算符區別
    • ==是python標準操做符中的比較操做符,用來比較判斷兩個對象的value(值)是否相等,例以下面兩個字符串間的比較:
# >> a = 'yms'
# >> b = 'yms'
# >> a == b
# True
    • is也被叫作同一性運算符,這個運算符比較判斷的是對象間的惟一身份標識,也就是id是否相同。
>> x = y = [4,5,6]
>> z = [4,5,6]
>> x == y
True
>> x == z
True
>> x is y
True
>> x is z
False
>>
>> print id(x)
3075326572
>> print id(y)
3075326572
>> print id(z)
3075328140
    • 前三個例子都是True,這什麼最後一個是False呢?x、y和z的值是相同的,因此前兩個是True沒有問題。至於最後一個爲何是False,看看三個對象的id分別是什麼就會明白了。
    • 下面再來看一個例子,同一類型下的a和b的(a==b)都是爲True,而(a is b)則否則。
>> a = 1 #a和b爲數值類型
>> b = 1
>> a is b
True
>> id(a)
14318944
>> id(b)
14318944
>> a = 'cheesezh' #a和b爲字符串類型
>> b = 'cheesezh'
>> a is b
True
>> id(a)
42111872
>> id(b)
42111872
>> a = (1,2,3) #a和b爲元組類型
>> b = (1,2,3)
>> a is b
False
>> id(a)
15001280
>> id(b)
14790408
>> a = [1,2,3] #a和b爲list類型
>> b = [1,2,3]
>> a is b
False
>> id(a)
42091624
>> id(b)
42082016
>> a = {'cheese':1,'zh':2} #a和b爲dict類型
>> b = {'cheese':1,'zh':2}
>> a is b
False
>> id(a)
42101616
>> id(b)
42098736
>> a = set([1,2,3])#a和b爲set類型
>> b = set([1,2,3])
>> a is b
False
>> id(a)
14819976
>> id(b)
14822256
  • 經過以上三個示例能夠看出,只有數值型和字符串型的狀況下,a is b才爲True,當a和b是tuple,list,dict或set型時,a is b爲False

效果圖Python基礎到此就分解完了,有不足的地方請留言,多多支持,持續更新中哦!!

相關文章
相關標籤/搜索