一片文章概括大部分python面试基础常考题(部分有详解)
本片文章部分參考地址:https://segmentfault.com/a/1190000018737045
python是動態解釋性的強類型定義語言
強類型:不允許不同類型相加。例如:整形+字符串會報類型錯誤。
動態:不使用顯示數據類型聲明,且確定一個變量的類型是在第一次給它賦值的時候。
腳本語言:一般是解釋性語言,運行代碼只需要一個解釋器,不需要編輯。
Python
面向對象OOP
魔法方法:
__init__用來做變量初始化 或 賦值 操作,在類實例化對象的時候,會被自動調用
__new__返回父類的new出來的實例self,或直接是object的new出來的實例self–>比init先調用生成實例供init用
__str__return 一個數據,并且只有self一個參數,當在類的外部 print(對象) 則打印這個數據
__del__析構函數,當刪除對象時,python解釋器也會默認調用
python面向對象特性:
封裝: 封裝就是把類中的屬性和方法定義為私有的,python會自動將其轉換為_類名__屬性名(方法名)的格式,子類無法對進行修改
繼承: 關于__mro__方法解析順序,新式類是廣度優先查找,經典類是深度優先,
多態: python弱化了多態概念:不管對象類型,只要對象有該方法就可執行。關于面向對象的多態理解,建議參考鴨子類型。
? ------ 鴨子類型: Python在使用傳入參數的過程中不會默認判斷參數類型,只要參數具備執行條件就可以執行
新式類和經典類:
2.2出現新式類,3全是新式類,新式類是廣度優先查找,經典類是深度優先;
? 重點: 新式類的廣度優先解決了經典類的mro查找忽略同一級別的屬性的bug
使用dir()方法也可以看出新式類中定義很多新的屬性和方法
(new,call,dict模塊\屬性\方法\doc,class對象屬于的類,...等),
而經典類好像就2個(doc文檔,module屬于哪個模塊):
靜態方法和類方法和實例方法
| a = A() | a.foo(x) | a.class_foo(x) | a.static_foo(x) |
| A | 不可用 | A.class_foo(x) | A.static_foo(x) |
Python自省(特性)
自省就是面向對象的語言通過一定的機制查詢到對象的內部結構
比如dir(對象), 對象.__dict__
Python元類 type(future_class_name, future_class_parents元祖, uppercase_attr字典)#返回一個類
創建類的類,元類要繼承type,創建一個類的時候,他還沒有在內存中生成,直到被調用:
python做了如下操作:依次在—>當前類中–>父類中–>模塊層次中–>查看有沒有__metacalss__屬性,都沒有就會調用內置元類type創建這個類對象。
ORM(對象關系映射)核心:數據描述符和元類
優缺點:
1)只需要面向對象編程, 不需要面向數據庫編寫代碼,對數據庫的操作都轉化成對類屬性和方法的操作.不用編寫各種數據庫的sql語句.
2)實現了數據模型與數據庫的解耦, 屏蔽了不同數據庫操作上的差異.不在關注用的是mysql,oracle…等.通過簡單的配置就可以輕松更換數據庫, 而不需要修改代碼.
3)在映射過程中有性能缺失,面向對象編程到sql語句之間的映射需要過程時間,造成性能缺失
定義一個ORM:
class Field:passclass CharField(Field):'''數據描述符, 好處在于可以在各方法中校驗傳入值的合理性'''def __init__(self, col_name, max_length):if col_name is None or not isinstance(col_name, str):raise ValueError("col_name must be given as str")if max_length is None or not isinstance(max_length, numbers.Integral):raise ValueError("max_length must be given as int")self._col_name = col_nameself._max_length = max_lengthdef __get__(self, instance, owner):# return getattr(instance, self._col_name)return instance.fields[self._col_name]def __set__(self, instance, value):# 這里如果col_name和數據描述符對應的名字一樣的話,如name=CharField(col_name="name",10)# 用setattr(instance, self._col_name, value)即user.name=value會再次進入此__set__方法,導致無限遞歸instance.fields[self._col_name] = valueclass ModelMetaClass(type):'''元類'''def __new__(cls, cls_name, base_class, attrs):if cls_name == "Model":return super().__new__(cls, cls_name, base_class, attrs)fields = {}for k, v in attrs.items():if isinstance(v, Field):fields[k] = vattrs["fields"] = fields_meta = {}attrs_meta = attrs.get("Meta", None)if attrs_meta is not None and isinstance(attrs_meta, type):_meta["tb_name"] = getattr(attrs_meta, "tb_name", cls_name)del attrs["Meta"]else:_meta["tb_name"] = cls_name.lower()attrs["_meta"] = _metareturn super().__new__(cls, cls_name, base_class, attrs)class Model(metaclass=ModelMetaClass): # 指定要使用的元類def __init__(self, **kwargs):self.fields = {}for k, v in kwargs.items():setattr(self, k, v)# def more_func(self):# passclass User(Model):name = CharField(col_name="name", max_length=10)sex = CharField(col_name="sex", max_length=1)age = IntField(col_name="age", min_length=1, max_length=10)class Meta:tb_name = "User"設計模式
單例模式:
該模式的主要目的是確保某一個類只有一個實例存在。當你希望在整個系統中,某個類只能出現一個實例時,單例對象就能派上用場。(比如系統回收站)
# __new__方法實現 class Singleton(object):def __new__(cls,*args,**kw)if not hasattr(cls,'_instance')cls._instance = super(Singleton,cls).__new__(cls,*args,**kw)return cls._instance # 創建一個類的實例或者繼承這個類的就是單例類 # 裝飾器方法 def singleton(cls,*args,**kwargs):instances={}def getinstance():if cls not in instances:instances[cls]=cls(*args,**kwargs)return instances[cls]return getinstance @singleton class MyClass(object):pass# 還有import 一個類的實例,就是天然的單例工廠模式
核心的思想是: 通過傳遞給類或函數某種產品的信息來創建產品并返回。當我們想得到產品a對象,只需把產品a的名字傳遞給工廠函數就能得到產品a對象。
import abcclass A(object):def __init__(self, product_type):self.product_type = product_typedef __str__(self):return 'product %s' % self.product_typeclass B(object):def __init__(self, product_type):self.product_type = product_typedef __str__(self):return 'product %s' % self.product_typeclass Abstract_factory(object):__metaclass__ = abc.ABCMeta@abc.abstractmethoddef yield_product(self):passclass Factory_a(Abstract_factory):def yield_product(self):return A('A')class Factory_b(Abstract_factory):def yield_product(self):return B('B')def factory(product_type):if product_type == 'A':return Factory_a()if product_type == 'B':return Factory_b()if __name__ == '__main__':factory_a = factory('A')a = factory_a.yield_product()factory_b = factory('B')b = factory_b.yield_product()print(a)print(b)Python基礎
Py2和Py3區別
| print是一個打印語句 | 函數 可以多個位置的參數 | |
| input | raw_input得到str | input得到str |
| 整除 | 3/2 -->1 但3/2.0–>1.5 | 3//2 得到1 |
| range | range(4)–>[0,1,2,3]列表 | range(4) 得到可迭代對象(type是對象) |
| xrange | xrange(4)–>生成器 | 只有range |
| unicode | 默認ASC II ,unicode() 是單獨的 | 源碼文件默認使用utf-8編碼 |
| nonlocal關鍵字 | 函數內變量就是局部變量 | 可聲明為非局部變量,可以在外使用 |
| True.False | 視為全局變量,隨意賦值 | 變成關鍵字,指向固定對象,不能重新賦值。 |
Python中的list
是分離式的元素外置順序表,表頭區,元素區存id地址,數據區存數據可以不連續的內存
dict底層結構
-
為了支持快速查找使用了哈希表作為底層結構
-
哈希表平均查找時間復雜度為o(1)
-
CPython解釋器使用二次探查解決哈希沖突問題
字符串處理方法
mystr.find(str, start=0, end=len(mystr)) # 有返回下標索引==無返回-1mystr.strip() # 刪除兩邊空白字符mystr.splitlines() # 按行分隔成列表mystr.split(",", 1) # 默認全部分割,1代表分割第一個a.isalnum # 判斷全是字母數字"_".join(list) #每個元素后面加一個字符串_變成str賦值、淺拷貝和深拷貝
-
直接賦值:其實就是對象的引用(別名)。=
-
淺拷貝(copy):拷貝父對象,不會拷貝對象的內部的子對象。切片,工廠函數list(), copy.copy()
-
深拷貝(deepcopy): copy 模塊的 deepcopy 方法,完全拷貝了父對象及其子對象。
?
python函數式編程 map,filter,reduce
類裝飾器:要實現init和call方法
閉包裝飾器:外函數返回內函數的引用,內函數使用外函數的局部變量并調用被裝飾函數
垃圾回收機制
-
(主)引用計數(reference counting): 引用計數為0時,該對象生命就結束了。維護引用計數消耗資源,循環引用L.append(L) L一直不回收
-
(輔)標記清除機制(mark and sweep): 不改變真實引用計數,復制一份,如果A引用了B,將B計數-1,B也引用了A,將A也-1,這樣就顯示出了真是的引用計數
-
(輔)分代計數: 將系統中的所有內存塊根據其存活時間劃分為不同的集合,每一個集合就成為一個“代”,垃圾收集的頻率隨著“代”的存活時間的增大而減小。也就是說,活得越長的對象,就越不可能是垃圾,就應該減少對它的垃圾收集頻率。那么如何來衡量這個存活時間:通常是利用幾次垃圾收集動作來衡量,如果一個對象經過的垃圾收集次數越多,可以得出:該對象存活時間就越長。
面試回答: Python的GC模塊主要運用了“引用計數”來跟蹤和回收垃圾。在引用計數的基礎上,還可以通過“標記-清除”解決容器對象可能產生的循環引用的問題。通過“分代回收”以空間換取時間來進一步提高垃圾回收的效率。
sys模塊幾個常用方法
- argv 命令行參數list,第一個是程序本身的路徑
- path 返回模塊的搜索路徑
- modules.keys() 返回已經導入的所有模塊的列表
- exit(0) 退出程序
OS模塊幾個常用方法
os.system(command) #函數用來運行shell命令
os.path模塊:
abspath(path) #返回絕對路徑
basename§ #返回路徑的最后一個/后面的內容
dirname(file) #返回當前文件/的目錄名 __file__也可以目錄
exits(path) #測試一個目錄存在與否
isabs(s) #判斷一個路徑是否是絕對路徑
isdir(s) #判斷是不是目錄
isfile(path) #判斷是不是文件
join(‘路徑’,‘路徑/文件’) #os.path.join(os.path.dirname(BASE_DIR), “logs/meiduo.log”)
globals/locals(可以變相操作代碼)
- globals中保存了當前模塊中所有的變量屬性與值
- locals中保存了當前環境中的所有變量屬性與值
實現從1-100每三個為一組分組
print([[x for x in range(1,101)][i:i+3] for i in range(0,100,3)])單元測試
import unittest class MyTest(unittest.TestCase): # 繼承自TestCase 單獨運行文件# 測試開始的方法def setUp(self):print("setup")#測試方法 必須test_開頭 可以多個 光標放在哪個函數的內部,就執行哪個測試方法def test_login(self):print("test_login")# 測試結束的方法def tearDown(self):print("teardown")GIL全局解釋器鎖
-
同一時間只能有一個線程執行,競爭的是cpu內存資源,CPython的特點,其他解釋器不存在
-
cpu密集型:多進程+進程池
-
io密集型:多線程/協程
- **釋放:**GIL會根據執行的字節碼行數以及時間片釋放GIL,GIL在遇到io的操作時候主動釋放
tip:什么是Cpython==將python解釋成C代碼工具
進程,線程,協程,鎖
- 進程:系統資源分配的最小單位, 一個運行的程序(代碼)就是一個進程,進程擁有自己獨立的內存空間,所以進程間數據不共享,開銷大
- **進程做并發處理:**Linux系統函數fork()可以在父進程中創建一個子進程,這樣的話,在一個進程接到來自客戶端新的請求時就可以復制出一個子進程讓其來處理,父進程只需負責監控請求的到來,然后創建子進程讓其去處理,這樣就能做到并發處理。
- 線程:調度執行的最小單位, 依賴進程存在. 一個進程至少有一個線程,叫主線程,而多個線程共享內存(數據共享,共享全局變量),從而極大地提高了程序的運行效率.
- 協程:用戶層面的輕量級的線程,由程序員根據需要自己調度。我們把一個線程中的一個個函數叫做子程序,那么子程序在執行過程中可以中斷去執行別的子程序;別的子程序也可以中斷回來繼續執行之前的子程序,這就是協程。
- 協程包gevent有個monkey.patch_all()補丁,讓gevent框架識別耗時操作,比如:time.sleep,網絡請求延時
鎖
-
線程互斥鎖:當多個線程同時修改同一條數據時可能會出現臟數據,產生了互斥鎖保證同一時刻只有一個線程訪問數據 threading.Lock()
-
線程安全:因為多線程是共享系統資源的,有了互斥鎖,每個線程對數據進程操作都能得到正確的結果
?
避免死鎖
死鎖:2個線程互相等待對方的鎖,互相占用著資源不釋放, 某一個線程沒有正常釋放鎖
利用上下文管理器with 自動申請并釋放鎖
with在執行時需要這兩個方法:__enter__與__exit__ 比如open就有
進程與線程的使用場景
- 多進程適合在CPU密集型操作(CPU指令比較多, 如位數多的浮點運算).
- 多線程適合在IO密集型操作(讀寫數據操作較多的, 比如爬蟲)
**進程間通訊 **multiprocessing中的Manager類和Pipe類和Queue類,進程池用Manager().Queue(10)
-
Manager().dict()
progress_dict = Manager().dict() # 傳給子進程任務函數 -
Pipe(適用于兩個進程) 管道
recevie_pipe, send_pipe = Pipe() # 傳給接受和發送的任務函數 -
Queue(10) 消息隊列
queue = Queue(10) # 傳給子進程 queue.get(),queue.put()
unix進程間通信方式(IPC)
-
管道pipe:兩個親戚進程
-
命名管道named pipe:允許不是親戚的進程間通信
-
信號signal:信號是比較復雜的通信方式,用于通知接受進程有某種事件發生
-
消息隊列message: 息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩沖區大小受限等缺
-
共享內存、使得多個進程可以訪問同一塊內存空間,是最快的可用IPC形
-
內存映射:內存映射允許任何多個進程間通信,每個進程通過把一個共享的文件映射到自己的進程地址空間來實現它
-
信號量: 主要作為進程間以及同一進程不同線程之間的同步手段。
-
套接口: 更為一般的進程間通信機制,可用于不同機器之間的進程間通信。
生成器和迭代器
- 實現__next__和__iter__方法的對象就是迭代器
- 可迭代對象只需要實現__iter__方法
- 使用生成器表達式或者yield的生成器函數(生成器是一種特殊的迭代器)
- 節省資源,在用戶需要訪問數據的時候 才延遲產生,
- yield會記錄代碼停止的地方,從哪里跌倒從哪里爬起來,send(None給yield傳參), next(gen)
編程題
對字典列表按年齡進行倒序排序d_list = [{'name':'a','age':18},....]
d_list.sort(key=lambda x:x['age'],reverse = True)如何將一個可迭代對象的每個元素變成一個字典的所有鍵?
{}.fromkeys(['jim','han'],21) # output:{'jim': 21, 'han': 21}一只青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
fib = lambda n: n if n <=2 else fib(n-1) + fib(n-2)實現刪除一個 L1=[1,2,3,3,2]里面的重復元素
L2 = list(set(L1)) L2.sort(key = L1.index) # 使用列表生成時 append L2 = [] [L2.append(i) for i in L1 if i not in L2]合并兩個有序列表
def loop_merge_sort(l1, l2):tmp = []while len(l1) > 0 and len(l2) > 0:if l1[0] > l2[0]:tmp.append(l2[0])del l2[0] # 列表刪除時間復雜度大else:tmp.append(l1[0])del l1[0]tmp.extend(l1)tmp.extend(l2)return tmp # 不刪除元素,時間復雜度低 def text(listone,listtwo):join_list=[]i,j=0,0while True:if len(listone) <=i:join_list.extend(listtwo[j:])breakelif len(listtwo)<=j:join_list.extend(listone[i:])breakif listone[i]>= listtwo[j]:join_list.append(listtwo[j])j+=1else:join_list.append(listone[i])i+=1return join_listlinux基礎
多路復用io
其實所有的I/O都是輪詢的方法,只不過實現的層面不同罷了.
python的select模塊,提供了:select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路復用。
-
select
- 連接數受限
- 查找配對速度慢
- 數據由內核拷貝到用戶態
-
poll
- 比select提高鏈接數的并不多,改善第一個缺點
-
epoll
-
適用于連接數量較多,但活動鏈接數少的情況 改善select三個缺點
?
說一些你比較常用linux指令
-
ls/ll、cd、cat、tree、touch、mkdir、rm-rf、cp、mv、ps -aux| grep xxx、kill -9、top、tar -xvf file.tar
權限 ls -l
chmod u=rw, g=x, o=r test.py
搜索文本grep grep -n ‘^a’ file.txt
查找文件:find find ./ -name ‘*.sh’
遠程登錄ssh 用戶名@IP
SSH(openssh-server) 是專為遠程登錄會話和其他網絡服務提供安全性的協議。常用于遠程登錄,以及用戶之間進行資料拷貝
遠程拷貝 (前提是已經安裝了openssh)如果是文件夾+-r
scp 本地文件名 遠程用戶名@遠程ip:文件 本地上傳到遠程
scp 遠程用戶名@遠程ip:遠程文件 本地文件名 復制遠程到本地
-
FileZilla 軟件
可以通過圖形化操作的方式進行遠程主機的文件上傳和下載.
vim(vi)編輯器
有命令模式、輸入模式、末行模式三種模式。
命令模式:查找內容(/abc、跳轉到指定行(20gg)、跳轉到尾行(G)、跳轉到首行(gg)、刪除行(dd)、插入行(o)、復制粘貼(yy,p)
輸入模式:編輯文件內容
末行模式:保存退出(wq)、強制退出(q!)、顯示文件行號(set number)
在命令模式下,輸入a或i即可切換到輸入模式,輸入冒號(:)即可切換到末行模式;在輸入模式和末行模式下,按esc鍵切換到命令模式
排序算法
快速排序:不穩定,最優O(nlogn),差O(n*n)
取第一個值為中間值,其他的與之比較,放左邊和右邊,再對左右兩邊分別排序
def quick_sort(arr):if len(arr) < 2:return arrmid = arr[0]less = [i for i in arr[1:] if i <= mid]more = [i for i in arr[1:] if i > mid]return quick_sort(less) + [mid] + quick_sort(more)歸并排序:穩定,最優O(nlogn),最差O(nlogn) 產生了多一份的空間
先拆分成最小的單元,然后兩兩判斷合并
def merge(left, right): L,R = 0, 0 # 先考慮左邊一個和右邊一個進行比較 添加到新列表中slist = []while L < len(left) and R < len(right):if left[L] <= right[R]:slist.append(left[L])L += 1else:slist.append(right[R])R += 1slist += left[L:]slist += right[R:]return slistdef merge_sort(arr):if len(arr) < 2:return arrelse:mid_i = len(arr) // 2 # 分而治之left = merge_sort(arr[:mid_i])right = merge_sort(arr[mid:])return merge(left, right)插入排序: 穩定,最優O(n),差O(n*n)
認定一個值是有序的,后面的依次浮動比較前一個值,小了就交換,目的是把后面的插入到前面的有序序列
def insert_sort(arr):for i in range(1,len(arr)):j = i # 1,2,3,4 ...n-1while j > 0:if arr[j] < arr[j-1]:arr[j], arr[j-1] = arr[j-1], arr[j]j -= 1else:break # 希爾排序 gap = len(arr) // 2 while gap >0 :...插入排序里面的1換成gapgap //= 2冒泡排序: 穩定, 最優O(n),差O(n*n)
臨近的兩個值比較,大的放后面,第一遍的時候最后一個正確位置,第二遍時后兩個都正確。。。。
def bubule_sort(arr):for i in range(len(arr)-1,0,-1) # n-1, n-2,... 2, 1flag = Falsefor j in range(i):if arr[j+1] < arr[j]:arr[j], arr[j+1] = arr[j+1], arr[j]flag = Trueif not flag:break選擇排序: 不穩定,最優最壞都是O(n*n)
假設最后一個最大,從左到右(除了最后一個)依次與最大值進行比較并交換
def selection_sort(arr):for i in range(len(arr)-1, 0, -1): # n-1, n-2,... 2, 1for j in range(i):if arr[j] >arr[i]:array[j], array[i] = array[i], array[j]return array堆排序[heapq模塊] 不穩定,時間復雜度與歸并一樣 nlogn,nlogn
堆是一顆完全二叉樹:除了最后一層之外的其他每一層都被完全填充,并且所有結點都保持向左對齊的樹
最大堆:任意節點的值不大于其父親節點的值。
最小堆:任意節點的值不小于其父親節點的值。
堆有什么用途?
堆最常用于優先隊列以及相關動態問題。
優先隊列指的是元素入隊和出隊的順序與時間無關,既不是先進先出,也不是先進后出,而是根據元素的重要性來決定的
二分查找: 最優O(1),差O(logn)
二分查找必須是有序的序列,
def binary_find(arr, item):'''非遞歸''' # 定義頭和尾的游標,依次進行判斷進行移動游標first,end = 0, len(arr)-1while first <= end:mid = (first + end) //2if item < arr[mid]:end = midelif item > arr[mid]:first = mid + 1else:return midreturn False print(binary_find([1,2,3], 1))def binary_search(alist, item):'''遞歸實現'''if len(alist) == 0:return Falseelse:mid = len(alist)//2if alist[mid]==item:return Trueelse:if item < alist[mid]:return binary_search(alist[:midpoint],item)else:return binary_search(alist[midpoint+1:],item)棧與隊列
class Stack(object):"""棧"""def __init__(self):self.items = []def is_empty(self):"""判斷是否為空"""return self.items == []def push(self, item):"""加入元素"""self.items.append(item)def pop(self):"""彈出元素"""return self.items.pop()def peek(self):"""返回棧頂元素"""return self.items[len(self.items)-1]def size(self):"""返回棧的大小"""return len(self.items) class Queue(object):"""隊列"""def __init__(self):self.items = []def is_empty(self):return self.items == []def enqueue(self, item):"""進隊列"""self.items.insert(0,item)def dequeue(self):"""出隊列"""return self.items.pop()def size(self):"""返回大小"""return len(self.items)用兩個棧實現一個隊列(面試遇到的題)
class Queue(object):def __init__(self):self.stack1 = []self.stack2 = []def append_tail(self,item):self.stack1.append(item)def del_head(self):if not self.stack2:if self.stack1:while self.stack1:item = self.stack1.pop()self.stack2.append(item)else:raise Exception('stack1 is empty')item = self.stack2.pop()return item q = Queue() q.append_tail(1) q.append_tail(2) q.append_tail(3) print(q.del_head()) # 輸出先進入隊列的1,總結
以上是生活随笔為你收集整理的一片文章概括大部分python面试基础常考题(部分有详解)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java遍历删除原理,Java 垃圾回收
- 下一篇: 获取iOS任意线程调用堆栈(四)符号化实