Python語言高頻重點彙總

地質學家dm發表於2020-02-11

Python語言高頻重點彙總

GitHub面試寶典倉庫——點這裡跳轉

1. 函式-傳參

回到頂部
在python中,給一個函式傳遞引數其實是把實參這個變數對應的地址複製了一份,然後把複製的這個地址傳遞給函式中區域性變數形參,此時實參和對應的形參都指向記憶體中這一個實際的物件。
第一個例子:

a = 500

def function1(a):
  print("函式中區域性變數a的地址:", id(a))
  a = 100  # 更改之後
  print("指向100後的區域性a 和 100的地址:", id(a), id(100))

print("全域性a和500的地址:", id(a), id(500))
function1(a)
print("函式呼叫完之後全域性a的地址:", id(a))

# 輸出結果:
# 全域性a和500的地址: 2272322100976 2272322100976
# 函式中區域性變數a的地址: 2272322100976
# 指向100後的區域性a 和 100的地址: 140730892516720 140730892516720
# 函式呼叫完之後全域性a的地址: 2272322100976

我們可以看到,全域性a指向500這個實體的地址,我們在呼叫function1函式的時候,把全域性a的地址複製了一份,給了區域性a,雖然全域性a指向500,區域性a也指向500,但是全域性a和區域性a是兩個變數,即便名字相同,因為都指向500,所以可以看到此刻的全域性a還有區域性a還有500的記憶體地址都是同一塊記憶體;接著在函式中區域性a不再指向500了,區域性a重新指向了100所在的記憶體地址了,這時候可以看到區域性a和100指向的是同一塊記憶體,因為整個函式呼叫的過程中,全域性a一直指向500這個記憶體地址,在函式呼叫結束之後,區域性a消亡,全域性a依舊指向500,記憶體地址從來沒有變化過。

第二個例子:

lst = []


def function2(lis: list):
  print("lis的地址:", id(lis))
  lis.append(200)
  print("append200之後lis的地址:", id(lis))

print("lst的地址", id(lst))
function2(lst)
print("函式呼叫之後的lst:", lst)

# 輸出結果:
# lst的地址 2110929195592
# lis的地址: 2110929195592
# append200之後lis的地址: 2110929195592
# 函式呼叫之後的lst: [200]

這個例子是同樣的道理,lst指向[]所在的記憶體地址,接著呼叫函式,lst所指向的記憶體地址複製一份給了lis,然後lis在函式中通過接收到的記憶體地址把[]增加了一個元素200,然後呼叫結束lis區域性變數消亡,所以lst還是指向[]這個物件所在的地址,因為在函式中,lis把這個列表修改了,所以lst通過地址找到這個列表是被修改過的。
我們可以把所有變數理解為記憶體中一個物件的引用,或者可以看作是C++ 中的指標型別。每一個變數記住的都是物件的地址,而物件又可以分為可變的mutable不可變的immutable,在python中,string、tuple、數值是不可變的,list、set、dict是可修改的物件,可以通過多個變數都記住它的地址,然後通過不同變數去修改這些可修改的物件。
這是stack overflow上的解答:連線地址

2. 元類

回到頂部
元類又叫metaclass,在python中我們使用type(59)就可以知道59是int型別,但是你考慮過int類的型別嗎?這就是元類問題,python中一切皆物件,就像linux中的一切皆檔案哲學那麼徹底,所以類也是物件,既然是物件就有型別,所有新型別的預設都是type型別,可以修改,在python中,當我們建立一個物件的時候,它會進行型別檢查,如果我們沒指定型別預設就是type類了。可以參考下圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-cjAVQJAT-1574565572061)(https://github.com/duanmingpy/python-interview/blob/master/images/yuanlei.png)]

細節可以參考stack overflow的解答:連線地址

3. @staticmethod和@classmethod兩個裝飾器

回到頂部
在python類方法中有三中型別的方法,第一種是最普通的方法,在例項進行呼叫的時候會主動繫結呼叫的例項作為第一個引數,第二個是通過@classmethod裝飾的方法,無論是類還是例項進行呼叫,都會自動繫結當前類作為第一個引數,第三個是通過@staticmethod裝飾的方法,這種方法呼叫就像普通函式一樣,不會進行傳引數的自動繫結。
其實這三種方法的本質都是用類寫一個裝飾器,然後通過描述器的方法對類方法的引數進行約束,使用描述器實現的還有@property,其中前面的都是非資料描述器non-data descriper, @property則是資料描述器的實現data descriper。記得在剛學習python描述器的時候我還自己手寫了@staticmethod、@classmethod還有@property這三個裝飾器。
例子:

class Student:
  def __init__(self, name, age):
    self.name = name
    self.age = age

  @classmethod
  def play(cls):
    print(cls.__name__)

  @staticmethod
  def eating(student, food):
    print(f"{student} eat {food}")

tom = Student("tom", 17)
tom.play()
Student.eating("tom", "apples")

# 輸出:
# Student
# tom eat apples

如果實在回憶不上來了(不可能的),可以參考real python 和stack overflow的解讀:
real python:Instance, Class, and Static Methods — An Overview
stack overflow:What is the difference between @staticmethod and @classmethod?

4. 類屬性和例項屬性

回到頂部
類屬性:通俗的講是掛在類上的屬性,所以只要是這個類的例項就可以訪問,比如人類有馬克思哲學這個屬性,所有人都可以去學習使用馬克思哲學,所有人都可以訪問;從專業角度講類屬性就是放在類的__dict__這個字典中的屬性。
例項屬性:同理,例項屬性就是每個例項私有的,比如每個人的房子,這就是私有的,別人是不能夠擁有的,從專業角度講例項屬性就是放在例項的字典__dict__中的。
例子:

class Server:  # Server類
  protocol = "TCP/IP"  # TCP/IP協議是所有伺服器共有的資源
  
  def __init__(self, ip, port):
    self._ip = ip  # ip和埠則是每臺伺服器自己的
    self._port = port  # 即使埠相同也不是一臺計算機的埠

這裡的協議protocol就是類屬性,IP+port就是例項屬性。

5. Python的自省

回到頂部
什麼是自省?
在日常生活中,自省(introspection)是一種自我檢查行為。
在計算機程式設計中,自省是指這種能力:檢查某些事物以確定它是什麼、它知道什麼以及它能做什麼。自省向程式設計師提供了極大的靈活性和控制力。
說的更簡單直白一點:自省就是物件導向的語言所寫的程式在執行時,能夠知道物件的型別。簡單一句就是,執行時能夠獲知物件的型別。
例如python, buby, object-C,C++ 都有自省的能力,這裡面的c++的自省的能力最弱,只能夠知道是什麼型別,而像python可以知道是什麼型別,還有什麼屬性。
python中的自省方法:
type()
dir()
getattr()
hasattr()
isinstance()
也是外掛化開發技術的依賴之一。

class Server:  # Server類
  protocol = "TCP/IP"
  
  def __init__(self, ip, port):
    self._ip = ip 
    self._port = port 
    

print(hasattr(Server, "protocol"))  # True

6. 列表、集合、字典推導式

回到頂部
推導式是python開發過程中非常常用的技術,簡單但是絕對是好用的,在2.7版本之前並沒有字典推導式,由於太好用了,社群一直建議增加,在2.7之後增加了字典推導式。
例子:

# 100以內所有的奇數 —— 列表解析式
a = [i for i in range(100) if i & 1]

# 100以內3的倍數 —— 集合解析式
b = {i for i in range(100) if i % 3 == 0}

import random
import string

# 生成100個name和對應的id —— 字典解析式
name_id = {"".join([random.choice(string.ascii_letters) for i in range(4)]):random.randint(1000, 9999) for j in range(100)}

7. Python中單下劃線和雙下劃線

回到頂部
如:伺服器的addr是需要大家知道的,不隱藏,_socket是伺服器的監聽socket不必讓別人知道,可以隱藏一下,start是伺服器的啟動方法,需要讓別人知道,而用來接收連線的__accept則不需要別人知道。

import socket
import threading

class Server:
  def __init__(self, ip, port):  # 魔術方法
    self.addr = ip, port   # 伺服器的地址和埠
    self._socket = socket.socket()  # 伺服器的監聽socket
    
  def start(self):  # 啟動伺服器的介面
    self._socket.bind(self.addr)
    self._socket.listen()
    threading.Thread(target=self.__accept, name="接收連線").start()
    
  def __accept(self):  # 監聽socket用來接收連線的方法
    new_socket, raddr = self._socket.accept()
    pass
    
print(Server.__dict__)

在python的類中,單下劃線被約定為隱藏變數,分為兩種,一種是開頭短下劃線_socket,另一種是開頭長下劃線__accept;其中短下劃線的識別符號在類字典中是不更改名稱的,而長下滑線的在類屬性字典中更改了名稱,如:_Server__accept,但是由於python的黑魔法太過容易破解,如果我們真的想訪問對應的屬性,只需要把類字典拿出來看一下名稱就可以呼叫了。
總之,防君子不防小人把!
雙下滑下,即兩端都有下劃線的是一些特殊的魔術方法,以及特殊方法,比如類或例項的字典使用__dict__訪問,還有上下文使用__enter____exit__來控制。

回憶不上來的時候可以查閱知乎和stack overflow:
stack overflow:What is the meaning of a single and a double underscore before an object name?
知乎:Python的類的下劃線命名有什麼不同?
閒扯:為什麼知乎選擇用python寫?我覺得可能是開發效率高把,現在聽說轉到go語言了。

8. 格式化字串中的%和format

回到頂部
其中%是類c語言的風格,現如今隨著發展,學python的同學可能並不是很熟悉C語言,更多的使用的是format進行字串格式化了,format函式不僅僅是佔個位置那麼簡單了,它甚至可以進行進位制轉換、各種對齊方式等,功能堪稱強大。
以後建議使用format函式,它和print, str呼叫的都是例項的__str__方法。
stack overflow參考:String formatting: % vs .format

9. 迭代器和生成器

回到頂部
如果一個物件只有__iter__魔術方法,我們可以稱它為可迭代物件,但是不是迭代器。
如果一個物件擁有__next__方法,是迭代器。

定義一個可迭代物件,要實現__iter__方法,定義一個迭代器則必須實現__iter__方法和__next__方法。因為迭代器也是可迭代物件,所以雖然迭代器的定義是擁有__next__方法,但是同時是可迭代物件所以必須有__iter__方法。

__iter__方法返回的是迭代器類的例項,__next__方法返回的是自身,因為自身已經實現了__iter__方法(迭代器一定實現了)。

生成器是一種特殊的迭代器,生成器自動實現了迭代器協議,即__iter__方法和next方法,不需要再手動實現了。

在建立一個包含百萬元素的列表,要佔用很大的記憶體空間,我們可以採用生成器,能夠邊計算邊迴圈。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-3Y44C8YO-1574565572063)(https://github.com/duanmingpy/python-interview/blob/master/images/iterable.png)]

10. args和**kwargs

回到頂部
在我們定義形式引數的時候,*args**kwargs是一個很方便的選擇,但是也可以不使用,比如我們在函式形參中定義時不知道使用者會傳多少引數時可以使用可變引數,*args稱為可變位置引數,**kwargs稱為可變關鍵字傳參,接收的引數分別封成了元組和字典,還有放置在函式引數列表中的位置也要注意,**kwargs放在最後, *args必須在**kwargs之前。
args:

def average_score(*args): # args是一個元組
    """計算所有學科的平均分"""
    return sum(args) / len(args)

print(average_score(10, 20))

kwargs:

def make_tab(**kwargs):
    return kwargs   # 返回的是字典    

print(make_tab(name="tom", grade=100, age=20))

# 輸出結果:  {'name': 'tom', 'grade': 100, 'age': 20}

我們同樣可以使用*進行解包,但是引數要對應整齊。
stack overflow參考:Use of *args and **kwargs

11. 面向切面程式設計AOP和裝飾器

回到頂部
AOP和OOP一樣,是一種程式設計正規化,這種在執行時,動態地將程式碼切入到類的指定方法、指定位置上的程式設計思想就是面向切面的程式設計。簡單理解我認為AOP是OOP的補充,OOP從橫向上區分出一個個類來,而AOP則從縱向上向物件中加入特定的程式碼,有了AOP之後,OOP就變得立體了。
裝飾器就是這種思路了,有AOP的程式設計經驗,理解Python的裝飾器就是分分鐘的事。既然是裝飾器,那麼對被裝飾的物件來說,一定是功能得到了增強,按方法能增強的地方進行劃分,又可以分為以下四類:

  1. 方法呼叫前;
  2. 方法呼叫後;
  3. 方法呼叫前後(環繞);
  4. 方法呼叫異常;
# 方法呼叫前:   
def before(func):
    def check(a, *args):
        # 如果小於0,丟擲異常
        if a < 0:  # id肯定是大於等於0的
            raise Exception('a is less than zero!')
        else:
            return func(a, *args)
    # 記住,返回的一定是函式            
    return check

@before
def id(*args):
    return args  
    
# -------------------------
# 方法呼叫後
def afterProxy(func):
    # 修改返回結果
    def add_more (*args):
        result = func(* args)
        return result + 100  # 呼叫後修改
    return add_more
    
# -------------------------

# 方法呼叫前後
def afterProxy(func):
    # 修改返回結果
    def add_more (*args):
        #  對結果進行包裝
        for value in args:  # 呼叫前檢查
            if value < 0:
                raise ValueError
        result = func(* args)
        return result + 100  # 呼叫後修改
    return add_more
    
# ------------------------
# 方法呼叫異常
# 方法呼叫前後
def afterProxy(func):
    # 修改返回結果
    def add_more (*args):
        #  對結果進行包裝
        try:
            result = func(* args)
        except Exception:  # 捕獲異常
            return "run error"
        return result + 100  
    return add_more

StackOverflow參考:How to make a chain of function decorators?

12. 鴨子型別

回到頂部
理解:當我們看到遠遠的一隻鳥走起來像鴨子,游泳也像鴨子,叫聲也像鴨子,那麼我們就可以稱這隻鳥為鴨子。
在程式設計中:
我們並不關心物件是什麼型別,到底是不是鴨子,只關心行為。
比如在python中,有很多file-like的東西,比如StringIO,GzipFile,socket。它們有很多相同的方法,我們把它們當作檔案使用。
又比如list.extend()方法中,我們並不關心它的引數是不是list,只要它是可迭代的,所以它的引數可以是list/tuple/dict/字串/生成器等.
鴨子型別在動態語言中經常使用,非常靈活,使得python不想java那樣專門去弄一大堆的設計模式。

13. Python中的過載

回到頂部
函式過載的目的是解決兩個問題。

  1. 可變引數型別;
  2. 可變引數個數。
    設計原則:
    兩個函式的功能是相同的,但是傳入的引數型別是不同的,此時可以採用函式過載。
    在python中對於函式功能相同,引數型別不同這種情況並不需要過載,因為python本身就可以接收各種型別的引數到函式中,但是我們也可以說天生的實現了過載。
    對於函式功能相同,但是引數個數不同這種情況我們想到的肯定就是可變或者預設引數了,這裡是函式功能相同,但是如果函式功能不同那麼預設引數也就可以用得上了。
    分析過這兩種情況之後我們發現,python就根本不需要單獨提出來一個過載的方法,因為天生能夠實現。
    知乎參考:為什麼 Python 不支援函式過載?其他函式大部分都支援的?

14. 新式類和舊式類

回到頂部
舊式類沒有共同的祖先object,新式類是從python2.2版本出現的,到了python3來之後,所有的類都是新式類了,python2版本採用了相容模式,分為古典類(舊式類)和新式類,新式類中可以使用super。
在2.2之前python的MRO遵循的是經典演算法,2.2版本採用的是新式類演算法,到了2.2之後採用了C3演算法,能夠保證多繼承的單一性。
stack overflow參考:What is the difference betweeen old style and new style classes in Python?
部落格園參考:新式類和經典類

15. __new____init__的區別

回到頂部
這兩個都是類的魔術方法,都是在建立例項的時候使用的,其中__new__是一個靜態方法,而__init__是一個例項方法,在呼叫__new__方法的時候會返回一個建立的例項,然後才進行呼叫__init__進行對例項的例項化。
例子:

class School:
    def __new__(cls, *args, **kwargs):
        obj = super().__new__(School)
        obj.student_number = 10000  # 在new的時候就偷偷的增加一個屬性
        return obj

    def __init__(self, name, city):
        self.name = name
        self.city = city


Tsinghua = School('清華', "北京")
print(Tsinghua.student_number)  # 10000  

__metaclass__是建立類時起作用.所以我們可以分別使用__metaclass__,__new____init__來分別在類建立,例項建立和例項初始化的時候做一些小手腳.

stack overflow參考:Why is __init__() always called after __new__()?

16. Python中的作用域

回到頂部
Python中,一個變數的作用域總是由程式碼中被賦值的地方所決定的,如1. 函式-傳參中也能體現這樣一個作用域的思想,在Python中遇到一個變數的搜尋順序是:
本地作用域(Local)→ 當前作用域被嵌入的本地作用域(Enclosing locals) → 全域性/模組作用域(Global)→ 內建作用域(Built-in)。

17. GIL執行緒全域性鎖

回到頂部
執行緒全域性鎖(Global Interpreter Lock),即Python為了保證執行緒安全而採取的獨立執行緒執行的限制,說白了就是一個核只能在同一時間執行一個執行緒.對於io密集型任務,python的多執行緒起到作用,但對於cpu密集型任務,python的多執行緒幾乎佔不到任何優勢,還有可能因為爭奪資源而變慢。
可以參考開源中國的翻譯文章:Python最難的問題

18. 協程

回到頂部
簡單點說協程是程式和執行緒的升級版,程式和執行緒都面臨著核心態和使用者態的切換問題而耗費許多切換時間,而協程就是使用者自己控制切換的時機,不再需要陷入系統的核心態。
Python裡最常見的yield就是協程的思想!

19. 閉包

回到頂部
閉包(closure)是函數語言程式設計的重要的語法結構。閉包也是一種組織程式碼的結構,它同樣提高了程式碼的可重複使用性。
當一個內嵌函式引用其外部作作用域的變數,我們就會得到一個閉包. 總結一下,建立一個閉包必須滿足以下幾點:

  1. 必須有一個內嵌函式
  2. 內嵌函式必須引用外部函式中的變數
  3. 外部函式的返回值必須是內嵌函式

重點是函式執行後並不會被撤銷,就像16. Python中的作用域的instance字典一樣,當函式執行完後,instance並不被銷燬,而是繼續留在記憶體空間裡,這個功能類似類裡的類變數,只不過遷移到了函式上。
閉包就像個空心球一樣,你知道外面和裡面,但你不知道中間是什麼樣。

20. lambda匿名函式

回到頂部
lambda函式叫做匿名函式的原因是當我們想要再次呼叫這個函式的時候,我們必須重寫一遍,也就是重新定義一遍,雖然可以有識別符號記住它,但是我們一般不這樣做,真的是用來複用的函式我們會使用def關鍵字進行定義,注意的是lambda函式中不能出現return等號

print((lambda a, b: a + b)(3, 4))  

res = lambda : 100

print(res())  # 可以記住,但是一般不這樣做

result = (lambda a, b: a + b)(3, 4)

詳細內容參考知乎:Lambda 表示式有何用處?如何使用?

21. Python中函數語言程式設計

回到頂部
支援filtermapreduce三個高階函式。

a = [i for i in range(10) if i & 1]

result = filter(lambda x: x > 5, a)
print(result)  # <filter object at 0x00000204412ECC88>
print(list(result))  # [7, 9]  
a = [i for i in range(10) if i & 1]

result = map(lambda x: str(x), a)
print(result)  # <map object at 0x0000022BEB4BCAC8>
print(list(result))  # ['1', '3', '5', '7', '9']
from functools import reduce
a = [i for i in range(10) if i & 1]

result = reduce(lambda x, y: x + y, a)
print(result)  # 25

從上面可以看到,filtermap的結果都是惰性的,reduce的結果不是惰性的。

22. Python中的拷貝

回到頂部
copy()我們稱為淺拷貝,deepcopy()我們稱為深拷貝;看下面的例子:

import copy
lst = [1, 2, [5, 6]]
print("修改前的lst:", lst)
new_lst1 = lst.copy()
new_lst2 = copy.copy(lst)
new_lst3 = copy.deepcopy(lst)

lst[2][0] = 100  # 把lst的元素修改了,引用型別

print("修改後的lst:", lst)
print("內建的函式copy():", new_lst1)
print("copy模組的函式copy():", new_lst2)
print("copy模組的函式deepcopy():", new_lst3)

# 輸出結果:   
修改前的lst: [1, 2, [5, 6]]
修改後的lst: [1, 2, [100, 6]]
內建的函式copy(): [1, 2, [100, 6]]
copy模組的函式copy(): [1, 2, [100, 6]]
copy模組的函式deepcopy(): [1, 2, [5, 6]]

從結果中我們可以看到,如果是內建的copy還是copy模組的copy對於列表中存的地址都是複製一份地址過來,所以導致在修改地址背後的資料所有的copy都被修改了;而deepcopy則會順著地址,把地址後面的物件也複製一份,這樣在修改了lst之後,new_lst3沒有被修改。

23. Python的垃圾回收機制

回到頂部
Python GC主要使用引用計數(reference counting)來跟蹤和回收垃圾。在引用計數的基礎上,通過“標記-清除”(mark and sweep)解決容器物件可能產生的迴圈引用問題,通過“分代回收”(generation collection)以空間換時間的方法提高垃圾回收效率。
一:引用計數
PyObject是每個物件必有的內容,其中ob_refcnt就是做為引用計數。當一個物件有新的引用時,它的ob_refcnt就會增加,當引用它的物件被刪除,它的ob_refcnt就會減少.引用計數為0時,該物件生命就結束了。

優點:

  1. 簡單
  2. 實時性

缺點:

  1. 維護引用計數消耗資源
  2. 迴圈引用

二:標記-清除機制
基本思路是先按需分配,等到沒有空閒記憶體的時候從暫存器和程式棧上的引用出發,遍歷以物件為節點、以引用為邊構成的圖,把所有可以訪問到的物件打上標記,然後清掃一遍記憶體空間,把所有沒標記的物件釋放。

三:分代技術
分代回收的整體思想是:將系統中的所有記憶體塊根據其存活時間劃分為不同的集合,每個集合就成為一個“代”,垃圾收集頻率隨著“代”的存活時間的增大而減小,存活時間通常利用經過幾次垃圾回收來度量。

Python預設定義了三代物件集合,索引數越大,物件存活時間越長。

舉例: 當某些記憶體塊M經過了3次垃圾收集的清洗之後還存活時,我們就將記憶體塊M劃到一個集合A中去,而新分配的記憶體都劃分到集合B中去。當垃圾收集開始工作時,大多數情況都只對集合B進行垃圾回收,而對集合A進行垃圾回收要隔相當長一段時間後才進行,這就使得垃圾收集機制需要處理的記憶體少了,效率自然就提高了。在這個過程中,集合B中的某些記憶體塊由於存活時間長而會被轉移到集合A中,當然,集合A中實際上也存在一些垃圾,這些垃圾的回收會因為這種分代的機制而被延遲。

24. List

回到頂部
List是python的內建資料結構,在標準庫中一句pass帶過,Cpython是用C語言寫的List,下面是C中List的結構:
結構定義:

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

初始化:
假定是空列表[]

arguments: size of the list = 0
returns: list object = []
PyListNew:
    nbytes = size * size of global Python object = 0
    allocate new list object
    allocate list of pointers (ob_item) of size nbytes = 0
    clear ob_item
    set list's allocated var to 0 = 0 slots
    return list object 

非常重要的是知道list申請記憶體空間的大小(後文用allocated代替)的大小和list實際儲存元素所佔空間的大小(ob_size)之間的關係,ob_size的大小和len(L)是一樣的,而allocated的大小是在記憶體中已經申請空間大小。通常你會看到allocated的值要比ob_size的值要大。這是為了避免每次有新元素加入list時都要呼叫realloc進行記憶體分配。接下來我們會看到更多關於這些的內容。

追加:
使用Append函式會呼叫內部的C函式app1()

arguments: list object, new element
returns: 0 if OK, -1 if not
app1:
    n = size of list
    call list_resize() to resize the list to size n+1 = 0 + 1 = 1
    list[n] = list[0] = new element
    return 0

list_resize()會申請多餘的空間以避免呼叫多次list_resize(),list的增長模型是: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, …

arguments: list object, new size
returns: 0 if OK, -1 if not
list_resize:
    new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) = 3
    new_allocated += newsize = 3 + 1 = 4
    resize ob_item (list of pointers) to size new_allocated
    return 0

還有其他對應的函式可以參考網上的解讀。
推薦簡書上的解答:Python中list的實現

25. Python中的is

回到頂部
在python中我們經常會有判斷兩個值或兩個物件是否相等或是同一個,在兩個物件使用==進行比較的時候會呼叫相應的例項魔術方法__eq__,而使用is進行比較的時候會比較兩個物件的記憶體地址。

class MyClass1:
    def __init__(self):
        self.num = 1
    def __eq__(self, other):
        return self.num == other

class MyClass2:
    def __init__(self):
        self.num = 1

    def __eq__(self, other):
        return other == self.num

print(MyClass1() == MyClass2())  # True
print(MyClass1() is MyClass2())  # False

看上面的例子,真正比較的是兩個物件的num屬性,而is比較的是物件的地址;如果沒有定義__eq__==會比較記憶體地址,一般容器==比較的是大小,非容器的==比較的是地址。

26. read, readline和readlines

回到頂部
read是讀取整個檔案;
readline是讀取一行,使用生成器方法;
readlines是讀取整個檔案到一個迭代器供我們遍歷。

27. Python2和Python3的區別

回到頂部
print函式的變化:

# Python2
print 'Python', python_version()
print 'Hello, World!'
print('Hello, World!')
print "text", ; print 'print more text on the same line'

 
run result:
Python 2.7.6
Hello, World!
Hello, World!
text print more text on the same line

# Python3
print('Python', python_version())
print('Hello, World!')
print("some text,", end="") 
print(' print more text on the same line')


run result:
Python 3.7.4
Hello, World!
some text, print more text on the same line

整除的變化:

# python2
print 'Python', python_version()
print '3 / 2 =', 3 / 2
print '3 // 2 =', 3 // 2
print '3 / 2.0 =', 3 / 2.0
print '3 // 2.0 =', 3 // 2.0

# 輸出:   
run result:
Python 2.7.6
3 / 2 = 1
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0

# python3
print('Python', python_version())
print('3 / 2 =', 3 / 2)
print('3 // 2 =', 3 // 2)
print('3 / 2.0 =', 3 / 2.0)
print('3 // 2.0 =', 3 // 2.0)

# 輸出:
run result:
Python 3.4.1
3 / 2 = 1.5
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0

具體可以參考ShinChan的部落格:Python 2.x 與 Python 3.x的主要差異

28. super init

回到頂部
在前面14. 新式類和舊式類中也提了因為MRO的原因,python3可以直接使用super().__init__(),這是因為C3演算法把搜尋路徑確定了,而在python2.2之前並不能確定,所以必須要使用super(ChildB, self).__init__()來確定。
stack overflow參考:Understanding Python super() with __init__() methods [duplicate]
CSDN參考:
Python2.7中的super方法淺見

相關文章