python 面試真題


0、Python是什么?

復制代碼
  • Python是一種解釋型語言。但是跟C和C的衍生語言不同,Python代碼在運行之前不需要編譯。其他解釋型語言還包括PHP和Ruby。
  • Python是動態類型語言,指的是在聲明變量時,不需要說明變量的類型。可以直接編寫類似x=111x="Hello World"這樣的代碼,程序不會報錯。
  • Python是一門強類型語言,是指不容忍隱式的類型轉換,比如字符串類型的數字和整型的數字進行比較不會成立。
  • Python非常適合面向對象的編程(OOP),因為它支持通過組合(composition)與繼承(inheritance)的方式定義類(class)。
  • 在Python語言中,函數是第一類對象(first-class objects)。這指的是它們可以被指定給變量,函數既能返回函數類型,也可以接受函數作為輸入。類(class)也是第一類對象。
  • Python代碼編寫快,而運行速度比編譯語言通常要慢。但是Python允許加入基於C語言編寫的擴展,也常被用作“膠水語言”。因此我們能夠優化代碼,消除瓶頸。比如說numpy就是一個很好地例子,它的運行速度非常快。
  • Python用途非常廣泛——爬蟲、Web 程序開發、桌面程序開發、自動化、科學計算、科學建模、大數據應用、圖像處理、人工智能等等。
  • Python包含八種數據類型:字符串、元組、字典、列表、集合、布爾類型、整型、浮點型。擁有三大特性:封裝、繼承、多態。最顯著特點是采用縮進/4個空格(不能混用)表示語句塊的開始和結束。
  • Python的標識符命名規則有:可以由字母下划線數字組成,不能以數字開頭,不能與關鍵字重名,不能含有特殊字符和空格,使用大駝峰、小駝峰式命名。Python區分大小寫

 

_單下划線開頭:聲明為私有變量,通過from M import * 方式將不導入所有以下划線開頭的對象,包括包、模塊、成員。
單下划線結尾_:為了避免與python關鍵字的命名沖突。
__雙下划線開頭:模塊內的成員,表示私有成員,外部無法直接調用
__雙下划線開頭雙下划線結尾__:指那些包含在用戶無法控制的名字空間中的“魔術”對象或屬性,如類成員的name 、doc、init、import、file、等。
表達式:算術運算符  + - * / // %   ; 比較運算符 >  <  >=  <= !=  ; 邏輯運算符 and or not ;判斷是否為同一對象:is、is not ; 判斷是否屬於另一個對象: in  、not in。
函數支持遞歸、默認參數值、可變參數、閉包,實參與形參之間的結合是傳遞對象的引用。另外還支持字典、集合、列表的推導式。
Python3中的print函數代替Python2的print語句 Python3中的Str類型代表Unicode字符串,Python2中的Str類型代表bytes字節序列 Python3中的 / 返回浮點數,Python2中根據結果而定,能被整除返回整數,否則返回浮點數 Python3中的捕獲語法 except exc as var 代替Python2中的 except exc, var
復制代碼

 

 

1、Python新式類&舊式類的區別

復制代碼
Python2中默認都是舊式類,除非顯式繼承object才是新式類; Python3中默認都是新式類,無需顯式繼承object。

 新式類對象可以直接通過__class__屬性獲取自身類型:實例對象a1.__class__ 、type(實例對象a1)結果為:<class '__main__.A1'>; 舊式類為 __main__.A、<type 'instance'>

 多繼承時,經典類搜索父類的順序: 先深入繼承樹左側,再返回開始找右側,菱形搜索,深度優先搜索(MRO算法); 新式類搜索父類的順序: 先水平搜索,然后再向上移動,廣度優先搜索(C3算法)。

 新式類增加了__slots__內置屬性, 可以把實例屬性的種類鎖定到__slots__規定的范圍之中。

 新式類增加了__getattribute__方法

復制代碼

 

 

2、如何在一個函數內部修改全局變量  

復制代碼
a = 10    

def info():
  print(a)

def foo():
  global a
  a = 22
  print(a)

print(a)
info()
foo()

# 經過foo函數的修改,a的值已變為22
print(a)
info()
復制代碼

 

 

3、列出5個python標准庫

標准庫:sys、os、re、urllib、logging、datetime、random、threading、multiprocessing、base64
第三方庫:requests、Scrapy、gevent、pygame、pymysql、pymongo、redis-py、Django、Flask、Werkzeug、celery、IPython、pillow

 

 

4、字典如何刪除鍵和合並兩個字典

復制代碼
di = {"name": "power"}
ci = {"age": 18}

# 刪除鍵
del di["name"]

# 合並字典
di.update(ci)
復制代碼

 

 

5、python實現列表去重的方法

復制代碼
li = [2, 2, 1, 0, 0, 1, 2]

# 方法一:使用set方法
sl = list(set(li))


# 方法二:額外使用一個列表
li2 = list()
for i in li:
    if i not in li2:
        li2.append(i)
      

# 方法三:使用列表的sort方法
l1 = ['b','c','d','c','a','a'] l2 = list(set(l1)) l2.sort(key=l1.index) print(l2)

l1 = ['b','c','d','c','a','a']
l2 = sorted(set(l1),key=l1.index)
print(l2)

 
         
復制代碼

 

 

6、一句話解釋什么樣的語言能夠用裝飾器?

復制代碼
裝飾器本質上是一個Python函數,他可以讓其他函數在不需要做任何代碼改動的前提下額外增加功能,裝飾器接收一個函數作為參數,返回值也是一個函數。
誰可以用:
函數可以作為參數傳遞的語言,就可以使用裝飾器。
作用:在不改變源代碼的情況下添加新的功能。
使用場景:插入日志、性能測試(計算函數運行時間)、事務處理(讓函數實現事務的一致性)、緩存、權限校驗等場景

問題:函數A接收整數參數n,返回一個函數B,函數B是將函數A的參數與n相乘后的結果返回

def info(func):
  def foo(n):
    res = func(n)
    return res * n
  return foo

@info
def func(n):
  return n

func(2)
[Out]: 4
復制代碼

 

 

7、python內建數據類型有哪些 

復制代碼
# 不可變類型
int      整形
str      字符串
float    浮點型
tuple  元組

bool 布爾型

# 可變類型 list 列表 dict 字典
復制代碼

 

 

8、簡述面向對象中__new__和__init__區別

復制代碼
1、__new__必須接收一個名為 cls 的參數,代表的是當前類對象。
2、__new__必須有返回值,返回的是由當前類對象創建的實例對象,而__init__什么都不返回。
3、__init__必須接收一個名為 self 的參數,代表的是由__new__方法所創建的實例對象。
4、只有在__new__方法返回一個 cls 的實例時,后面的__init__才能被調用。
5、當創建一個新實例對象是,自動調用__new__方法, 當初始化一個實例時調用__init__方法。

ps: __metaclass__ 是創建類時起作用.所以我們可以分別使用__metaclass__,__new____init__來分別在類創建,實例創建和實例初始化的時候做一些小手腳.
復制代碼

 

 

9、進程&線程&協程?

復制代碼

1、進程多與線程進行比較

  • 線程是CPU真正調度的單位,也是進程內的一個執行單元,進程是系統資源分配的基本單位;
  • 一個進程內至少有一個線程,同一個進程內的線程共享進程的資源,而進程有自己獨立的地址空間,每個進程各自有獨立的資源互不干擾。
  • 每個獨立的線程必須有一個程序運行的入口、順序執行序列和程序的出口,但是線程不能夠獨立執行,必須依存在進程當中。
  • 不僅進程之間可以並發執行,同一個進程的多個線程之間也可以並發執行。
  • 多線程的意義在與一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

 

2、協程與線程進行比較

  •  一個線程內可以有多個協程,一個進程也可以單獨擁有多個協程,這樣python中則能使用多核CPU。
  • 線程進程都是同步機制,而協程可以選擇同步或異步。
  • 協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態。
復制代碼

 

 

10、列表[1,2,3,4,5],請使用map()函數輸出[1,4,9,16,25],並使用列表推導式提取出大於10的數,最終輸出[16,25]

li = [1, 2, 3, 4, 5]

def info(x):
    return x**2

li2 = [i for i in map(info, li) if i > 10]

 

 

11、python中生成隨機整數、隨機小數、0--1之間小數方法

復制代碼
import random
import numpy as np

# 隨機整數
print(random.randint(0, 99999))

# 隨機小數
print(np.random.randn())

# 隨機 0-1 小數
print(random.random())
復制代碼

 

 

12、避免轉義給字符串加哪個字母表示原始字符串?

復制代碼
import re

a = 'power.top*one'

res = re.match(r'.*', a)

print(res)

# 注:r 只針對於Python代碼生效,與re正則並無關系
復制代碼

 

 

13、<div class="nam">中國</div>,用正則匹配出標簽里面的內容(“中國”),其中class的類名是不確定的

復制代碼
import re

a = '<div class="nam">中國</div>'

res = re.match(r'<div class=".*">(.*)</div>', a)
res.group(1)


res = re.findall(r'<div class=".*">(.*)</div>', a)
print(res[0])
復制代碼

 

 

14、python中斷言方法舉例

 

 

 

15、數據表student有id,name,score,city字段,其中name中的名字可有重復,需要消除重復行,請寫sql語句

select distinct  name from student

 

 

16、10個Linux常用命令

cd、ls、cp、mv、mkdir、touch、cat、grep、echo、pwd、more、tar、tree

 

 

17、python2和python3區別? 

復制代碼
1、Python3 使用 print 必須要以小括號包裹打印內容,比如 print('hi')
   Python2 既可以使用帶小括號的方式,也可以使用一個空格來分隔打印內容,比如 print 'hi'

2、python3的range 返回可迭代對象,python2的range返回列表,xrange返回迭代器
  for ... in 和 list() 都是迭代器

3、python2中使用ascii編碼,python中使用utf-8編碼

4、python2中unicode表示字符串序列,str表示字節序 python3中str表示字符串序列,byte表示字節序列

5、python2中為正常顯示中文,引入coding聲明,python3中不需要

6、python2中是raw_input()函數,python3中是input()函數

復制代碼

 


18、列出python中可變數據類型和不可變數據類型,並簡述原理

可變類型: 列表、字典

不可變類型:字符串、整型、浮點型、元組

 


19、s = "ajldjlajfdljfddd",去重並從小到大排序輸出"adfjl"

復制代碼
s = "ajldjlajfdljfddd"

# set方法
li = list(set(s))
li.sort()        # 注:sort 沒有返回值


# 循環遍歷
li2 = []
for i in s:
    if i not in li2:
        li2.append(i)
li2.sort()
復制代碼

 

20、用lambda函數實現兩個數相乘

復制代碼
nu = lambda x, y: x*y

nu(2, 3)


lambda 函數是一個可以接收任意多個參數(包括可選參數)並且返回單個表達式值的函數。

1、lambda函數比較輕便,即用即扔,很適合的場景是,需要完成一項功能,但此功能只在此一處使用,連名字都很隨意的情況下。
2、匿名函數,一般用來給 filter、map 這樣的函數式編程服務。
3、作為回調函數,傳遞給某些應用,比如消息處理。
復制代碼

 

21、字典根據鍵從小到大排序

dict(sorted(di.items(), key=lambda x: x))

 


22、利用collections庫的Counter方法統計字符串每個單詞出現的次數"kjalfj;ldsjafl;hdsllfdhg;lahfbl;hl;ahlf;h"

復制代碼
from collections import Counter

s = "kjalfj;ldsjafl;hdsllfdhg;lahfbl;hl;ahlf;h"
Counter(s)

# 輸出結果
Counter({'l': 9, 'h': 6, ';': 6, 'f': 5, 'a': 4, 'd': 3, 'j': 3, 's': 2, 'b': 1, 'g': 1, 'k': 1})
復制代碼

 

 

23、字符串a = "not 404 found 張三 99 深圳",每個詞中間是空格,用正則過濾掉英文和數字,最終輸出"張三  深圳

復制代碼
import re

a = "not 404 found 張三 99 深圳"

# 第一種方式
res = re.sub('[^\u4e00-\u9fa5]', '', a).split()
print(res)


# 第二種方式
value = re.findall(r'[^a-zA-Z\d\s]+', a)
print(''.join(value))
復制代碼

 

 

24、filter方法求出列表所有奇數並構造新列表,a =  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

復制代碼
# filter(function, iterable)      function -- 判斷函數。 iterable -- 可迭代對象。

a =  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


def info(x):
    
    if x % 2 == 1:
    
        return x


print(list(filter(info, a)))
復制代碼

 


25、列表推導式求列表所有奇數並構造新列表,a =  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

li = [i for i in a if i % 2 == 1]

 

 

26、正則re.complie作用

re.compile是將正則表達式編譯成一個對象,加快速度,並重復使用

 

 

27、a=(1,)b=(1),c=("1") 分別是什么類型的數據?

元組 : a=(1,)

整型 :b=(1)

字符串 : c=("1")

 

 

28、兩個列表合並為一個列表

復制代碼
#[1,2,5,6,7,8,9]

li1 = [1,5,7,9]
li2 = [2,2,6,8]

# 第一種方法
li3 = list(set(li1 + li2))
print(li3.sort())

# 第二種方法
li1.extent(li2)
li5 = list(set(li1))
li5.sort()
print(li5)

# 第三種方法
li6 = li1 + li2
print(li6)
復制代碼

 

 

29、用python刪除文件和用linux命令刪除文件方法

python:   os.remove(文件名)
linux:       rm  文件名

 

 

30、log日志中,我們需要用時間戳記錄error,warning等的發生時間,請用datetime模塊打印當前時間戳 “2018-04-01 11:38:54”

復制代碼
from datetime import datetime

print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

print(datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S"))


# Out[12]: '2019-03-30 18:57:55'


logging模塊的日志等級:

DEBUG    最詳細的日志信息,典型應用場景是:問題診斷

INFO     信息細程度僅次於DEBUG,通常只記錄關鍵節點信息,用於確認一切都是按照我們預期的那樣進行工作

WARNING   當某些不期望的事情發生時記錄的信息(如,磁盤可用空間較低),但是此時應用程序還是正常運行的

ERROR     由於一個更嚴重的問題導致某些功能不能正常運行時記錄的信息

CRITICAL   當發生驗證錯誤,導致應用程序不能繼續運行時記錄的信息

logging.debug('debug message') logging.info('info message') logging.warn('warn message') logging.error('error message') logging.critical('critical message')  
復制代碼

 

31、寫一段自定義異常代碼

復制代碼
li = [1, 2, 3, 0]

for i in li:
    try:
        if i == 0:
            raise Exception("遍歷得到0")
    except Exception as e:
        print(e)
復制代碼

 

 

32、正則表達式匹配中,(.*)和(.*?)匹配區別?

(.*)是貪婪匹配,會把滿足正則的盡可能多的往后匹配
(.*?)是非貪婪匹配,會把滿足正則的盡可能少匹配

 

 

33、[[1,2],[3,4],[5,6]]一行代碼展開該列表,得出[1,2,3,4,5,6]

li = [[1, 2], [3, 4], [5, 6]]

lis = [j for i in li for j in i]

 

 

34、x="abc",y="def",z=["d","e","f"],分別求出x.join(y)和x.join(z)返回的結果

復制代碼
x="abc"

z=["d","e","f"]

y="def"


x.join(y)
# Out[202]:'dabceabcf'

x.join(z)
# Out[202]: 'dabceabcf'
復制代碼

 

 

35、舉例說明異常模塊中 try except else finally 的相關意義

復制代碼
li = [1, 2, 3, 0]

for i in li:
    try:
         if i == 0:
             raise Exception("遍歷得到0")
     except Exception as e:
         print(e)
     else:
         print(i)
     finally:
         print("順利跑完")
 

# Out[28]:
1
順利跑完
2
順利跑完
3
順利跑完
遍歷得到0
順利跑完


try: 放置可能出現異常的代碼
except: 當出現異常時,執行此處代碼
else: 當程序沒有沒有出現異常時,執行此處代碼
finally:  不管程序是否出現異常,此處代碼都會執行
復制代碼

 

 

36、舉例說明zip()函數用法

 

 

37、a="張明 98分",用re.sub,將98替換為100

res = re.sub(r'\d+', '100', a)
print(res)

 

 

38、寫5條常用sql語句

復制代碼
# 增
insert into student (name, age) values ("power", 22);

# 刪
delete from student where name="power";

# 改
update student set age=12 where name="power";

# 查
select name from student where id=2;
復制代碼

 

 

39、簡述ORM

復制代碼
O  模型類對象    R 關系    M 映射    :    模型類對象和數據庫的表的映射關系


ORM擁有轉換語法的能力,沒有執行SQL 語句的能力,執行SQL語句需要安裝數據庫驅動(python3解釋器需安裝PyMySQL, python2解釋器需安裝mysql)django只能識別mysqldb驅動,需要給pymysql起別名騙過django

ORM作用:
1. 將面向對象的操作數據庫的語法轉換為相對應SQL語句
2. 解決數據庫之間的語法差異性(根據數據庫的配置不同,生成不同的SQL語句) 
復制代碼

 

 

40、a="hello"和b="你好"編碼成bytes類型

a="hello"
print(b'a')

b="你好"
print(b.encode())

 

  

41、提高python運行效率的方法

復制代碼
1、使用生成器,因為可以節約大量內存。 

2、循環代碼的優化,避免重復執行循環代碼。
 
3、核心模塊用Cython  PyPy等,提高效率。
 
4、對於不同場景使用多進程、多線程、協程,充分利用CPU資源。
 
5、多個if elif條件判斷,將最有可能先發生的條件放到前面寫,可減少程序判斷的次數。
復制代碼

 

 

42、遇到bug如何處理 

復制代碼
0、查看報錯信息(錯誤日志信息),分析bug出現原因。

1、在接口的開頭打斷點,單步執行往下走,逐漸縮小造成bug的代碼范圍。

2、【可選】在程序中通過 print() 打印,能執行到print() 說明一般上面的代碼沒有問題,分段檢測程序是否有問題,如果是js的話可以alert或console.log

2、若自己無法解決,利用搜索引擎將報錯信息進行搜索。

3、查看官方文檔,或者一些技術博客。

4、查看框架的源碼。 5、對於bug進行管理與歸類總結,一般測試將測試出的bug用teambin等bug管理工具進行記錄。
復制代碼

 

 

43、正則匹配,匹配日期2018-03-20

復制代碼
a= 'aa20kk18-power03-30oo'

res = re.findall(r'[-\d]+', a)
data = ''.join(res)
print(data)

Out[82]: '2018-03-30'
復制代碼

 

 

44、list=[2,3,5,4,9,6],從小到大排序,不許用sort,輸出[2,3,4,5,6,9]

復制代碼
li = [2, 3, 5, 4, 9, 6]
lis = []
# 冒泡排序 for i in range(len(l)-1): for j in range(len(l)-1): if l[j] > l[j+1]: l[j],l[j+1] = l[j+1], l[j] Out[51]: [2, 3, 4, 5, 6, 9]


# 遞歸刪除
def info():
  nu = min(li)
  li.remove(nu)
  lis.append(nu)
  
  # 如果原列表中仍有數據,再次調用自身進行刪除&追加
  if len(li)> 0:
    info()
  print(lis)

info()

 Out[77]: [2, 3, 4, 5, 6, 9]

復制代碼

 

 

45、寫一個單例模式

復制代碼
# 第一種方法: 基類 __new__ 方法是真正創建實例對象的方法
class Info(object):
 def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):    
            cls._instance = super(Info, cls).__new__(cls, *args, **kwargs)
        return cls._instance


info = Info() 
info2 = Info()
print(id(foo), id(foo2))
#Out[106]: 140395100343696, 140395100343696


# 第二種方法 type元類是python創建類對象的類,類對象創建實例對象時必須調用 __call__方法
class Info(type):   
  def __call__(cls, *args, **kwargs):     
    if not hasattr(cls, '_instance'):    
      #cls._instance = super().__call__(*args, **kwargs)   
      cls._instance = super(Info, cls).__call__(*args, **kwargs)   
    return cls._instance


class Foo(object):   
  __metaclass__ = Info


f = Foo()
f1 = Foo()
print(id(f), id(f1))
#Out[122]: 139622152568912, 139622152568912



# 第三種方法:使用裝飾器
def singleton(cls):
 instances = {} def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper @singleton class Foo(object): pass foo1 = Foo() foo2 = Foo() print foo1 is foo2 # True


單例模式應用場景:
資源共享的情況下,避免由於資源操作時導致的性能或損耗等,如日志文件,應用配置。
控制資源的情況下,方便資源之間的互相通信。如線程池等,1,網站的計數器 2,應用配置 3.多線程池 4數據庫配置 數據庫連接池 5.應用程序的日志應用...
復制代碼

 

46、保留兩位小數

復制代碼
a = 0.255
b = 22

# 保留2位小數
print("%0.2f" % a)

# 補足6位
print("%06d" % b)
復制代碼

 

47、分別從前端、后端、數據庫闡述web項目的性能優化 

復制代碼
前端內容優化:

(1)減少HTTP請求數:這條策略是最重要最有效的,因為一個完整的請求要經過DNS尋址,與服務器建立連接,發送數據,等待服務器響應,接收數據這樣一個消耗時間成本和資源成本的復雜的過程。常見方法:合並多個CSS文件和js文件,利用CSS Sprites整合圖像,Inline Images(使用 data:URL scheme在實際的頁面嵌入圖像數據 ),合理設置HTTP緩存等。
(2)減少DNS查找
(3)避免重定向
(4)使用Ajax緩存
(5)延遲加載組件,預加載組件
(6)減少DOM元素數量:頁面中存在大量DOM元素,會導致javascript遍歷DOM的效率變慢。
(7)最小化iframe的數量:iframes 提供了一個簡單的方式把一個網站的內容嵌入到另一個網站中。但其創建速度比其他包括JavaScript和CSS的DOM元素的創建慢了1-2個數量級。
(8)避免404:HTTP請求時間消耗是很大的,因此使用HTTP請求來獲得一個沒有用處的響應(例如404沒有找到頁面)是完全沒有必要的,它只會降低用戶體驗而不會有一點好處。



cookie優化:

(1)減小Cookie大小
(2)針對Web組件使用域名無關的Cookie



CSS優化:

(1)將CSS代碼放在HTML頁面的頂部
(2)避免使用CSS表達式
(3)使用<link>來代替@import
(4)避免使用Filters


JavaScript優化:

(1)將JavaScript腳本放在頁面的底部。
(2)將JavaScript和CSS作為外部文件來引用:在實際應用中使用外部文件可以提高頁面速度,因為JavaScript和CSS文件都能在瀏覽器中產生緩存。
(3)縮小JavaScript和CSS
(4)刪除重復的腳本
(5)最小化DOM的訪問:使用JavaScript訪問DOM元素比較慢。
(6)開發智能的事件處理程序
(7)javascript代碼注意:謹慎使用with,避免使用eval Function函數,減少作用域鏈查找。


HTML優化:

1、HTML標簽有始終。 減少瀏覽器的判斷時間

2、把script標簽移到HTML文件末尾,因為JS會阻塞后面的頁面的顯示。

3、減少iframe的使用,因為iframe會增加一條http請求,阻止頁面加載,即使內容為空,加載也需要時間

4、id和class,在能看明白的基礎上,簡化命名,在含有關鍵字的連接詞中連接符號用'-',不要用'_'

5、保持統一大小寫,統一大小寫有利於瀏覽器緩存,雖然瀏覽器不區分大小寫,但是w3c標准為小寫

6、清除空格,雖然空格有助於我們查看代碼,但是每個空格相當於一個字符,空格越多,頁面體積越大,像google、baidu等搜索引擎的首頁去掉了所有可以去掉的空格、回車等字符,這樣可以加快web頁面的傳輸。可以借助於DW軟件進行批量刪除 html內標簽之間空格,sublime text中ctrl+a,然后長按shift+tab全部左對齊,清除行開頭的空格

7、減少不必要的嵌套,盡量扁平化,因為當瀏覽器編譯器遇到一個標簽時就開始尋找它的結束標簽,直到它匹配上才能顯示它的內容,所以當嵌套很多時打開頁面就會特別慢。

8、減少注釋,因為過多注釋不光占用空間,如果里面有大量關鍵詞會影響搜索引擎的搜索

9、使用css+div代替table布局,去掉格式化控制標簽如:strong,b,i等,使用css控制

10、代碼要結構化、語義化

11、css和javascript盡量全部分離到單獨的文件中

12、除去無用的標簽和空標簽

13、盡量少使用廢棄的標簽,如b、i等,盡管高版本瀏覽器是向后兼容的



服務器優化:

(1)CDN:把網站內容分散到多個、處於不同地域位置的服務器上可以加快下載速度。
(2)GZIP壓縮
(3)設置ETag:ETags(Entity tags,實體標簽)是web服務器和瀏覽器用於判斷瀏覽器緩存中的內容和服務器中的原始內容是否匹配的一種機制。
(4)提前刷新緩沖區

 
后端優化:

1.SQL優化,最常見的方式是,優化聯表查詢,以及優化索引。這里面包括,盡量使用left join 替代 where聯表;當碰到,頻繁查詢字段A和字段B,以及AB聯合查詢的情況時,對AB做聯合索引,能夠有效的降低索引存儲空間,提升查詢效率。在復雜聯表的情況下,可以考慮使用 Memory中間表。

2.主從數據庫和讀寫分離,主從分庫是用來應對訪問量增加,帶來頻繁讀寫導致數據庫的訪問和操作性能下降的問題。對數據庫的操作,為了保證數據的完整性,通常涉及到鎖的機制的問題。MySQL的InnoDB引擎支持行級鎖,而MyIsAM只支持表鎖定。這樣的話,如果讀寫集中在一個表中的情況下,當訪問量增加,就會造成明顯的性能下降。因此,通過主從數據庫的方式可以實現讀寫分離。一般來說,使用InnoDB來作為寫庫,使用MyISAM作為讀庫。這種方式是缺點當然是,數據庫的維護難度增加,但是通常都會有專門的DBA這個職位來負責。

3.數據庫分庫和分表.有的時候會出現,某個表變得越來越龐大,比如存放message信息表,這樣會造成讀取性能的增加。這種情況下,你可以通過分表的方式來解決。將一個大表切分成若干個表。

4.使用存儲過程中,將一些操作,直接通過存儲過程的方式,預先設置在MySQL,客戶端只需要調用存儲過程就可以操作數據。

5.對動態頁面進行緩存,比如網站首頁,內容頁等這種查詢次數高,數據改動不大的頁面:應用程序讀取數據時,先從緩存中讀取,如果讀取不到或數據已失效,再訪問磁盤數據庫,並將數據再次寫入緩存。

  • 1)直接生成靜態html頁面,需要更新時通過后台成生成新頁面進行覆蓋。然后可以把靜態頁存放到本地硬盤或者同步到CDN上。
  • 2)使用vanish服務器作為方向代理,對php生成的動態頁面進行緩存,由於vanish可以使用內存作為緩存,因此訪問速度更快,且對生成頁面的php代碼不需要做任何的修改,就可以實現靜態頁面緩存。所以可以很好地解決因為一直遺留下來的問題導致代碼修改成本高的情況。缺點也是,提升了運維成本。

6.瀏覽器緩存,通過Cache-Control,以及Last-Modified等控制緩存頭的設置來告訴瀏覽器緩存頁面。這樣不必每次,都從服務器端重復請求新文件,也可以防止用戶頻繁刷新頁面。

 

  代碼優化:

  • 不斷通過for循環去進行查找
  • 錯誤的選擇了容器,導致不斷的遍歷查找
  • 在循環中添加了很多可以移到循環之外只運行一次的函數調用
  • 可以實現成 事件觸發的邏輯 變成了輪詢
  • 函數中存在大量重復調用
  • 避免循環和判斷次數太多,如果多個if else判斷,將最有可能先發生的情況優先判斷
  • 將耗時操作放入celery異步任務中 
復制代碼

 

 

48、簡述同源策略

協議相同: http / https
域名(主機號)相同
端口相同

 

 

49、簡述cookie和session的區別 

復制代碼
cookie數據存儲在客戶端的瀏覽器上,session數據存儲在服務器端。

cookie
通過request對象獲取cookie,通過response對象設置cookie,並且cookie的設置和讀取不能夠同步進行,比如:第一次設置cookie時不能夠立馬獲取。Session的獲取&設置都通過request對象進行。

cookie
不是很安全,別人可以分析存放在本地的COOKIE並進行COOKIE欺騙考慮到安全應當使用session。session存儲在服務器上,相對於cookie更安全。
單個cookie保存的數據不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。 session中多用於存儲敏感、重要的信息,session依賴於cookie。

session會在一定時間內保存在服務器上。當訪問增多,會比較占用服務器的性能考慮到減輕服務器性能方面,應當使用COOKIE。

Session的存儲過程: session通過request對象進行設置,假設將session數據(鍵值對)存儲到數據庫中,會生成一個session_id交由響應對象返回給瀏覽器,瀏覽器會將其保存在本地。之后每一次請求這個網站時會自動攜帶session_id,服務器會根據session_id取出整條session數據。注意:session是依賴於cookie的,但是在瀏覽器中是可以禁用掉cookie的。即:cookie被禁用,session也將失去意義,此條session記錄在服務器中將不能取出。
 
         

5、建議:

 
         
  • 將登陸信息等重要信息存放為SESSION

  • 其他信息如果需要保留,可以放在COOKIE中

復制代碼

 

 

50、簡述any()和all()方法 

any():只要迭代器中有一個元素為真就為真

all():迭代器中所有的判斷項返回都是真,結果才為真

Python中為假的條件:False、None、[]、()、{}、""、0

 

 

51、IOError、AttributeError、ImportError、IndentationError、IndexError、KeyError、SyntaxError、NameError分別代表什么異常

復制代碼
IOError:輸入輸出異常
AttributeError:試圖訪問一個對象沒有的屬性
ImportError:無法引入模塊或包,基本是路徑問題
IndentationError:語法錯誤,代碼沒有正確的對齊
IndexError:下標索引超出序列邊界
KeyError:試圖訪問你字典里不存在的鍵
SyntaxError:Python代碼邏輯語法出錯,不能執行
NameError:使用一個還未賦予對象的變量
復制代碼

 

 

 

52、單位轉換

復制代碼
    
    1 bytes = 8 bit
    1024 bytes = 1KB
    1024 KB = 1 MB
    1024 MB = 1 GB
    1024 GB = 1 TB
    1024 TB = 1 PB
    1024 PB = 1 EB
    1024 EB = 1 ZB

10進制,人類使用。
2進制, 供計算機使用,1,0代表開和關,有和無,機器只認識2進制。
16進制,內存地址空間是用16進制的數據表示, 如0x8049324。

十進制轉二進制: 十進制數除2取余法,即十進制數除2,余數為權位上的數,得到的商值繼續除2,依此步驟繼續向下運算直到商為0為止。

二進制轉十進制: 把二進制數按權展開、相加即得十進制數。

二進制轉八進制: 3位二進制數按權展開相加得到1位八進制數。(注意事項,3位二進制轉成八進制是從右到左開始轉換,不足時補0)。

八進制轉成二進制: 八進制數通過除2取余法,得到二進制數,對每個八進制為3個二進制,不足時在最左邊補零。

二進制轉十六進制:與二進制轉八進制方法近似,八進制是取三合一,十六進制是取四合一。(注意事項,4位二進制轉成十六進制是從右到左開始轉換,不足時補0)。

十六進制轉二進制: 十六進制數通過除2取余法,得到二進制數,對每個十六進制為4個二進制,不足時在最左邊補零。

十進制轉八進制&十進制轉十六進制:1.間接法—把十進制轉成二進制,然后再由二進制轉成八進制或者十六進制。  2.直接法—把十進制轉八進制或者十六進制按照除8或者16取余,直到商為0為止。

八進制轉十進制&十六進制轉十進制:把八進制、十六進制數按權展開、相加即得十進制數。

八進制與十六進制之間的轉換:1.先轉成二進制然后再相互轉換。  2.他們之間的轉換可以先轉成十進制然后再相互轉換。

復制代碼

 

 

53、列出幾種魔法方法並簡要介紹用途

復制代碼
__new__(cls[, ...])       1.實例化對象時第一個被調用的方法     2.其參數直接傳遞給__init__方法處理

__init__(self[, ...])       構造方法,初始化類的時候被調用
__del__(self)            析構方法,當實例化對象被徹底銷毀時被調用(實例化對象的所有指針都被銷毀時被調用)
__call__(self[, args...])     允許一個類的實例像函數一樣被調用:x(a, b) 調用 x.__call__(a, b)
__len__(self)            定義當被 len() 調用時的行為
__repr__(self)            定義當被 repr() 調用時的行為
__str__(self)            定義當被 str() 調用時的行為
__bytes__(self)           定義當被 bytes() 調用時的行為
__hash__(self)           定義當被 hash() 調用時的行為
__bool__(self)           定義當被 bool() 調用時的行為,應該返回 True 或 False
__format__(self, format_spec)        定義當被 format() 調用時的行為
復制代碼

 


54、a = "  hehheh  ",去除首尾空格

a.strip()
Out[95]: 'hehheh'

 

 

 55、用兩種方法去除空格

復制代碼
# 第一種方法:使用split方法分割
a = ' hello world '
b = a.split()
c = ''.join(b)
print(c)
#Out[22]:  ''helloworld''


# 第二種方法:使用re正則
a = ' hello world '
b = re.sub(' ', '', a)
print(b)
#Out[23]:  ''helloworld''


# 第三種方法:使用strip方法去除首尾空格
a = ' helloworld  '
b = a.strip()
print(b)
#Out[24]:  'helloworld'
# rstrip():去除尾部空格; lstrip():去除首部空格
復制代碼

 

 

 

 

 

56、舉例sort和sorted對列表排序,list=[0,-1,3,-10,5,9]

復制代碼
li=[0,-1,3,-10,5,9]


li.sort()
print(li)
Out[90]: [-10, -1, 0, 3, 5, 9]


lis = sorted(li)
print(lis)
Out[90]: [-10, -1, 0, 3, 5, 9]
復制代碼

 

 

 57、對list排序foo = [-5,8,0,4,9,-4,-20,-2,8,2,-4],使用lambda函數從小到大排序

復制代碼
foo = [-5,8,0,4,9,-4,-20,-2,8,2,-4]

# 第一種方式
g = lambda x: x.sort()
g(foo)
print(foo)
# Out[102]: [-20, -5, -4, -4, -2, 0, 2, 4, 8, 8, 9]

# 第二種方式
a = sorted(foo, key=lambda x: x)
print(a)
復制代碼

 

 

58、使用lambda函數對list排序foo = [-5,8,0,4,9,-4,-20,-2,8,2,-4],輸出結果為[0,2,4,8,8,9,-2,-4,-4,-5,-20],正數從小到大,負數從大到小

復制代碼
foo = [-5, 8, 0, 4, 9, -4, -20, -2, 8, 2, -4]

g = sorted(foo, key=lambda x:(x<0, abs(x)))

print(g)

Out[113]: [0, 2, 4, 8, 8, 9, -2, -4, -4, -5, -20]
復制代碼

 

 

58、字典分別按照key、value排序 

復制代碼
a = {'y': 2, 'x':1, 'z': 3}

# 按照key排序 - 升序
g = sorted(a.items(), key=lambda x: x[0])
dict(g)

[Out]:{'x': 1, 'y': 2, 'z': 3}


# 按照key排序 - 降序
g = sorted(a.items(), key=lambda x: x[0], reverse=True)
dict(g)

[Out]: {'z': 3, 'y': 2, 'x': 1}


# 按照value排序 - 升序
g = sorted(a.otems(), key=lambda x: x[1])
dict(g)

[Out]: {'x': 1, 'y': 2, 'z': 3}


# 按照value排序 - 降序
g = sorted(a.items(), key=lambda x: x[1], reverse=True)
dict(g)

[Out]: {'z': 3, 'y': 2, 'x': 1}
復制代碼

 

 

 

59、列表嵌套字典,根據年齡排序

alist = [{'name':'a','age':20},{'name':'b','age':30},{'name':'c','age':25}]

def sort_by_age(list1):
  
  return sorted(alist, key=lambda x:x['age'], reverse=True)

 

 

60、以下代碼的輸出結果,為什么?

復制代碼
list = ['a','b','c','d','e']
print(list[10:])


代碼將輸出[],不會產生IndexError錯誤,就像所期望的那樣,嘗試用超出成員的個數的index來獲取某個列表的成員。例如,嘗試獲取list[10]和之后的成員,會導致IndexError。
然而,嘗試獲取列表的切片,開始的index超過了成員個數不會產生IndexError。
復制代碼

 

 

61、給定兩個列表,怎么找出他們相同的元素和不同的元素?

A = [1,2,3]
B = [3,4,5]

A,B 中相同元素: print(set(A) & set(B))

A,B 中不同元素:  print(set(A) ^ set(B))

 

 

62、列表推導式、字典推導式、生成器

復制代碼
# 列表推導式
li = [i for i in range(1,101) if i % 2 ==0]

# 字典推導式
a = [0, 1, 2]
b = ['a', 'b', 'c']
c = {a[i]:b[i] for i in range(len(a))}

# 生成器
def info(num):
  
  offset = 0
  a = 0
  b = 1

  while offset < num:
    result = a
    a, b = b, a+b
    offset += 1
    yield result

print(list(info(10)))   
復制代碼

 

 

63、正則匹配IPV4地址

IPV4由四組數字組成,中間由.隔開:

a = "169.254.0.112"
 re.search(r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$', a).group()

 

 

64、舉例說明SQL注入和解決辦法

復制代碼
產生原因:程序開發過程中不注意規范書寫 sql 語句和對特殊字符進行過濾,導致客戶端可以通過全局變量POST 和 GET 提交一些 sql 語句正常執行。產生 Sql 注入。

防止辦法:
    a. 過濾掉一些常見的數據庫操作關鍵字,或者通過系統函數來進行過濾。
    b. 對傳入的參數進行編碼轉義。
    c. SQL 語句書寫的時候盡量不要省略小引號(tab 鍵上面那個)和單引號
    d. 提高數據庫命名技巧,對於一些重要的字段根據程序的特點命名,取不易被猜到的
    e. 使用Python的pymysql模塊自帶的方法,給execute傳遞兩個參數,第一個是SQL語句,第二個是列表格式的SQL語句所需參數
復制代碼

 

 

 

65、s="info:xiaoZhang 33 shandong",用正則切分字符串輸出['info', 'xiaoZhang', '33', 'shandong']

復制代碼
s = 'info:xiaoZhang 33 shandong'

# 正則方式
g = re.findall(r'[^:\s]+', s)

 Out[162]: ['info', 'xiaoZhang', '33', 'shandong']




# spilt分隔方式
li = [] li.extend(s.split(":")[0].split() + s.split())

Out[156]: ['info', 'xiaoZhang', '33', 'shandong']
復制代碼

 

 

66、正則匹配以163.com結尾的郵箱 

re.match(r'[0-9a-zA-Z_]{0,19}@163.com', text).group()

 

 

67、遞歸求和 

復制代碼
def info(num):

    if num > 0:
        res = num + info(num - 1)
    else:
        res = 0
    return res


res = info(10)
print(res)
復制代碼

 

 

68、python字典和json字符串相互轉化方法

json.dumps()   字典轉json字符串
json.loads()     json轉字典

 

 

69、快排

復制代碼
def info(li):
    
    midpivot = lil[0]
    lesspivot = [i for i in li[1:] if i <= midpivot]
    biggpivot = [i for i in li[1:] if i > midpivot]
    lesspivot.sort()
    biggpivot.sort()

    result = lesspivot + [midpivot] + biggpivot
    print(result)
復制代碼

 

 

 

70、RESTFul風格 

復制代碼
# 1、后端API定義規范
請求方法    請求地址    后端操作
GET        /goods     獲取所有商品
POST     /goods      增加商品
GET       /goods/1    獲取編號為1的商品
PUT       /goods/1    修改編號為1的商品
DELETE    /goods/1    刪除編號為1的商品

# 2、盡量將API部署在專用域名之下。
https://api.example.com

# 將API的版本號放入URL。
http://www.example.com/app/2.0/foo

# 3、路徑
資源作為網址,只能有名詞,不能有動詞,而且所用的名詞往往與數據庫的表名對應。
API中的名詞應該使用復數。無論子資源或者所有資源。

# 4、錯誤處理
服務器向用戶返回出錯信息時,返回的信息以error作為鍵名,出錯信息作為value值即可。

# 5、返回結果規范
GET /collection:返回資源對象的列表(數組)
GET /collection/resource:返回單個資源對象
POST /collection:返回新生成的資源對象
PUT /collection/resource:返回完整的資源對象
PATCH /collection/resource:返回完整的資源對象
DELETE /collection/resource:返回一個空文檔

# 6、超媒體
訪問api.github.com會得到一個所有可用API的網址列表。

# 7、服務器返回的數據格式應盡量為json格式,避免使用xml
復制代碼

 

 

71、列舉3條以上PEP8編碼規范

復制代碼
分號:不要在行尾加分號, 也不要用分號將兩條命令放在同一行.

行長度:每行不超過80個字符

縮進:用4個空格來縮進代碼,絕對不要用tab, 也不要tab和空格混用

空行:頂級定義之間空兩行, 方法定義之間空一行

空格:按照標准的排版規范來使用標點兩邊的空格,括號內不要有空格,不要在逗號、分號、 冒號前面加空格, 但應該在它們后面加(除了在行尾),參數列表, 索引或切片的左括號前不應加空格.
導入格式:每個導入應該獨占一行 
類:類應該在其定義下有一個用於描述該類的文檔字符串. 如果你的類有公共屬性(Attributes), 那么文檔中應該有一個屬性(Attributes)段. 並且應該遵守和函數參數相同的格式

繼承:如果一個類不繼承自其它類, 就顯式的從object繼承. 嵌套類也一樣.
復制代碼

 

72、正則匹配中文

復制代碼
import re

title = "Hello World, 你好 世界"

pattern = re.compile(r'[\u4e00-\u9fa5]+')
res = pattern.findall(title)

print(res)
復制代碼

 

 

73、Linux命令重定向 > 和 >>

復制代碼
Linux 允許將命令執行結果 重定向到一個 文件

將本應顯示在終端上的內容 輸出/追加 到指定文件中

> 表示輸出,會覆蓋文件原有的內容

>> 表示追加,會將內容追加到已有文件的末尾
復制代碼

 

 

74、正則表達式匹配出<html><h1>www.baidu.com</h1></html>

g = re.match(r'<(\w+)><(\w+)>(.*)</\2></\1>', a)
g.group()

 

 

75、遍歷列表的同時刪除元素

復制代碼
# 1.新表用於遍歷,舊表用於刪除元素
a = [1, 2, 3, 4, 5, 6, 7, 8]
print(id(a))
print(id(a[:]))
for i in a[:]:
    if i>5:
        pass
    else:
        a.remove(i)
    print(a)
print('-----------')
print(id(a))


# 2.filter
a = [1, 2, 3, 4, 5, 6, 7, 8]
b = filter(lambda x: x>5, a)
print(list(b))


# 3.列表推導式
a = [1, 2, 3, 4, 5, 6, 7, 8]
b = [i  for i in a if i > 5]
print(b)


# 4.倒序刪除
a = [1, 2, 3, 4, 5, 6, 7, 8]
print(id(a))
for i in range(len(a)-1,-1,-1):
    if a[i]>5:
        pass
    else:
        a.remove(a[i])

print(id(a))
print('-----------')
print(a)
復制代碼

 

 

 

76、常見的網絡傳輸協議

復制代碼
HTTP(80)      瀏覽器的網絡請求 協議
HTTPS(443)    瀏覽器的網絡請求 協議
TCP        數據傳輸協議
UDP        數據傳輸協議
SMTP(25)    發郵件協議    
TEL      電話協議  
SMS     短信協議     
POP3    收郵件協議 (需經過郵件服務器,從服務器拉去最新郵件時,服務器不做緩存) 
IMAP    收郵件協議(即使本地郵件刪除,服務器上還有備份的郵件) 
HHT     文件傳輸協議 
復制代碼

 

 

77、8個老師隨機分配3個辦公室, 每個辦公室人數在2~3人

復制代碼
import random

room_nums = 0  # 辦公室數量默認是0
teacher_nums = 0  # 教師數量默認是0
while True:
    r_nums = input("請輸入辦公室的個數:")
    t_nums = input("請輸入老師的個數:")

    # 如果教師數目大於等於辦公室數目,才退出循環,並賦值給room_nums、teacher_nums
    # 否則重新輸入
    if int(r_nums) <= int(t_nums):
        room_nums = int(r_nums)
        teacher_nums = int(t_nums)
        break
    else:
        print("老師數低於辦公室數,請重新輸入")


# 創建辦公室列表,即創建一個嵌套列表 大列表:辦公室  小列表:老師
rooms = []
while room_nums >= 1:
    rooms.append([])
    room_nums -= 1

# 創建老師列表,並添加老師
teachers = []
while teacher_nums>= 1:
    teacher_nums -= 1
    teachers.append("teacher%d"%(teacher_nums+1))

# 開始安排辦公室
# 1.先隨機選出三位老師,依次放到辦公室中
for room in rooms:
    # 隨機選出一名老師,注意teachers長度會變
    index = random.randint(0, len(teachers)-1)
    # pop方法彈出一個元素,並列表中刪除
    teac = teachers.pop(index)
    room.append(teac)

# 2.將剩下的老師,再隨機分配
for teacher in teachers:
    room_index = random.randint(0, len(rooms)-1)
    rooms[room_index].append(teacher)
print("分配結束后:", rooms)
復制代碼

 

 

 

78、靜態函數、類函數、成員函數的區別

復制代碼
類方法: 是類對象的方法,在定義時需要在上方使用 @classmethod 進行裝飾,形參為cls,表示類對象,類對象和實例對象都可調用

類實例方法: 是類實例化對象的方法,只有實例對象可以調用,形參為self,指代對象本身;

靜態方法: 是一個任意函數,在其上方使用 @staticmethod 進行裝飾,可以用對象直接調用,靜態方法實際上跟該類沒有太大關系

\ 實例方法 類方法 靜態方法
a = A() a.foo(x) a.class_foo(x) a.static_foo(x)
A 不可用 A.class_foo(x) A.static_foo(x)
 
         
復制代碼

 

 

79、變量查找順序

復制代碼
L: local 函數內部作用域(本地作用域)

E: enclosing 函數內部與內嵌函數之間

G: global 全局/模塊作用域

B: build-in 內置作用域
復制代碼

 

 

79.字符串 "123" 轉換成 123,不使用內置api,例如 int()

復制代碼
# 方法一:利用 str方法
def atoi(s):
    num = 0
    for v in s:
        for j in range(10):
            if v == str(j):
                num = num * 10 + j
    return num


# 方法二:利用 ord方法
def atoi(s):
    num = 0
    for v in s:
        num = num * 10 + ord(v) - ord('0')
    return num


# 方法三:利用 eval方法
def atoi(s):
    num = 0
    for v in s:
        t = "%s * 1" % v
        n = eval(t)
        num = num * 10 + n
    return num


# 方法四:使用 reduce 並結合方法二
from functools import reduce
def atoi(s):
    return reduce(lambda num, v: num * 10 + ord(v) - ord('0'), s, 0)
復制代碼

 

 

80、MySQL的事務隔離級別

復制代碼
未提交讀(Read Uncommitted):允許臟讀,其他事務只要修改了數據,即使未提交,本事務也能看到修改后的數據值。也就是可能讀取到其他會話中未提交事務修改的數據
提交讀(Read Committed):只能讀取到已經提交的數據。Oracle等多數數據庫默認都是該級別 (不重復讀)。
可重復讀(Repeated Read):可重復讀。無論其他事務是否修改並提交了數據,在這個事務中看到的數據值始終不受其他事務影響。
串行讀(Serializable):完全串行化的讀,每次讀都需要獲得表級共享鎖,讀寫相互都會阻塞

MySQL數據庫(InnoDB引擎)默認使用可重復讀( Repeatable read)


命令行修改數據庫事務隔離級別:transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE}
配置文件中修改數據庫事務隔離級別(最后一行進行添加):transaction-isolation={READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE}

===============================================================================================================
       隔離級別               臟讀(Dirty Read)          不可重復讀(NonRepeatable Read)     幻讀(Phantom Read) 
===============================================================================================================

未提交讀(Read uncommitted)        可能                            可能                      可能

已提交讀(Read committed)          不可能                          可能                       可能

可重復讀(Repeatable read)         不可能                          不可能                     可能

可串行化(Serializable )           不可能                          不可能                     不可能

===============================================================================================================

 

臟讀: 是指事務T1將某一值修改,然后事務T2讀取該值,此后T1因為某種原因撤銷對該值的修改,這就導致了T2所讀取到的數據是無效的。

當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個修改了還未提交的數據。  

栗子: 1.Mary的原工資為1000, 財務人員將Mary的工資改為了8000(但未提交事務)
      2.Mary讀取自己的工資 ,發現自己的工資變為了8000,歡天喜地!
      3.而財務發現操作有誤,回滾了事務,Mary的工資又變為了1000
          像這樣,Mary記取的工資數8000是一個臟數據。

 

不可重復讀 :是指在數據庫訪問時,一個事務范圍內的兩次相同查詢卻返回了不同數據。在一個事務內多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么在第一個事務中的兩次讀數據之間,由於第二個事務的修改,第一個事務兩次讀到的的數據可能是不一樣的。這樣在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。

栗子1.在事務1中,Mary 讀取了自己的工資為1000,操作並沒有完成。

     2.在事務2中,這時財務人員修改了Mary的工資為2000,並提交了事務。
     3.在事務1中,Mary 再次讀取自己的工資時,工資變為了2000。

解決辦法:如果只有在修改事務完全提交之后才可以讀取數據,則可以避免該問題。

 

幻讀: 是指當事務不是獨立執行時發生的一種現象,比如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么就會發生,操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。 

栗子:目前工資為1000的員工有10人。
     1.事務1,讀取所有工資為1000的員工。
     2.這時事務2向employee表插入了一條員工記錄,工資也為1000
    3.事務1再次讀取所有工資為1000的員工 共讀取到了11條記錄, 
解決辦法:一般解決幻讀的方法是增加范圍鎖RangeS,鎖定檢索范圍為只讀,這樣就避免了幻讀。如果在操作事務完成數據處理之前,任何其他事務都不可以添加新數據,則可避免該問題

 

丟失更新:兩個事務同時更新一行數據,最后一個事務的更新會覆蓋掉第一個事務的更新,從而導致第一個事務更新的數據丟失,這是由於沒有加鎖造成的;

復制代碼

 

 

81、TCP協議的 TIME WAITE 狀態

復制代碼
# 1.TIME WAITE狀態:
是指主動關閉方在發送四次揮手的最后一個ACK后會進入TIME_WAIT狀態,也就是這個發起關閉的一方會保持2MSL時間之后才會回到初始狀態。(linux里一個MSL為30s,是不可配置的)

# 2.MSL值是數據包在網絡中的最大生存時間。當主動關閉方進入TIME WAITE狀態后,會使得這個TCP連接在2MSL連接等待期間,定義這個連接的四元數組(local_ip, local_port, remote_ip,remote_port)不能被使用。

# 3.TIME_WAIT狀態產生的原因:
雖然雙方都同意關閉連接了,而且握手的4個報文也都協調和發送完畢,按理可以直接回到CLOSED狀態(就好比從SYN_SEND狀態到ESTABLISH狀態那樣);但因為我們必須要假想網絡是不可靠的,你無法保證你最后發送的ACK報文會一定被對方收到
假設發起主動關閉的一方(client)四次揮手時最后發送的ACK在網絡中丟失,但由於TCP協議的重傳機制,執行被動關閉的一方(server)將會重發上一個數據包FIN,所以這個TIME_WAIT狀態的作用就是用來重發可能丟失的ACK報文。(TCP屬於全雙工連接) # 4.TIME_WAIT兩個MSL的作用: 可靠安全的關閉TCP連接。比如網絡擁塞,主動方最后一個ACK被動方沒收到,這時被動方會對FIN開啟TCP重傳,發送多個FIN包,在這時尚未關閉的TIME_WAIT就會把這些尾巴問題處理掉,不至於對新連接及其它服務產生影響。 # 5.TIME_WAIT狀態如何避免(端口復用): 如果服務器程序停止后想立即重啟,而新的套接字依舊希望使用同一端口,可以通過(SO_REUSEADDR, True)選項避免TIME_WAIT狀態。
復制代碼

 

 

82、網絡的七層協議

復制代碼
OSI的7層從上到下分別是:
  7 應用層 6 表示層 5 會話層 4 傳輸層 3 網絡層 2 數據鏈路層 1 物理層 
  其中高層(即7、6、5、4層)定義了應用程序的功能,下面3層(即3、2、1層)主要面向通過網絡的端到端的數據流。

應用層:與其它計算機進行通訊的一個應用,它是對應應用程序的通信服務的。TELNET,HTTP,FTP,NFS,SMTP

表示層:定義數據格式及加密。加密,ASCII等。

會話層:定義了如何開始、控制和結束一個會話,包括對多個雙向消息的控制和管理,以便在只完成連續消息的一部分時可通知應用,表示層看到的數據是連續的,本質用於區分不同進程的。RPC,SQL等。

傳輸層:負責將數據進行可靠或者不可靠傳遞,負責終端之間的傳送。即:選擇差錯恢復協議還是無差錯恢復協議,以及在同一主機上對不同應用的數據流輸入進行復用,還包括對收到順序不對的數據包的重新排序功能。TCP,UDP,SPX。

網絡層:負責選擇最佳路徑,並保證數據始終沿着最佳路徑傳輸,以數據包為單位。IP,IPX等。

數據鏈路層:在單個鏈路上如何傳輸數據,比如對數據進行處理封裝成數據幀,進行傳遞和錯誤檢測的就是數據鏈路層,數據以幀為單位。ATM,FDDI等。

物理層:為網絡設備之間的數據通信提供傳輸媒體及互連設備,為數據傳輸提供可靠的環境,數據是以比特的形式傳遞的。Rj45,802.3等。


栗子:從西班牙去羅馬的貿易商人
1、 要想貿易獲得成功,首先要有至少一條路,能夠從西班牙通向羅馬。此層為【物理層】
2、有了路是不是就能去貿易了?還要保證路上不會把商人的貨物給磕壞了,要有一層保護的包裝。引出第二層,【數據鏈路層】
3、所謂條條道路通羅馬。並不只有一條路能夠到達羅馬,那么在那么多的選擇中選一條最短的,或者路費的成本最少的,這才符合商人的利益。引出第三層,【網絡層】以上三層為網絡中的下三層,叫媒體層,讓我們來看看另外4層。
4、貿易出門前要先檢查一下自己的貨,有沒有拿錯了,事先要檢查過,如果錯了要重新取貨,引出第四層,【傳輸層】。
5、是不是可以上路了?還不行。我們要和羅馬聯系好, 如果我們這邊的貨物到了那邊賣不出去怎么辦?我們首先要交流、協商一下,看看羅馬的市場情況,能和那邊的另外一個商人合作的話就更好了,這就需要一些外交的關系。叫做【會話層】。
6、好象所有的事情都准備好了,但是商人到了羅馬以后突然發現,他的商隊里沒有人能聽懂羅馬人的話,羅馬人也沒有人能聽懂西班牙語,這個時候,還需要一個翻譯,要么把兩種語言都轉換成一種國際通用語言,比如說英語,要么至少能讓雙方能交流。這里就是【表示層】。
7、到了羅馬了,最終需要在交易所中把商品賣掉,這個交易所就是一個交易平台,相當於各個軟件平台,引出最后一層,【應用層】。
復制代碼

 

 

83、HTTP和TCP的keep-alive區別

復制代碼
1、keep-alive(持久連接/長連接):Http是一個”請求-響應”協議,它的keep-alive主要是為了使用同一個TCP連接來發送和接收多個HTTP請求/應答,而不是為每一個新的請求/應答打開新的連接的方法。可以減少tcp連接建立次數,也意味着可以減少TIME_WAIT狀態連接,以此提高性能(更少的tcp連接意味着更少的系統內核調用,socket的accept()和close()調用)。

2、HTTP的keep-alive:   HTTP 1.0版本中默認沒有keep-alive操作,是根據瀏覽器是否支持keep-alive而決定的,如果瀏覽器支持的話,它會在請求報頭中添加 'Connection: Keep-Alive' 字段,而服務器做出響應時也會在響應報頭中添加 'Connection: Keep-Alive',這樣就會保持當前的連接而不會中斷。HTTP 1.1版本中默認所有都是持久連接。 需要特別注意的是:長時間的tcp連接容易導致系統資源無效占用。配置不當的keep-alive,有時比重復利用連接帶來的損失還更大。所以,正確地設置keep-alive timeout時間非常重要。keep alive_timout時間值意味着:一個http產生的tcp連接在傳送完最后一個響應后,還需要hold住keep alive_timeout秒后,才開始關閉這個連接。 3、TCP的keep-alive:
Tcp的keep-alive是Tcp協議的一種保鮮裝置,當鏈接建立之后,如果應用程序或者上層協議一直不發送數據,或者隔很長時間才發送一次數據的情況下,TCP協議需要考慮當鏈接很久沒有數據報文傳輸時確定對方是否還在線,到底是掉線了還是確實沒有數據傳輸,鏈接還需不需要保持。在此情況下,當超過一段時間之后,服務器自動發送一個監測包(數據為空的報文)
如果對方回應了這個報文,說明對方還在線,鏈接可以繼續保持,如果對方沒有報文返回,並且重試了多次之后則認為鏈接丟失,沒有必要保持鏈接。


4、http keep-alive與tcp keep-alive: http keep-alive是為了讓tcp活得更久一點,以便在同一個連接上傳送多個http,提高socket的效率。而tcp keep-alive是TCP的一種檢測TCP連接狀況的機制。
tcp keepalive原理,當網絡兩端建立了TCP連接,但雙方沒有任何數據流發送往來,服務器內核就會嘗試向客戶端發送偵測包,來判斷TCP連接狀況(有可能客戶端崩潰、強制關閉了應用、主機不可達等等)。如果沒有收到對方的回答(ack包),則會在指定x時間后再次嘗試發送偵測包,直到收到對方的ack包,如果一直沒有收到對方的ack,一共會嘗試y次數。如果重試y次數后,依然沒有收到對方的ack包,則會丟棄該TCP連接。

長連接概念:所謂長連接,指在一個TCP連接上可以連續發送多個數據包,在TCP連接保持期間,如果沒有數據包發送,需要雙方發檢測包以維持此連接。連接->傳輸數據->保持連接 -> 傳輸數據 -> 直到一方關閉連接

短鏈接概念:短連接是指通信雙方有數據交互時,就建立一個TCP連接,數據發送完成后,則斷開此TCP連接,即每次TCP連接只完成一對 CMPP消息的發送。連接->傳輸數據->關閉連接 

      瀏覽器client發起並建立TCP連接 -> client發送HttpRequest報文 -> server接收到報文->server handle並發送HttpResponse報文給前端->
      發送完畢之后立即調用socket.close方法->client接收response報文->client最終會收到server端斷開TCP連接的信號->client 端斷開TCP連接,調用close方法。

  • HTTP 協議的 KeepAlive 意圖在於連接復用,同一個連接上串行方式傳遞請求-響應數據
  • TCP 的 KeepAlive 機制意圖在於保活、心跳,檢測連接錯誤。

Redis設置長連接:修改redis.conf配置文件的tcp-keepalive=1(啟用長連接), 默認為0(表示禁止長連接)

復制代碼

 

 

84、當保持長連接時,如何判斷一次請求已經完成? 

復制代碼
Content-Length 
Content-Length表示實體內容的長度。瀏覽器通過這個字段來判斷當前請求的數據是否已經全部接收。 所以,當瀏覽器請求的是一個靜態資源時,即服務器能明確知道返回內容的長度時,可以設置Content-Length來控制請求的結束。
但當服務器並不知道請求結果的長度時,如一個動態的頁面或者數據,Content-Length就無法解決上面的問題,這個時候就需要用到Transfer-Encoding字段。 Transfer-Encoding Transfer-Encoding是指傳輸編碼,在上面的問題中,當服務端無法知道實體內容的長度時,就可以通過指定Transfer-Encoding: chunked來告知瀏覽器當前的編碼是將數據分成一塊一塊傳遞的。
當然, 還可以指定Transfer-Encoding: gzip, chunked表明實體內容不僅是gzip壓縮的,還是分塊傳遞的。最后,當瀏覽器接收到一個長度為0的chunked時, 知道當前請求內容已全部接收。
復制代碼

 

 

85、TCP&UDP的區別

復制代碼
TCP :
是一種面向連接的、可靠的字節流服務,一個客戶端和一個服務器在發送數據之前必須先三次握手建立連接。 在一個 TCP 連接中,僅有兩方進行彼此通信。廣播和多播不能用於 TCP。
TCP 采用發送應答機制,所發送的每個報文段都必須得到接收方的應答才認為這個TCP報文段傳輸成功。
TCP 協議的超時重傳機制,發送一個報文段后就啟動定時器,若在一定時間內未收到對方應答就會重新發送這個報文段。
TCP 為了保證不發生丟包,會給每一個包標上序號,也保證了消息的有序性。該消息從服務器端發出順序會以同樣的順序發送到客戶端。
因為TCP必須創建連接,以保證消息的可靠交付和有序性,所以相對於UDP而言TCP速度比較慢。
TCP 數據包報頭的大小是20字節,UDP數據報報頭是8個字節。TCP報頭中包含序列號,ACK號,數據偏移量,保留,控制位,窗口,緊急指針,可選項,填充項,校驗位,源端口和目的端口。而UDP報頭只包含長度,源端口號,目的端口,和校驗和。
TCP 使用滑動窗口機制來實現流量控制,通過動態改變窗口的大小進行擁塞控制,以此避免主機發送得過快而使接收方來不及完全收下。
TCP 用⼀個校驗和函數來檢驗數據是否有錯誤;在發送和接收時都要計算校驗和。
TCP 使用發送應答機制、超時重傳、錯誤校驗、流控和阻塞管理來保證可靠傳輸。
注意:TCP 並不能保證數據一定會被對方接收到,因為這是不可能的。TCP 能夠做到的是,如果有可能,就把數據遞送到接收方;否則就(通過放棄重傳並且中斷連接這一手段)通知用戶。因此准確說 TCP 也不是 100% 可靠的協議,它所能提供的是數據的可靠遞送或故障的可靠通知。
UDP :
是無連接的,不可靠的,沒有序列保證,但是一個快速傳輸的數據報協議。
UDP 缺乏可靠性。UDP 本身不提供確認,序列號,超時重傳等機制。UDP 數據報可能在網絡中被復制,被重新排序。即 UDP 不保證數據報會到達其最終目的地,也不保證各個數據報的先后順序,也不保證每個數據報只到達一次
UDP 數據報是有長度的。每個UDP數據報都有長度,如果一個數據報正確地到達目的地,那么該數據報的長度將隨數據一起傳遞給接收方。而 TCP 是一個字節流協議,沒有任何(協議上的)記錄邊界。
UDP 是無連接的。UDP 客戶和服務器之前不必存在長期的關系。UDP 發送數據報之前也不需要經過握手創建連接的過程。
UDP 支持多播和廣播。
復制代碼

 

 86、閉包是什么?

閉包必須滿足的三個條件:
1、一個外函數中定義了一個內函數。
2、內嵌函數中引用了外部函數的變量。
3、外部函數的返回值是內嵌函數的引用。

 

 

87、中間件是什么?

復制代碼
中間件是一種獨立的系統軟件或服務程序,處在操作系統軟件與用戶的應用軟件的中間,分布式應用軟件借助這種軟件在不同的技術之間共享資源(網絡通信功能),管理計算機資源和網絡通訊,相連接的系統,即使它們具有不同的接口,但通過中間件相互之間仍然能交換信息,以便於運行在一台或多台機器上的多個軟件通過網絡進行交互。
中間件的六大分類:終端仿真/屏幕轉換中間件、數據訪問中間件、遠程過程調用中間件、消息中間件、交易中間件、對象中間件。
 提供 分布環境下通訊服務的中間件:遠程過程調用中間件(Remote Procedure Call)、面向消息的中間件(MesSAge-Oriented Middleware)、對象請求代理中間件(object RequeST Brokers)
  RPC遠程過程調用中間件:主要用於client/server分布式計算
  
  MOM面向消息
中間件:指的是利用高效可靠的消息傳遞機制進行平台無關的數據交流,並基於數據通信來進行分布式系統的集成,提供消息傳遞和消息排隊模型,可在分布環境下擴展進程間的通信。
      比如:IBM的MQSeries、BEA的MessageQ等;消息傳遞和排隊技術特點:通訊程序可在不同的時間運行、對應用程序的結構沒有約束、程序與網絡復雜性相隔離
  ORB對象請求代理中間件:提供一個通信框架,透明地在異構的分布計算環境中傳遞對象請求

django中,中間件其實就是一個類,監聽請求進入視圖之前 響應對象真正到達瀏覽器之前的中間執行過程,django會根據自己的規則在合適的時機執行中間件中相應的方法。在django項目的settings模塊中,有一個 MIDDLEWARE_CLASSES 變量,其中每一個元素就是一個中間件
中間件中一共有四個方法: __init__     # 初始化:無需任何參數,服務器響應第一個請求的時候調用一次,用於確定是否啟用當前中間件 process_request(request)    # 處理請求前:在每個請求上調用,返回None或HttpResponse對象。 process_view(request,view_func,view_args,view_kwargs)      # 處理視圖前:在每個請求上調用,返回None或HttpResponse對象。
process_template_response(request,response)  # 處理模板響應前:在每個請求上調用,返回實現了render方法的響應對象。
process_exception(request,exception)  # 當視圖拋出異常時調用,在每個請求上調用,返回一個HttpResponse對象。 process_response(request,response)  # 處理響應后:所有響應返回瀏覽器之前被調用,在每個請求上調用,返回HttpResponse對象。 Django自定義中間件類時,必須繼承自MiddlewareMixin父類:from django.utils.deprecation import MiddlewareMixin
Flask中的請求鈎子也類似於中間件,通過裝飾器實現的,支持以下四種: before_first_request:第一次請求之前,可以在此方法內部做一些初始化操作。 before_request:每次請求之前,可以對不同請求的url處理不同的業務邏輯。 after_request:每次請求之后,可以使用response對象統一設置cookie。 teardown_request:每次請求之后是否有錯誤,會接受一個參數,參數是服務器出現的錯誤信息。
復制代碼

 

 

 

88、深淺拷貝

復制代碼

淺拷貝:copy.copy(變量);如果淺拷貝的是不可變對象,並沒有產生開辟新的內存空間;如果淺拷貝的是簡單的可變對象,會產生一塊新的內存空間,修改原來值不影響拷貝值;如果淺拷貝的是復雜的可變對象,淺拷貝只拷貝父對象,不會拷貝對象內部的子對象,如果原來值的內部子對象發生改變,會影響到拷貝值,內部子對象僅僅是引用拷貝。淺拷貝有三種形式:切片操作、工廠函數(list(a))、copy模塊中的copy函數
深拷貝:copy.deepcopy(變量);如果深拷貝的是不可變對象,仍然沒有開辟新的內存空間;如果深拷貝的是可變對象,則會產生一塊新的內存空間,修改原來值也不會影響拷貝值;如果深拷貝的是復雜的可變對象,深拷貝會拷貝父對象及其對象內部的所有子對象,如果原來值的內部子對象發生改變,不會影響到拷貝值,二者是獨立存在,互不影響。

只要是拷貝不可變對象,無論淺拷貝還是深拷貝都是引用拷貝,都指向同一個內存空間;
復制代碼

 

 

89、Redis緩存擊穿、緩存穿透、緩存雪崩

復制代碼
緩存穿透
概念:緩存穿透是指查詢一個一定不存在的數據,由於緩存是請求數據不命中時被動寫入的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,在流量大時數據庫可能就掛掉了,通俗說就是惡意用戶模擬請求很多緩存中不存在的數據,由於緩存中都沒有,導致這些請求短時間內直接落在了數據庫上,導致數據庫異常。從系統層面來看像是穿透了緩存層直接達到db。
解決:
布隆過濾器(bloom filter):類似於哈希表的一種算法,將所有可能存在的數據哈希到一個足夠大的bitmap中,在進行數據庫查詢之前會使用這個bitmap進行過濾,如果一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
空值緩存:一種比較簡單的解決辦法,在第一次查詢完不存在的數據后,將該key與對應的空值也放入緩存中,只不過設定為較短的失效時間,最長不超過五分鍾。,這樣則可以應對短時間的大量的該key攻擊,設置為較短的失效時間是因為該值可能業務無關,存在意義不大,且該次的查詢也未必是攻擊者發起,無過久存儲的必要,故可以早點失效。



緩存雪崩
概念:緩存雪崩是指在我們設置緩存時采用了相同的過期時間,導致緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。
解決:
線程互斥:只讓一個線程構建緩存,其他線程等待構建緩存的線程執行完,重新從緩存獲取數據才可以,每個時刻只有一個線程在執行請求,減輕了db的壓力,但缺點也很明顯,降低了系統的qps。
交錯失效時間:可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鍾隨機,這樣每一個緩存的過期時間的重復率就會降低,就很難引發集體失效的事件。



緩存擊穿
概念:對於一些設置了過期時間的key,如果這些key在某些時間點被超高並發地訪問,是一種非常“熱點”的數據。這個時候可能會發生緩存被“擊穿”的問題,和緩存雪崩的區別在於:緩存擊穿是針對某一/幾個key緩存,緩存雪崩則是很多key。當緩存在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的並發請求過來,這些請求發現緩存過期一般都會從后端DB加載數據並回設到緩存,這個時候大並發的請求可能會瞬間把后端DB壓垮。
比如:微博有一個熱門話題的功能,用戶對於熱門話題的搜索量往往在一些時刻會大大的高於其他話題,這種我們成為系統的“熱點“,由於系統中對這些熱點的數據緩存也存在失效時間,在熱點的緩存到達失效時間時,此時可能依然會有大量的請求到達系統,沒有了緩存層的保護,這些請求同樣的會到達db從而可能引起故障。擊穿與雪崩的區別即在於擊穿是對於特定的熱點數據來說,而雪崩是全部數據。
解決:
二級緩存:對於熱點數據進行二級緩存,並對於不同級別的緩存設定不同的失效時間,則請求不會直接擊穿緩存層到達數據庫。
互斥鎖(mutex key): 只讓一個線程構建緩存,其他線程等待構建緩存的線程執行完,重新從緩存獲取數據即可。
LRU算法:根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如果數據最近被訪問過,那么將來被訪問的幾率也更高”。最常見的實現是使用一個鏈表保存緩存數據,緩存步驟:
    首先將新數據放入鏈表的頭部
    在進行數據插入的過程中,如果檢測到鏈表中有數據被再次訪問也就是有請求再次訪問這些數據,那么就其插入的鏈表的頭部,因為它們相對其他數據來說可能是熱點數據,具有保留時間更久的意義
    最后當鏈表數據放滿時將底部的數據淘汰,也就是不常訪問的數據
復制代碼

 

 

90、實現WSGI協議的框架

復制代碼
WSGI協議:必須同時實現 web server web application;WSGI不是服務器,python模塊,框架,API或者任何軟件,只是一種規范,描述web server如何與web application通信的規范。
WSGI協議其實是定義了一種server與application解耦的規范,即可以有多個實現WSGI server的服務器,也可以有多個實現WSGI application的框架,可以選擇任意的server和application組合實現自己的web應用。例如uWSGI和Gunicorn都是實現了WSGI server協議的服務器,Django,Flask是實現了WSGI application協議的web框架,可以根據項目實際情況搭配使用。

WSGI server:
負責從客戶端接收http請求,將request轉發給WSGI application,將application返回的response返回給客戶端。
WSGI application:
接收由server轉發的request,並處理請求,最后將response處理結果返回給server。application中可以包括多個棧式的中間件(middlewares),這些中間件需要同時實現server與application,可以在WSGI服務器與WSGI應用之間起調節作用:對服務器來說,中間件扮演應用程序,對應用程序來說,中間件扮演服務器。
WSGI application應該實現為一個可調用對象,例如函數、方法、類(包含`call`方法)。需要接收兩個參數:1、一個字典,該字典可以包含了客戶端請求的信息以及其他信息,可以認為是請求上下文,一般叫做environment(編碼中多簡寫為environ、env) 2、一個用於發送HTTP響應狀態(HTTP status )、響應頭(HTTP headers)的回調函數 通過回調函數將響應狀態和響應頭返回給server,同時返回響應正文(response body),響應正文是可迭代的、並包含了多個字符串。

uwsgi:與WSGI一樣是一種通信協議,是uWSGI服務器的獨占協議,用於定義傳輸信息的類型(type of information),每一個uwsgi packet前4byte為傳輸信息類型的描述。
uWSGI是一個web服務器,實現了WSGI協議、uwsgi協議、http協議等。主要特點:超快的性能,低內存占用,多app管理,詳盡的日志功能(可以用來分析app的性能和瓶頸),高度可定制(內存大小限制,服務一定次數后重啟等)。uWSGI服務器自己實現了基於uwsgi協議的server部分,我們只需要在uwsgi的配置文件中指定application的地址,uWSGI就能直接和應用框架中的WSGI application通信


生產環境使用的WSGI服務器: gunicorn:接受從Nginx轉發的動態請求,處理完之后返回給Nginx,由Nginx返回給用戶。 uwsgi:把HTTP協議轉化成語言支持的網絡協議。比如把HTTP協議轉化成WSGI協議,讓Python可以直接使用。 注:響應時間較短的應用中,使用uWSGI+django;如果有部分阻塞請求 Gunicorn+gevent+django有非常好的效率; 如果阻塞請求比較多的話,還是用tornado重寫吧。
復制代碼

 

 

91、簡述Django的MVT模式

復制代碼
### MVC: 瀏覽器 --> Controll

Model : 用於 `Controll(服務器)和數據庫的交互`,對數據庫中的數據進行增、刪、改、查操作。

View : 用於`封裝html,css,js`,生成頁面展示的html內容(數據的呈現方式)。

Controll : 用於 `接收請求,業務處理,返回結果`  & `C和V交互、C和M交互、C和客戶端交互`

C必不可缺



### MVT:瀏覽器 --> 路由器 --> View(MVC中的C) 

M : model  `View (服務器)和數據庫的交互`

V : View 等同於 `MVC的Controll`,接收請求,業務處理,返回結果 & C和V交互 、C和M交互 、C和客戶端交互

T : template  `封裝html,css,js`,負責封裝構造要返回的html(如何顯示數據,產生html界面)。

V必不可缺
復制代碼

 

 

92、Redis高並發的解決方法

復制代碼
  • Redis 主從 + 哨兵(sentinel)

  • Redis Sentinel 集群 + 內網 DNS + 自定義腳本

  • Redis Sentinel 集群 + VIP + 自定義腳本

  • 封裝客戶端直連 Redis Sentinel 端口

    • JedisSentinelPool,適合 Java

    • PHP 基於 phpredis 自行封裝

  • Redis Sentinel 集群 + Keepalived/Haproxy

  • Redis M/S + Keepalived

  • Redis Cluster

  • Twemproxy

  • Codis

 

Django解決高並發方案:HTTP重定向實現負載均衡、DNS負載均衡、反向代理負載均衡

復制代碼

 

93、實現斐波那契數列

復制代碼
# 1、生成器方式
def fibonacci(num):

    a = 0
    b = 1
    offset = 0

    while offset < num:
        result = a
        a, b = b, a+b
        offset += 1

        yield result


result = fibonacci(10)
for i in result:
    print(i)



# 2、迭代器方式

class Fibonacci(object):
    """
    斐波那契數列 : 第一個數為 0 , 第二個數為 1 ,其后的每一個數都由前兩數相加之和
    """

    def __init__(self, num):

        self.offset = 0  # 記錄當前取值的位置
        self.a = 0
        self.b = 1
        self.num = num  # 指定最終輸出數列的長度

    def __iter__(self):

        return self  # 返回自身,自身就是一個迭代器

    def __next__(self):

        if self.offset < self.num:
            result = self.a
            self.a, self.b = self.b, self.a + self.b
            self.offset += 1
            return result
        else:
            raise StopIteration  # 拋出異常,停止迭代


list1 = list(Fibonacci(10))  # 也可使用 元組,列表..接收結果
print(list1)
復制代碼

 

 

94、輸入某年某月某日,判斷今日是當年的第幾天?

復制代碼
import datetime

y = int(input("請輸入4位數字的年份:"))
m = int(input("請輸入月份:"))
d = int(input("請輸入是哪一天"))

targetDay = datetime.date(y,m,d)
dayCount = targetDay - datetime.date(targetDay.year -1,12,31)
print("%s是 %s年的第%s天。"%(targetDay,y,dayCount.days))
復制代碼

 

 

95、Django一次請求的生命周期?

復制代碼

1.wsgi ,請求封裝后交給web框架(Flask,Django)

2.中間件,對請求進行校驗或在請求對象中添加其他相關數據,例如:csrf,request.session

3.路由匹配 根據瀏覽器發送的不同url去匹配不同的視圖函數

4.視圖函數,在視圖函數中進行業務邏輯的處理,可能涉及到:orm,templates

5.中間件,對響應的數據進行處理

6.wsgi,將響應的內容發送給瀏覽器



猴子補丁 : 在運行期間動態修改一個類或模塊(在函數或對象已經定義之后,再去改變它們的行為)
1. 在運行時替換方法、屬性等。 2. 在不修改第三方代碼的情況下增加原來不支持的功能。 3. 在行⾏時為內存中的對象增加patc而不是在磁盤的源代碼中
復制代碼

 

 

96、並行並發、同步異步、阻塞非阻塞

復制代碼

並行: 同一時刻多個任務同時在運行(實現並行:multiprocessing)  CPU運算量大的程序,使用並行會更好

並發:不會在同一時刻同時運行,存在交替執行的情況。(實現並發的: threading)  需要執行較多的讀寫、請求和回復任務的需要大量的IO操作,IO密集型操作使用並發更好。

 
         
同步: 多個任務之間有先后順序執行,一個執行完下個才能執行。

異步: 多個任務之間沒有先后順序,可以同時執行,有時候一個任務可能要在必要的時候獲取另一個同時執行的任務的結果,這個就叫回調!

阻塞: 如果卡住了調用者,調用者不能繼續往下執行,就是說調用者阻塞了。

非阻塞: 如果不會卡住,可以繼續執行,就是說非阻塞的。

同步異步相對於多任務而言,阻塞非阻塞相對於代碼執行而言。
復制代碼

 

 

97、簡述Python的垃圾回收機制

復制代碼
主要是引用計數、垃圾回收機制、內存池機制。
使用引用計數(reference counting)來跟蹤和回收垃圾。在引用計數的基礎上,通過“標記-清除”(mark and sweep)解決容器對象可能產生的循環引用問題,還有通過“分代回收”(generation collection)以空間換時間的方法提高垃圾回收效率。最后使用內存池機制將不使用的內存放到內存當中,而不是直接返回給操作系統。 引用計數:Python在內存中存儲了每個對象的引用計數(reference count)。當Python的某個對象的引用計數降為0時,說明沒有任何引用指向該對象,該對象就成為要被回收的垃圾了,分配給該對象的內存就會釋放出來。
      比如某個新建對象,它被分配給某個引用,對象的引用計數變為1,如果引用被刪除,對象的引用計數為0,那么該對象就可以被垃圾回收。 標記-清除:"標記-清除算法"是為了解決循環引用(reference cycle)問題而提出。它使用了根集的概念,基於tracing算法的垃圾收集器從根集開始掃描,識別出哪些對象可達,哪些對象不可達,並用某種方式標記可達對象,最后垃圾回收器回收那些不可達對象。
      舉個例子,假設有兩個對象o1和o2,而且符合o1.x == o2和o2.x == o1這兩個條件。如果o1和o2沒有其他代碼引用,那么它們就不應該繼續存在。但它們的引用計數都是1。 分代回收:基本思想是,將內存區域分兩塊(或更多),其中一塊代表年輕代,另一塊代表老的一代。針對不同的特點,對年輕一代的垃圾收集更為頻繁,對老代的收集則較少,每次經過年輕一代的垃圾回收總會有未被收集的活對象,這些活對象經過收集之后會增加成熟度,當成熟度到達一定程度,則將其放進老代內存塊中。
      例如,越晚創建的對象更有可能被回收。對象被創建之后,垃圾回收器會分配它們所屬的代(generation)。每個對象都會被分配一個代,而被分配更年輕代的對象是優先被處理的。
內存池機制:Pymalloc機制,為了加速Python的執行效率,Python引入了一個內存池機制,用於管理對小塊內存的申請和釋放。 
      Python中所有小於256個字節的對象都使用pymalloc實現的分配器,而大於256字節的對象則使用系統的malloc分配內存。
      對於Python對象,如整數、浮點數、list,都有獨立的私有內存池,對象間不共享他們的內存池。也就是說如果你分配又釋放了大量的整數,用於緩存這些整數的內存就不能再分配給浮點數。

內存泄漏:指由於疏忽或錯誤造成程序未能釋放已經不再使用的內存。內存泄漏並非指內存在物理上的消失,而是應用程序分配某段內存后,由於設計錯誤,導致在釋放該段內存之前就失去了對該段內存的控制,從而造成了內存的浪費。 不使用一個對象時使用: del object 來刪除一個對象的引用計數就可以有效防止內存泄露問題。可通過Python擴展模塊gc 來查看不能回收的對象的詳細信息;或者通過 sys.getrefcount(obj) 來獲取對象的引用計數,並根據返回值是否為0來判斷是否內存泄露
復制代碼

 

 

98、三次握手

復制代碼
1) Client首先發送一個連接試探,ACK=0 表示確認號無效,SYN = 1 表示這是一個連接請求或連接接受報文,同時表示這個數據報不能攜帶數據,seq = x 表示Client自己的初始序號(seq = 0 就代表這是第0號幀),這時候Client進入syn_sent狀態,表示客戶端等待服務器的回復
2) Server監聽到連接請求報文后,如同意建立連接,則向Client發送確認。TCP報文首部中的SYN 和 ACK都置1 ,ack = x + 1表示期望收到對方下一個報文段的第一個數據字節序號是x+1,同時表明x為止的所有數據都已正確收到(ack=1其實是ack=0+1,也就是期望客戶端的第1個幀),seq = y 表示Server 自己的初始序號(seq=0就代表這是服務器這邊發出的第0號幀)。這時服務器進入syn_rcvd,表示服務器已經收到Client的連接請求,等待client的確認。
3) Client收到確認后還需再次發送確認,同時攜帶要發送給Server的數據。ACK 置1 表示確認號ack= y + 1 有效(代表期望收到服務器的第1個幀),Client自己的序號seq= x + 1(表示這就是我的第1個幀,相對於第0個幀來說的),一旦收到Client的確認之后,這個TCP連接就進入Established狀態,就可以發起http請求了。
復制代碼

 

 

99、Redis為什么比MySQL快?

復制代碼
1、Redis存儲的是k-v格式的數據。查找和操作的時間復雜度都是O(1)常數階,而mysql引擎的底層實現是B+TREE,時間復雜度是O(logn)是對數階的。

2、數據結構簡單,對數據操作也簡單,Redis中的數據結構是專門進行設計的;

3、Redis是單線程的多路復用IO(非阻塞IO), 單線程避免了線程切換的開銷,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗;而多路而復用IO避免了IO等待的開銷,在多核處理器下提高處理器的使用效率可以對數據進行分區,然后每個處理器處理不同的數據。

4、Mysql數據存儲是存儲在表中,查找數據時要先對表進行全局掃描或根據索引查找,這涉及到磁盤的查找,但是順序查找就比較慢。而Redis完全基於內存,會根據數據在內存的位置直接取出,非常快速。

5、使用底層模型不同,它們之間底層實現方式以及與客戶端之間通信的應用協議不一樣,Redis直接自己構建了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求;

補充其他數據庫的模型:

  1、單進程多線程模型:MySQL、Memcached、Oracle(Windows版本);

  2、多進程模型:Oracle(Linux版本);

  3、Nginx有兩類進程,一類稱為Master進程(相當於管理進程),另一類稱為Worker進程(實際工作進程)。啟動方式有兩種:

    (1)單進程啟動:此時系統中僅有一個進程,該進程既充當Master進程的角色,也充當Worker進程的角色。

    (2)多進程啟動:此時系統有且僅有一個Master進程,至少有一個Worker進程工作。

    (3)Master進程主要進行一些全局性的初始化工作和管理Worker的工作;事件處理是在Worker中進行的。

復制代碼

 

 

100、sql語句中where和having哪個執行更快?

復制代碼
sql語句的書寫順序和執行順序時不一樣的,而是按照下面的順序來執行:
from--where--group by--having--select--order by。
from:需要從哪個數據表檢索數據 
where:過濾表中數據的條件 
group by:如何將上面過濾出的數據分組 
having:對上面已經分組的數據進行過濾的條件  
select:查看結果集中的哪個列,或列的計算結果 
order by :按照什么樣的順序來查看返回的數據 

通過執行順序發現where其實是比having先執行,也就是說where速度更快。

where和having的區別:
 “Where” 是一個約束聲明,使用Where來約束來自數據庫的數據,Where是在結果返回之前起作用的,且Where中不能使用聚合函數(例如Sum)。
 “Having”是一個過濾聲明,是在查詢返回結果集以后對查詢結果進行的過濾操作,在Having中可以使用聚合函數。
復制代碼

 

 

101、圖片管理為什么使用FastDFS?為什么不用雲端?它的好處是什么?

復制代碼
FastDFS是開源的輕量級分布式文件存儲系統。它解決了大數據量存儲和負載均衡等問題。特別適合以中小文件(建議范圍:4KB < file_size <500MB)為載體的在線服務。

優勢:
0. FastDFS比七牛雲等雲端存儲更便宜!
1. 只能通過專用的API訪問,不支持posix,降低了系統的復雜度,處理效率高
2. 支持在線擴容,增強系統的可擴展性
3. 支持軟RAID,增強系統的並發處理能力及數據容錯能力。Storage是按照分組來存儲文件,同組內的服務器上存儲相同的文件,不同組存儲不同的文件。Storage-server之間不會互相通信。
4. 主備Tracker,增強系統的可用性。
5. 支持主從文件,支持自定義擴展名
6.文件存儲不分塊,上傳的文件和os文件系統中的文件一一對應。
7. 相同內容的文件只存儲一份,節約磁盤存儲空間。對於上傳相同的文件會使用散列方式處理文件內容,假如是一致就不會存儲后上傳的文件,只是把原來上傳的文件在Storage中存儲的id和ULR返回給客戶端。
復制代碼

 

 

102、為什么使用celery不用線程?

主要是因為並發比較大的時候,線程切換會有開銷時間,假如使用線程池會限制並發的數量;同時多線程間的數據共享維護比較麻煩。
而celery是異步任務處理,是分布式的任務隊列。它可以讓任務的執行同主程序完全脫離,甚至不在同一台主機內。它通過隊列來調度任務,不用擔心並發量高時系統負載過大。它可以用來處理復雜系統性能問題,卻又相當靈活易用。

 

102、什么是線程安全?

線程安全是在多線程的環境下,能夠保證多個線程同時執行時程序依舊運行正確, 而且要保證對於共享的數據可以由多個線程存取,但是同一時刻只能有一個線程進行存取。
多線程環境下解決資源競爭問題的辦法是加鎖來保證存取操作的唯一性。

 

103、GIL鎖對多線程的影響?

復制代碼
GIL的全稱是Global Interpreter Lock(全局解釋器鎖),這把鎖只存在於CPython解釋器當中,與Python語言本身毫無關系,主要為了數據安全所做的決定。它保證了每個CPU在同一時間只能執行一個線程(在單核CPU下的多線程其實都只是並發,不是並行。並行是指兩個或者多個事件在同一時刻發生;而並發是指兩個或多個事件在同一時間間隔內發生。)

在Python多線程下,每個線程的執行方式:

1、獲取GIL

2、執行代碼直到sleep或者是python虛擬機將其掛起。

3、釋放GIL

所以某個線程想要執行,必須先拿到GIL,我們可以把GIL看作是“通行證”,並且在一個python進程中,GIL只有一個。拿不到通行證的線程,就不允許進入CPU執行。

GIL的釋放邏輯是當前線程執⾏超時后會⾃動釋放;在當前線程執⾏阻塞操作時會⾃動釋放;當前執⾏完畢時會釋放。而每次釋放GIL鎖,線程進行鎖競爭、切換線程,會消耗資源。並且由於GIL鎖存在,python里一個進程永遠只能同時執行一個線程(拿到GIL的線程才能執行)。

IO密集型代碼(文件處理、網絡爬蟲等),多線程能夠有效提升效率(單線程下有IO操作會進行IO等待,造成不必要的時間浪費,而開啟多線程能在線程A等待時,自動切換到線程B,可以不浪費CPU的資源,從而能提升程序執行效率),所以多線程對IO密集型代碼比較友好。
復制代碼

 

 

 

  

103、簡述一次HTTP請求經歷了什么?

復制代碼
1、client(瀏覽器)與server 通過 http 協議通訊,http 協議是基於 tcp 協議的一種應用層協議,所以client 與 server 主要通過socket 進行通訊;
2、瀏覽器輸入網址,本地的DNS服務器嘗試解析域名以此獲取域名對應的IP地址
3、若本地DNS服務器不能解析,將會把域名發送到遠程的DNS服務器上進行解析
4、DNS服務器域名解析成功,返回IP地址給瀏覽器,瀏覽器向IP地址發送連接請求
5、瀏覽器和服務器經過三次握手建立TCP連接
6、server服務器這邊 Nginx 首先拿到請求,進行一些驗證,比如負載均衡、黑名單攔截之類的,然后 Nginx 直接處理靜態資源請求,其他請求 Nginx 轉發給后端服務器,現在先假設后端服務器使用的是uWSGI,Nginx 和uWSGI通過uwsgi協議進行通信,uWSGI 拿到請求可以進行一些邏輯,驗證黑名單、判斷爬蟲等。
  根據 wsgi 標准,WSGI application是一個可以被調用的對象,比如函數方法類(包含call)等,並且它需要接收兩個參數,一個字典包含客戶端請求的信息,也可認為是請求上下文(environment/environ/env),另一個用於發送HTTP響應狀態和響應頭的回調函數,然后將拿到的 environs 參數傳遞給 Django。
  Django 根據 wsgi 標准接收請求和 env,Django 拿到請求后自上而下執行 middleware中間件內的相關邏輯,然后匹配所有路由到相應 view 執行邏輯,如果出錯執行 exception middleware 相關邏輯,接着 response 前執行再次返回到middleware 中間件當中執行相關邏輯,但是此次順序是自下而上執行的。
  最后通過 wsgi 標准構造 response,拿到需要返回的數據,設置一些 headers、cookies 之類的,最后將response返回,再通過 uWSGI 給 Nginx ,Nginx 返回給瀏覽器。瀏覽器-服務器通過http協議進行數據交互,http 協議是無狀態協議(post、get、RESTFul設計、服務器 server 模型 epoll、select)
7、瀏覽器接收到數據之后通過瀏覽器自己的渲染功能來顯示這個網頁。

8、瀏覽器和服務器四次揮手斷開連接,先斷開連接的一方需要再次等待2msl報文最大生存的時間
復制代碼

 

 

104、Mysql 數據庫存儲的原理

復制代碼
儲存過程是一個可編程的函數,它在數據庫中創建並保存。它可以有 SQL 語句和一些特殊的控制結構組成。當希望在不同的應用程序或平台上執行相同的函數,或者封裝特定功能時,存儲過程是非常有用的。
 數據庫中的存儲過程可以看做是對編程中面向對象方法的模擬。它允許控制數據的訪問方式。存儲過程通常有以下優點:   1、存儲過程能實現較快的執行速度   2、存儲過程允許標准組件是編程。   3、存儲過程可以用流程控制語句編寫,有很強的靈活性,可以完成復雜的判斷和較復雜的運算。   4、存儲過程可被作為一種安全機制來充分利用。   5、存儲過程能夠減少網絡流量
復制代碼

 

 

105、事務的特性

1、原子性(Atomicity):事務中的全部操作在數據庫中是不可分割的,要么全部完成,要么均不執行。
2、一致性(Consistency):幾個並行執行的事務,其執行結果必須與按某一順序串行執行的結果相一致。
3、隔離性(Isolation):事務的執行不受其他事務的干擾,事務執行的中間結果對其他事務必須是透明的。
4、持久性(Durability):對於任意已提交事務,系統必須保證該事務對數據庫的改變不被丟失,即使數據庫出現故障

 

 

106、數據庫的索引是什么?

復制代碼
數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。索引的實現通常使用 B_TREE。
B_TREE 索引加速了數據訪問,因為存儲引擎不會再去掃描整張表得到需要的數據;相反,它從根節點開始,根節點保存了子節點的指針,存儲引擎會根據指針快速尋找數據。

MyISAM引擎使用B+Tree作為索引結構,葉節點的data域存放的是數據記錄的地址,即:MyISAM索引文件和數據文件是分離的,MyISAM的索引文件僅僅保存數據記錄的地址。
  MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應數據記錄。MyISAM的索引方式也叫做“非聚集”的。

InnoDB引擎也使用B+Tree作為索引結構,但是InnoDB的數據文件本身就是索引文件,葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。這種索引叫做聚集索引
復制代碼

 

 

107、Python的參數傳遞是值傳遞還是引用傳遞 ?

復制代碼
Python的參數傳遞有:位置參數、默認參數、可變參數、關鍵字參數

函數的傳值到底是值傳遞還是引用傳遞,要分情況:

# 不可變參數用值傳遞:

像整數和字符串這樣的不可變對象,是通過拷貝進行傳遞的,因為你無論如何都不可能在原處改變不可變對象

# 可變參數是引用傳遞的:

比如像列表,字典這樣的對象是通過引用傳遞、和C語言里面的用指針傳遞數組很相似,可變對象能在函數內部改變。
復制代碼

 

 

107、迭代器和生成器函數的區別?

復制代碼
迭代器是一個更抽象的概念,任何對象,如果它的類有next方法和iter方法返回自己本身,對於string、list、dict、tuple等這類容器對象,使用for循環遍歷是很方便的。在后台for語句對容器對象調用iter()函數,iter()是python的內置函數。
  iter()會返回一個定義了next()方法的迭代器對象,它在容器中逐個訪問容器內元素,next()也是python的內置函數。在沒有后續元素時,next()會拋出一個StopIteration異常。

生成器(Generator)是創建迭代器的簡單而強大的工具。它們寫起來就像是正規的函數,只是在需要返回數據的時候使用 yield 語句。每次 next()被調用時,生成器會返回它脫離的位置(它記憶語句最后一次執行的位置和所有的數據值)

區別:生成器能做到迭代器能做的所有事,而且因為自動創建了iter()和 next()方法,生成器顯得特別簡潔,而且生成器也是高效的,使用生成器表達式取代列表解析可以同時節省內存。除了創建和保存程序狀態的自動方法,當發生器終結時,還會自動拋出 StopIteration 異常
復制代碼

 

108、談談你對面向對象的理解

復制代碼
面向對象是相對於面向過程而言的。

面向過程語言是一種基於功能分析的、以算法為中心的程序設計方法;

而面向對象是一種基於結構分析的、以數據為中心的程序設計思想。

在面向對象語言中有一個有很重要東西,叫做類。

面向對象有三大特性:封裝、繼承、多態。
復制代碼

 

 

108、打印星星形狀

復制代碼
# 九九乘法表
for i in range(1, 10):
     for j in range(1, i+1):
             print('{}*{}={}'.format(j, i, j*i), end='\t')
     print()


# 菱形星星
def info():
    k = 1
    while k <= 9:
        if k <= 5:
            print(" " * (5 - k), "*" * (2 * k - 1))
        else:
            print(" " * (k - 5), "*" * ((10 - k) * 2 - 1))

        k += 1
復制代碼

 

 

109、線上服務宕機?

使用supervisor,對用戶定義的進程進行啟動,關閉,重啟。針對意外關閉的進程可以進行重啟 ,僅需簡單配置即可,且有web端,狀態、日志查看清晰明了。

 

110、Redis宕機?

復制代碼
主從模式下的宕機區分來看:
1. slave從redis宕機
   在Redis中從庫重新啟動后會自動加入到主從架構中,自動完成同步數據;
如果從數據庫實現了持久化,只要重新假如到主從架構中會實現增量同步。
2. Master 宕機
   假如主從都沒數據持久化,此時千萬不要立馬重啟服務,否則可能會造成數據丟失,正確的操作如下:
  1. 在slave數據上執行SLAVEOF ON ONE,來斷開主從關系並把slave升級為主庫
  2. 此時重新啟動主數據庫,執行SLAVEOF,把它設置為從庫,自動備份數據。
以上過程很容易配置錯誤,可以使用簡單的方法:redis的哨兵(sentinel)的功能。

哨兵(sentinel)的原理:Redis提供了sentinel(哨兵)機制通過sentinel模式啟動redis后,自動監控master/slave的運行狀態,基本原理是:心跳機制+投票裁決 心跳機制:每個sentinel會向其它sentinal、master、slave定時發送消息,以確認對方是否“活”着,如果發現對方在指定時間(可配置)內未回應,則暫時認為對方已掛(所謂的“主觀認為宕機” Subjective Down,簡稱SDOWN)。
 
    投票裁決:若"哨兵群"中的多數sentinel,都報告某一master沒響應,系統才認為該master"徹底死亡"(即:客觀上的真正down機,Objective Down,簡稱ODOWN),通過一定的vote算法,從剩下的slave節點中,選一台提升為master,然后自動修改相關配置。
復制代碼

 

 

111、Redis緩存滿了怎么辦?

① 給緩存服務,選擇合適的緩存逐出算法,比如最常見的LRU。

② 針對當前設置的容量,設置適當的警戒值,比如10G的緩存,當緩存數據達到8G的時候,就開始發出報警,提前排查問題或者擴容。

③ 給一些沒有必要長期保存的key,盡量設置過期時間。

 

 

112、數組、鏈表、堆棧、隊列的區別

復制代碼
數據結構:是指相互之間存在一種或多種特定關系的數據元素集合,簡單理解:數據結構就是描述對象間邏輯關系的學科。比如隊列是一種先進先出的邏輯結構,桟是一種先進后出的邏輯結構,家譜是一種樹形的邏輯結構,

數據存儲結構:是描述數據在計算機中存儲方式的學科,常用的數據存儲方式:順序存儲和非順序存儲。順序存儲是將數據存儲在一塊連續的存儲介質中(比如硬盤或內存),數組就是采用的順序存儲;--舉個例子:從內存中拿出第100個字節到1000個字節間的連續位置,存儲數據;
        非順序存儲是指數據不一定存儲在一塊連續的位置上,只要每個數據知道它前面的數據和后面的數據就可以把數據連續起來了,鏈表即是采用的非順序存儲。
隊列、棧是線性數據結構的典型代表,而數組、鏈表是常用的兩種數據存儲結構;隊列和棧均可以用數組或鏈表的存儲方式實現它的功能!

數組和列表:數組初始化后大小固定,長度不可再變,且數據都已經被賦值,數組中存放的數據類型必須一致;list中可以存放不同類型數據,list的長度是根據元素的多少而相應的發生改變;
      數組不能刪除指定位置的元素,除非重建數組對象;list移除某一元素后,后續元素會前移;
數組和鏈表:數組是使用一塊連續的內存空間保存數據,保存的數據的個數在分配內存的時候就是確定的;鏈表是在非連續的內存單元中保存數據,並且通過指針將各個內存單元鏈接在一起,每個節點的存儲位置保存着它的前驅和后繼結點,最后一個節點的指針指向 NULL,
      鏈表不需要提前分配固定大小存儲空間,當需要存儲數據的時候分配一塊內存並將這塊內存插入鏈表中。
數組和鏈表的區別:

    1.占用的內存空間:鏈表存放的內存空間可以是連續的,也可以是不連續的,數組則是連續的一段內存空間。一般情況下存放相同多的數據數組占用較小的內存,而鏈表還需要存放其前驅和后繼的空間。

    2.長度的可變性:鏈表的長度是按實際需要可以伸縮的,而數組的長度是在定義時要給定的,如果存放的數據個數超過了數組的初始大小,則會出現溢出現象。

    3.對數據的訪問:鏈表方便數據的移動而訪問數據比較麻煩;數組訪問數據很快捷而移動數據比較麻煩。

    鏈表和數組的差異決定了它們的不同使用場景,如果需要很多對數據的訪問,則適合使用數組;如果需要對數據進行很多移位操作,則適合使用鏈表。

 

 隊列:隊列實現了先入先出的語義 (FIFO) 。隊列也可以使用數組和鏈表來實現;隊列只允許在隊尾添加數據,在隊頭刪除數據。但是可以查看隊頭和隊尾的數據。

堆棧:堆棧實現了一種后進先出的語義 (LIFO) 。可以使用數組或者是鏈表來實現它,對於堆棧中的數據的所有操作都是在棧的頂部完成的,只可以查看棧頂部的數據,並只能夠向棧的頂部壓入數據,也只能從棧的頂部彈出數據。
堆:堆可以理解它就是個一個可大可小,隨意分配的內存操作單元;它的特點就是動態的分配內存,適合存放大的數據量!比如一個對象的所有信息,雖然它的引用指向棧中的某個引用變量;所以堆是用來存放創建出來的對象的。

  棧:棧具有數據結構中棧的特點,后進先出,所有存放在它里面的數據都是生命周期很明確,占有的空間確定而且占用空間小。

復制代碼

 

 

113、使用linux命令查詢最近三個小時打開過的文件

復制代碼
# 查找當前目錄下.phtml文件中,最近30分鍾內修改過的文件。
>find . -name '*.phtml' -type f -mmin -30</code>

# 查找當前目錄下.phtml文件中,最近30分鍾內修改過的文件,的詳細情況。
>find . -name '*.phtml' -type f -mmin -30 -ls

# 查找當前目錄下,最近1天內修改過的常規文件。
>find . -type f -mtime -1

# 查找當前目錄下,最近1天前(2天內)修改過的常規文件。
>find . -type f -mtime +1
復制代碼

 

 

113、如何實現支付寶支付?

1、在支付寶沙箱環境下進行,在自己電腦生成公鑰(解密)和私鑰(加密),將自己公鑰設置在沙箱應用中,獲取支付寶對應公鑰,將支付寶公鑰和自己電腦私鑰置於項目中,用於django網站和支付寶之間的通信安全

2、用戶點擊去付款(訂單id),請求django對應視圖,校驗之后使用python工具包調用支付寶支付接口(訂單id,總金額,訂單標題),支付寶返回支付頁面,django引導用戶到支付頁面,用戶登錄並支付,由於本項目沒有公網ip,
  支付寶無法返回給django支付結果,django自己調用支付查詢接口獲取支付結果,返回給客戶支付結果

 

 

 

114、支付寶收不到異步回調通知?

復制代碼
1、支付寶的異步通知需要`使用POST的方式接收`; 

2、http的`header頭為標准頭`;
    例如:application/x-www-form-urlencoded;text/html;charset=utf-8。

3、檢查notify_url`外網post訪問狀態`(不支持除200以外的狀態) ;
    選擇和服務器不同域的一台電腦,在chrome瀏覽器右鍵「檢查」->地址欄輸入notify_url地址->查看Network中的Status是否是200。

4、選擇和服務器不同域的一台電腦,`ping服務器地址是否流暢`;
    長時間后查看是否會有不穩定的情況(偶爾一次斷開)。這也有可能會出現正好有半夜或什么時候有一次沒有收到的情況。 

5、如果您的地址是https,也就是有證書,需加一步`是否是證書問題`;
    只支持官方機構頒發的正版SSL證書,不支持自簽名。
    證書校驗地址參照:https://csr.chinassl.net/ssl-checker.html 服務器到根證書鏈路通暢即可。
    SSL證書校驗命令: openssl s_client -connect ${host}:${port} 或參考 SSL驗證

6、DNS解析校驗:dig ${host} +short 或者 nslookup 回車輸入域名再回車查看;

7、連接有效校驗: time curl -vk https://${host};

注:主要檢查服務器配置,服務器是否開啟寫入權限、防火牆是否開啟、端口443或80是否有開啟且不是假死狀態也沒有被占用、DNS解析是否能夠解析支付寶IP等,檢查程序運行到alipay_notify文件的notify_verify()函數中,在isSign是不是等於true。
復制代碼

 

 

115、支付寶回調通知延時怎么辦?

復制代碼
# 一個訂單在17:30之前未完成付款則超時關閉,用戶在17:29在支付寶完成了支付,但是在17:31才將支付結果回調給我們,此時單子已被超時關閉了,但是用戶也確實是在規定的時間內完成的支付:

1.設置支付訂單的時間與支付寶交易單號的自動關閉時間一致;
2.支付寶有主動查詢交易狀態接口;
3.支付寶可通過接口主動關閉訂單;
4.回調時檢查訂單狀態,若訂單已關閉則直接向支付寶發起退款請求,交易結束。
復制代碼

 

 115、項目中使用什么調試?

           

復制代碼
# 1、在Eclipse+Pydev中調試Django

適用於測試環境。 可進行單步調試,查看變量值,當出現except時,可以用Python標准模塊traceback的print_exc()函數查看函數調用鏈, 是最強大的調試利器。

# 2、使用Django的error page

適用於測試環境。 Django的error page功能很強大,能提供詳細的traceback,包括局部變量的值,以及一個純文本的異常信息。擁有同phpinfo() 一樣的作用,可以展示當前應用的相關設置,包括請求中的 GET, POST and COOKIE 數據以及HTTP環境中的所有重要META fields。

# 3、django-debug-toolbar

不確定是否用於生產環境。聽說功能非常強大。

# 4、輸出log到開發服務器終端中

適用於生產環境。 借助python的logging模塊
復制代碼

 

116、堆內存和棧內存的區別?

復制代碼
1、內存分配方面:

  棧(stack):由編譯器(Compiler)自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧,主要存放的是基本類型類型的數據 如int, float, bool, string 和對象句柄。

  堆(heap): 一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式類似於鏈表。可能用到的關鍵字如下:new、malloc、delete、free等等。

 

2、申請方式方面:

    堆:需要程序員自己申請,並指明大小。在c中malloc函數如 p1=(char *)malloc(10);在C++中用new運算符,但是注意p1、p2本身是在棧中的。因為他們還是可以認為是局部變量。

    棧:由系統自動分配。例如,聲明在函數中一個局部變量 x=2 ;系統會自動在棧中為x開辟空間。

 

3、系統響應方面:

    堆:操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。另外由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。

    棧:只要棧的剩余空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。

 

4、大小限制方面:

    堆:是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

    棧:在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。

 

5、效率方面

    堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便,另外,在WINDOWS下,最好的方式是用 VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活。

    棧:由系統自動分配,速度較快。但程序員是無法控制的。

 

6、存放內容方面

    堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。

    棧:在函數調用時第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的地址然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧,然后是函數中的局部變量。 注意: 靜態變量是不入棧的。當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。

 

7、存取效率方面:

    堆:char *s1 = "Hellow Word";是在編譯時就確定的;

    棧:char s1[] = "Hellow Word"; 是在運行時賦值的;用數組比用指針速度要快一些,因為指針在底層匯編中需要用edx寄存器中轉一下,而數組在棧上直接讀取。

復制代碼

 

 

117、Linux下批量刪除空文件

復制代碼
# Linux下批量刪除空文件(大小等於0的文件)的方法

find . -name "*" -type f -size 0c | xargs -n 1 rm -f

# 刪除指定大小的文件,只要修改對應的 -size 參數就行,例如:
# 刪除1k大小的文件。(但注意 不要用 -size 1k,這個得到的是占用空間1k,不是文件大小1k的)。
find . -name "*" -type f -size 1024c | xargs -n 1 rm -f


如果只要刪除文件夾或者名字連接等,可以相應的改 -type 參數
復制代碼

 

  

118、Linux下批量替換多個文件中的字符串

復制代碼
# sed命令可以批量替換多個文件中的字符串。
sed -i "s/原字符串/新字符串/g" `grep 原字符串 -rl 所在目錄`

# 具體格式如下:
sed -i "s/oldString/newString/g" `grep oldString -rl /path`

# 實例代碼:
sed -i "s/大小多少/日月水火/g" `grep 大小多少 -rl /usr/aa`
sed -i "s/大小多少/日月水火/g" `grep 大小多少 -rl ./`
復制代碼

 

 

119、計算函數運行時間

復制代碼
import datetime
import time

# 第一種方法
start_time = datetime.datetime.now()
end_time = datetime.datetime.now()
final_time = endtime - starttime.seconds
print(final_time)

# 第二種方法
start = time.time()    # 獲取自紀元以來的當前時間(以秒為單位), 返回浮點類型
end = time.time()
final = end-start
print(final) 

# 第三種方法
start = time.clock()    # 返回程序開始或第一次被調用clock() 以來的CPU時間, 返回浮點類型, 獲得的是CPU的執行時間。
end = time.clock()
final = end-start
print(final) 


注:程序執行時間=cpu時間 + io時間 + 休眠或者等待時間
復制代碼

 

120、創建TCP協議的socket套接字

復制代碼
# 1.創建套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2.綁定本地端口
server_socket.bind(("", 9090))

# 3.設置為監聽模式 1>把主動套接字轉為被動套接字  2>告訴操作系統創建一個等待連接隊伍
server_socket.listen(128)

# 4.等待客戶端的鏈接 accept會阻塞等待,直到有客戶端鏈接
client_socket, client_address = server_socket.accept()  # 返回一個新的套接字和客戶端的地址
print("一個新客戶端已經鏈接。。。。")

# 5.接收來自客戶端的數據
date = client_socket.recv(1024)
print("接收到的數據:", date.decode(encoding="utf-8"))

# 6.回送數據給客戶端
client_socket.send("世界之巔".encode(encoding="utf-8"))

# 7.關閉服務客戶端的套接字
client_socket.close()
復制代碼

 

 

121、簡述epoll、poll、select三種模型

復制代碼
select和epoll都是I/O多路復用的方式,但是select是通過不斷輪詢監聽socket實現,epoll是當socket有變化時通過回掉的方式主動告知用戶進程實現。

Select: select函數監視3類文件描述符,分別是writefds、readfds、和exceptfds。調用后select函數后會阻塞,直到有描述符就緒(有數據 可讀、可寫、或者有except)或者超時函數返回(timeout指定等待時間)。select目前幾乎在所有的平台上都支持,良好的跨平台性也是它的一個優點;但是select在單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024(這個數目與系統內存有關,具體數目可以cat/proc/sys/fs/file-max 查看。並且select對於socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低。當套接字比較多的時候,每次select()都要通過遍歷來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。


Poll:本質上和select沒有區別,他將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態,如果設備就緒則加入到設備等待隊列中並繼續遍歷,如果遍歷完所有fd后沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒后它又要再次遍歷fd。因為poll是基於鏈表進行存儲,所以沒有最大連接數限制,但是poll和select一樣,都是通過遍歷來獲取已經就緒的socket,而同時連接的大量客戶端在同一時間內可能只有很少的處於就緒狀態,因此隨着監視的描述符數量的增長,效率也會隨之下降。僅僅只是改善了select的最大連接數量限制的缺陷。


epoll:沒有描述符限制,而是事先通過 epoll_ctl() 預先注冊一個文件描述符,使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。一旦某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用 epoll_wait() 時便得到通知。(此處去掉了遍歷文件描述符的過程,而是通過監聽回調的機制,大大提高了效率)。epoll模型所監視的描述符數量不再受到限制,沒有最大並發連接的限制(1G的內存上能監聽約10萬個端口,具體數目可以 cat /proc/sys/fs/file-max察看);不再采用輪詢的方式,提升了效率,IO的效率不會隨着監視fd的數量的增長而下降,只有活躍可用的FD才會調用callback函數;
復制代碼

 

 

122、Django的用戶權限認證原理?

復制代碼
Django實現的permission體系,在底層被抽象為authentication backends。並且通過TemplateProcessor和RequestContext在模版系統中可以方便的使用,在界面中通過權限來控制提供給某個用戶的顯示。
Django中,所有的authentication backends,可以通過配置settings中的一個變量AUTHENTICATION_BACKENDS來做到,這個變量的類型是元組(Tuple),默認Django的設置是:
AUTHENTICATION_BACKENDS=('django.contrib.auth.backends.ModelBackend    ’)
但是,Django並沒有實現對象級別的權限控制。比方說在論壇系統中,只有管理員和帖子的發布者才有對該帖子對象的修改權限,這就是對象級別而非模型級別的權限控制。
因此,如果需要自己實現對象級別的權限控制,可以很容易的開發或者引用第三方提供的Object level auth
復制代碼

 

 

123、設計一個高並發?

復制代碼
1、部署至少2台以上的服務器構成集群,既防止某台服務器突然宕機,也減輕單台服務器的壓力。
2、頁面進行動靜分離,比如使用Nginx反向代理處理靜態資源,並實現負載均衡。
3、對於查詢頻繁但改動不大的頁面進行靜態化處理。
4、在代理前添加web緩存,在數據庫前增加緩存組件;比如可以使用Redis作為緩存,采用Redis主從+哨兵機制防止宕機,也可以啟用Redis集群。
5、對應用服務所在的主機做集群,實現負載均衡。
6、對數據庫進行讀寫分離,靜態文件做共享存儲。
7、對數據庫按照業務不同進行垂直拆分;分庫分表:將一張大表進行水平拆分到不同的數據庫當中;對於數據文件使用分布式存儲。
8、使用消息中間件集群,用作於請求的異步化處理,實現流量的削鋒效果。比如對於數據庫的大量寫請求時可以使用消息中間件。
9、將后端代碼中的阻塞、耗時任務使用異步框架進行處理,比如celery。
復制代碼

 

 

124、怎樣解決數據庫高並發的問題?

復制代碼
1) 緩存式的 Web 應用程序架構:在 Web 層和 DB(數據庫)層之間加一層 cache 層,主要目的:減少數據庫讀取負擔,提高數據讀取速度。cache 存取的媒介是內存,可以考慮采用分布式的 cache 層,這樣更容易破除內存容量的限制,同時增加了靈活性。

2) 增加 Redis 緩存數據庫
3) 增加數據庫索引
4) 頁面靜態化:效率最高、消耗最小的就是純靜態化的 html 頁面,所以我們盡可能使我們的網站上的頁面采用靜態頁面來實現,這個最簡單的方法其實也是最有效的方法。用戶可以直接獲取頁面,不用像 MVC結構走那么多流程,比較適用於頁面信息大量被前台程序調用,但是更新頻率很小的情況。
5) 使用存儲過程:處理一次請求需要多次訪問數據庫的操作,可以把操作整合到儲存過程,這樣只要一次數據庫訪問即可。

6) MySQL 主從讀寫分離:當數據庫的寫壓力增加,cache 層(如 Memcached)只能緩解數據庫的讀取壓力。讀寫集中在一個數據庫上讓數據庫不堪重負。使用主從復制技術(master-slave 模式)來達到讀寫分離,以提高讀寫性能和讀庫的可擴展性。
  讀寫分離就是只在主服務器上寫,只在從服務器上讀,基本原理是讓主數據庫處理事務性查詢,而從數據庫處理 select 查詢,數據庫復制被用於把事務性查詢(增刪改)導致的改變更新同步到集群中的從數據庫。1、主從只負責各自的讀和寫,極大程度緩解 X 鎖和 S 鎖爭用。2、slave 可以配置 MyISAM 引擎,提升查詢性能以及節約系統開銷。
  3、master 直接寫是並發的,slave 通過主庫發送來的 binlog 恢復數據是異步的。4、slave 可以單獨設置一些參數來提升其讀的性能。5、增加冗余,提高可用性。實現主從分離可以使用 MySQL 中間件如:Atlas。

7) 分表分庫,在 cache 層的高速緩存,MySQL 的主從復制,讀寫分離的基礎上,這時 MySQL 主庫的寫壓力開始出現瓶頸,而數據量的持續猛增,由於 MyISAM 使用表鎖,在高並發下會出現嚴重的鎖問題,大量的高並發 MySQL 應用開始使用 InnoDB 引擎代替 MyISAM。
  采用 Master-Slave 復制模式的 MySQL 架構,只能對數據庫的讀進行擴展,而對數據的寫操作還是集中在 Master 上。這時需要對數據庫的吞吐能力進一步地擴展,以滿足高並發訪問與海量數據存儲的需求。對於訪問極為頻繁且數據量巨大的單表來說,首先要做的是減少單表的記錄條數,以便減少數據查詢所需的時間提高數據庫的吞吐,這就是所謂的分表【水平拆分】。
  在分表之前,首先需要選擇適當的分表策略(盡量避免分出來的多表關聯查詢),使得數據能夠較為均衡地分布到多張表中,並且不影響正常的查詢。分表能夠解決單表數據量過大帶來的查詢效率下降的問題,但是卻無法給數據庫的並發處理能力帶來質的提升。面對高並發的讀寫訪問,當數據庫 master 服務器無法承載寫操作壓力時,不管如何擴展 Slave 服務器都是沒有意義的,對數據庫進行拆分,從而提高數據庫寫入能力,即分庫【垂直拆分】

8) 負載均衡集群,將大量的並發請求分擔到多個處理節點。由於單個處理節點的故障不影響整個服務,負載均衡集群同時也實現了高可用性。
復制代碼

 

 

125、實現 Python2 & python3 兼容的方法:

復制代碼
# python2導入同級目錄下的模塊無需 . python3導入同級目錄下的模塊需要加上 .

try:
  from queue import Queue as SameName
except:
  from Queue import Queue as SameName

 
         

from six.moves.queue import Queue

 
復制代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM