前言:
Hello大家好,我是Dream。 今天來總結一下Python和C語言中常見的面試知識,歡迎大家一起前來探討學習~
【一】Python中迭代器的概念?
可迭代對象是迭代器、生成器和裝飾器的基礎。簡單來說,可以使用for來循環遍歷的對象就是可迭代對象。比如常見的list、set和dict。
我們來看一個例子:
from collections import Iterable
print(isinstance('abcddddd', Iterable)) # str是否可迭代print(isinstance([1,2,3,4,5,6], Iterable)) # list是否可迭代print(isinstance(12345678, Iterable)) # 整數是否可迭代-------------結果如下----------------
True
True
False
當對所有的可迭代對象調用 dir() 方法時,會發現他們都實現了 iter 方法。這樣就可以通過 iter(object) 來返回一個迭代器。
x = [1, 2, 3]
y = iter(x)
print(type(x))print(type(y))------------結果如下------------
<class 'list'>
<class 'list_iterator'>
可以看到調用iter()之后,變成了一個list_iterator的對象。可以發現增加了一個__next__方法。所有實現了__iter__和__next__兩個方法的對象,都是迭代器。
迭代器是帶狀態的對象,它會記錄當前迭代所在的位置,以方便下次迭代的時候獲取正確的元素。__iter__返回迭代器自身,__next__返回容器中的下一個值,如果容器中沒有更多元素了,則拋出Stoplteration異常。
x = [1, 2, 3]
y = iter(x)
print(next(y))
print(next(y))
print(next(y))
print(next(y))----------結果如下----------
1
2
3
Traceback (most recent call last):File "/Users/Desktop/test.py", line 6, in <module>print(next(y))
StopIteration
如何判斷對象是否是迭代器,和判斷是否是可迭代對象的方法差不多,只要把 Iterable 換成 Iterator。
Python的for循環本質上就是通過不斷調用next()函數實現的,舉個栗子,下面的代碼先將可迭代對象轉化為Iterator,再去迭代。這樣可以節省對內存,因為迭代器只有在我們調用 next() 才會實際計算下一個值。
x = [1, 2, 3]
for elem in x:...
itertools 庫提供了很多常見迭代器的使用。
>>> from itertools import count # 計數器
>>> counter = count(start=13)
>>> next(counter)
13
>>> next(counter)
14
【二】Python中生成器的相關知識
我們創建列表的時候,受到內存限制,容量肯定是有限的,而且不可能全部給他一次枚舉出來。Python常用的列表生成式有一個致命的缺點就是定義即生成,非常的浪費空間和效率。
如果列表元素可以按照某種算法推算出來,那我們可以在循環的過程中不斷推算出后續的元素,這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
要創建一個generator,最簡單的方法是改造列表生成式:
a = [x * x for x in range(10)]
print(a)
b = (x * x for x in range(10))
print(b)--------結果如下--------------
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x10557da50>
還有一個方法是生成器函數,通過def定義,然后使用yield來支持迭代器協議,比迭代器寫起來更簡單。
def spam():yield"first"yield"second"yield"third"for x in spam():print(x)-------結果如下---------
first
second
third
進行函數調用的時候,返回一個生成器對象。在使用next()調用的時候,遇到yield就返回,記錄此時的函數調用位置,下次調用next()時,從斷點處開始。
我們完全可以像使用迭代器一樣使用 generator ,當然除了定義。定義一個迭代器,需要分別實現 iter() 方法和 next() 方法,但 generator 只需要一個小小的yield。
generator還有 send() 和 close() 方法,都是只能在next()調用之后,生成器處于掛起狀態時才能使用的。
python是支持協程的,也就是微線程,就是通過generator來實現的。配合generator我們可以自定義函數的調用層次關系從而自己來調度線程。
【三】Python中裝飾器的相關知識
裝飾器允許通過將現有函數傳遞給裝飾器,從而向現有函數添加一些額外的功能,該裝飾器將執行現有函數的功能和添加的額外功能。
裝飾器本質上還是一個函數,它可以讓已有的函數不做任何改動的情況下增加功能。
接下來我們使用一些例子來具體說明裝飾器的作用:
如果我們不使用裝飾器,我們通常會這樣來實現在函數執行前插入日志:
def foo():print('i am foo')def foo():print('foo is running')print('i am foo')
雖然這樣寫是滿足了需求,但是改動了原有的代碼,如果有其他的函數也需要插入日志的話,就需要改寫所有的函數,這樣不能復用代碼。
我們可以進行如下改寫:
import loggingdef use_log(func):logging.warning("%s is running" % func.__name__)func()def bar():print('i am bar')use_log(bar) #將函數作為參數傳入-------------運行結果如下--------------
WARNING:root:bar is running
i am bar
這樣寫的確可以復用插入的日志,缺點就是顯式的封裝原來的函數,我們希望能隱式的做這件事。
我們可以用裝飾器來寫:
import loggingdef use_log(func):def wrapper(*args, **kwargs):logging.warning('%s is running' % func.__name__)return func(*args, **kwargs)return wrapperdef bar():print('I am bar')bar = use_log(bar)
bar()------------結果如下------------
WARNING:root:bar is running
I am bar
其中,use_log函數就是裝飾器,它把我們真正想要執行的函數bar()封裝在里面,返回一個封裝了加入代碼的新函數,看起來就像是bar()被裝飾了一樣。
但是這樣寫還是不夠隱式,我們可以通過@語法糖來起到bar = use_log(bar)的作用。
import loggingdef use_log(func):def wrapper(*args, **kwargs):logging.warning('%s is running' % func.__name__)return func(*args, **kwargs)return wrapper@use_log
def bar():print('I am bar')@use_log
def haha():print('I am haha')bar()
haha()------------結果如下------------
WARNING:root:bar is running
I am bar
WARNING:root:haha is running
I am haha
這樣子看起來就非常簡潔,而且代碼很容易復用。可以看成是一種智能的高級封裝。
【四】Python的深拷貝與淺拷貝?
在Python中,用一個變量給另一個變量賦值,其實就是給當前內存中的對象增加一個“標簽”而已。
>>> a = [6, 6, 6, 6]
>>> b = a
>>> print(id(a), id(b), sep = '\n')
66668888
66668888>>> a is b
True(可以看出,其實a和b指向內存中同一個對象。)
淺拷貝是指創建一個新的對象,其內容是原對象中元素的引用(新對象與原對象共享內存中的子對象)。
注:淺拷貝和深拷貝的不同僅僅是對組合對象來說,所謂的組合對象就是包含了其他對象的對象,如列表,類實例等等。而對于數字、字符串以及其他“原子”類型,沒有拷貝一說,產生的都是原對象的引用。
常見的淺拷貝有:切片操作、工廠函數、對象的copy()方法,copy模塊中的copy函數。
>>> a = [6, 8, 9]
>>> b = list(a)
>>> print(id(a), id(b))
4493469248 4493592128 #a和b的地址不同>>> for x, y in zip(a, b):
... print(id(x), id(y))
...
4489786672 4489786672
4489786736 4489786736
4489786768 4489786768
# 但是他們的子對象地址相同
從上面的例子中可以看出,a淺拷貝得到b,a和b指向內存中不同的list對象,但是他們的元素指向相同的int對象,這就是淺拷貝。
深拷貝是指創建一個新的對象,然后遞歸的拷貝原對象所包含的子對象。深拷貝出來的對象與原對象沒有任何關聯。
深拷貝只有一種方式:copy模塊中的deepcopy函數。
我們接下來用一個包含可變對象的列表來確切地展示淺拷貝和深拷貝的區別:
>>> a = [[6, 6], [8, 8], [9, 9]]
>>> b = copy.copy(a) # 淺拷貝
>>> c = copy.deepcopy(a) # 深拷貝
>>> print(id(a), id(b)) # a和b地址不同
4493780304 4494523680
>>> for x, y in zip(a, b): # a和b的子對象地址相同
... print(id(x), id(y))
...
4493592128 4493592128
4494528592 4494528592
4493779024 4493779024
>>> print(id(a), id(c)) # a和c不同
4493780304 4493469248
>>> for x, y in zip(a, c): # a和c的子對象地址也不同
... print(id(x), id(y))
...
4493592128 4493687696
4494528592 4493686336
4493779024 4493684896
【五】Python是解釋語言還是編譯語言?
Python是解釋語言。
解釋語言的優點是可移植性好,缺點是運行需要解釋環境,運行起來比編譯語言要慢,占用的資源也要多一些,代碼效率低。
編譯語言的優點是運行速度快,代碼效率高,編譯后程序不可以修改,保密性好。缺點是代碼需要經過編譯才能運行,可移植性較差,只能在兼容的操作系統上運行。
【六】Python的垃圾回收機制
在Python中,使用引用計數進行垃圾回收;同時通過標記-清除算法解決容器對象可能產生的循環引用問題;最后通過分代回收算法提高垃圾回收效率。
【七】Python里有多線程嗎?
Python里的多線程是假的多線程。
Python解釋器由于設計時有GIL全局鎖,導致了多線程無法利用多核,只有一個線程在解釋器中運行。
對于I/O密集型任務,Python的多線程能起到作用,但對于CPU密集型任務,Python的多線程幾乎占不到任何優勢,還有可能因為爭奪資源而變慢。
對所有面向I/O的(會調用內建的操作系統C代碼的)程序來說,GIL會在這個I/O調用之前被釋放,以允許其它的線程在這個線程等待I/O的時候運行。
如果是純計算的程序,沒有 I/O 操作,解釋器會每隔 100 次操作就釋放這把鎖,讓別的線程有機會執行(這個次數可以通過 sys.setcheckinterval 來調整)如果某線程并未使用很多I/O 操作,它會在自己的時間片內一直占用處理器和GIL。
緩解GIL鎖的方法:多進程和協程(協程也只是單CPU,但是能減小切換代價提升性能)
【八】Python中range和xrange的區別?
首先,xrange函數和range函數的用法完全相同,不同的地方是xrange函數生成的不是一個list對象,而是一個生成器。
要生成很大的數字序列時,使用xrange會比range的性能優很多,因為其不需要一上來就開辟很大的內存空間。
Python 2.7.15 | packaged by conda-forge | (default, Jul 2 2019, 00:42:22)
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> xrange(10)
xrange(10)
>>> list(xrange(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
xrange函數和range函數一般都用在循環的時候。具體例子如下所示:
>>> for i in range(0,7):
... print(i)
...
0
1
2
3
4
5
6>>> for i in xrange(0,7):
... print(i)
...
0
1
2
3
4
5
6
在Python3中,xrange函數被移除了,只保留了range函數的實現,但是此時range函數的功能結合了xrange和range。并且range函數的類型也發生了變化,在Python2中是list類型,但是在Python3中是range序列的對象。
【九】Python中列表和元組的區別?
-
列表是可變的,在創建之后可以對其進行任意的修改。
-
元組是不可變的,元組一旦創建,便不能對其進行更改,可以元組當作一個只讀版本的列表。
-
元組無法復制。
-
Python將低開銷的較大的塊分配給元組,因為它們是不可變的。對于列表則分配小內存塊。與列表相比,元組的內存更小。當你擁有大量元素時,元組比列表快。
【十】Python中dict(字典)的底層結構?
Python的dict(字典)為了支持快速查找使用了哈希表作為底層結構,哈希表平均查找時間復雜度為O(1)。CPython 解釋器使用二次探查解決哈希沖突問題。
【十一】常用的深度學習框架有哪些,都是哪家公司開發的?
-
PyTorch:Facebook
-
TensorFlow:Google
-
Keras:Google
-
MxNet:Dmlc社區
-
Caffe:UC Berkeley
-
PaddlePaddle:百度
【十二】PyTorch動態圖和TensorFlow靜態圖的區別?
PyTorch動態圖:計算圖的運算與搭建同時進行;其較靈活,易調節。
TensorFlow靜態圖:計算圖先搭建圖,后運算;其較高效,不靈活。
文末推薦
內容介紹:
《機器學習平臺架構實戰》詳細闡述了與機器學習平臺架構相關的基本解決方案,主要包括機器學習和機器學習解決方案架構,機器學習的業務用例,機器學習算法,機器學習的數據管理,開源機器學習庫,Kubernetes容器編排基礎設施管理,開源機器學習平臺,使用AWS機器學習服務構建數據科學環境,使用AWS機器學習服務構建企業機器學習架構,高級機器學習工程,機器學習治理、偏差、可解釋性和隱私,使用人工智能服務和機器學習平臺構建機器學習解決方案等內容。此外,本書還提供了相應的示例、代碼,以幫助讀者進一步理解相關方案的實現過程。
當當: https://product.dangdang.com/29625469.html
京東: https://item.jd.com/13855627.html