本文假設你已經有一門面向對象編程語言基礎,如Java
等,且希望快速了解並使用Python
語言。本文對重點語法和數據結構以及用法進行詳細說明,同時對一些難以理解的點進行了圖解,以便大家快速入門。一些較偏的知識點在大家入門以后根據實際需要再查詢官方文檔即可,學習時切忌胡子眉毛一把抓。同時,一定要跟着示例多動手寫代碼。學習一門新語言時推薦大家同時去刷leetcode
,一來可以快速熟悉新語言的使用,二來也為今后找工作奠定基礎。推薦直接在網頁上刷leetcode
,因為面試的時候一般會讓你直接在網頁編寫代碼。leetcode
刷題路徑可以按我推薦的方式去刷。以下代碼中,以 >>>
和 ...
開頭的行是交互模式下的代碼部分,>?
開頭的行是交互模式下的輸入,其他行是輸出。python
代碼中使用 #
開啟行注釋。
模塊
模塊是包含 Python
定義和語句的文件,文件后綴名為 .py
,文件名即是模塊名。在pycharm
中創建名為python-learn
的項目,然后創建fib.py
文件,並輸入以下代碼后保存:
# 斐波拉契數列
def print_fib(n: int) -> None:
"""打印斐波拉契數列
:param n: 數列截至范圍
:return: None
"""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def get_fib(n: int) -> list:
"""獲取斐波拉契數列
:param n: 數列截至范圍
:return: 存有數列的list
"""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
接着,打開pycharm
中的Python Console
,使用import
語句導入該模塊:
# 導入 fib 模塊
>>> import fib
# 使用 fib 模塊中定義的函數
>>> fib.print_fib(10)
0 1 1 2 3 5 8
>>> fib.get_fib(10)
[0, 1, 1, 2, 3, 5, 8]
# 如果經常使用某個函數,可以把它賦值給局部變量
>>> print_fib = fib.print_fib
>>> print_fib(10)
0 1 1 2 3 5 8
# 每個模塊中都有一個特殊變量 __name__ 記錄着模塊的名字
>>> print(fib.__name__)
fib
使用as
關鍵字為導入的模塊指定別名:
# 導入 fib 並指定別名為 fibonacci
>>> import fib as fibonacci
>>> fibonacci.get_fib(10)
[0, 1, 1, 2, 3, 5, 8]
# 模塊名依然為 fib
>>> print(fibonacci.__name__)
fib
當我們通過import
語句導入模塊時:
- 首先查找要導入的模塊是否為內置模塊;
- 沒找到就會根據
sys.path
(list)中的路徑繼續查找。(sys.path
默認值包含:當前路徑、環境變量PYTHONPATH
中的路徑等)
查看sys.path
的值:
>>> print("======= start =======")
... for path in sys.path:
... print(path)
... print("======= end =======")
======= start =======
C:\software\jetbrains\PyCharm 2021.2.3\plugins\python\helpers\pydev
C:\software\jetbrains\PyCharm 2021.2.3\plugins\python\helpers\pycharm_display
C:\software\jetbrains\PyCharm 2021.2.3\plugins\python\helpers\third_party\thriftpy
C:\software\jetbrains\PyCharm 2021.2.3\plugins\python\helpers\pydev
C:\software\anaconda3\envs\python-learn\python310.zip
C:\software\anaconda3\envs\python-learn\DLLs
C:\software\anaconda3\envs\python-learn\lib
C:\software\anaconda3\envs\python-learn
C:\software\anaconda3\envs\python-learn\lib\site-packages
C:\software\jetbrains\PyCharm 2021.2.3\plugins\python\helpers\pycharm_matplotlib_backend
D:\code\python\python-learn # fib 模塊所在目錄
D:/code/python/python-learn # 對應linux路徑形式
======= end =======
當我們要導入的模塊路徑不在sys.path
中時,通過append
或extend
函數可以將目標路徑手動加入sys.path
中。前面的例子里,為什么我們沒有手動將項目路徑加入sys.path
中就可以導入fib
模塊呢?答案是pycharm
幫我們做了。在項目中,當我們打開Python Console
時,pycharm
執行了以下腳本:
sys.path.extend(['D:\\code\\python\\python-learn', 'D:/code/python/python-learn'])
注意:為了保證運行效率,每次解釋器會話只導入一次模塊。如果更改了模塊內容,必須重啟解釋器;僅交互測試一個模塊時,也可以使用 importlib.reload()
,例如 import importlib; importlib.reload(modulename)
。
以腳本方式執行模塊
.py
文件(模塊)還可以通過python
解釋器以腳本的方式執行。在pycharm
項目中打開Terminal
並執行以下命令可以解釋執行fib
模塊(也可點擊圖形界面的執行按鈕):
python fib.py
執行fib.py
時,解釋器從上到下依次解釋執行,由於代碼中沒有任何輸出動作所以終端沒有任何輸出。
當一個.py
文件(模塊)被當作腳本執行時,會被認為是程序的入口,類似於其他語言中的main
函數,於是python
解釋器會將該模塊的特殊變量__name__
置為__main__
。
現在,我們給fib.py
文件添加一些內容:
# 斐波拉契數列
def print_fib(n: int) -> None:
"""打印斐波拉契數列
:param n: 數列截至范圍
:return: None
"""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def get_fib(n: int) -> list:
"""獲取斐波拉契數列
:param n: 數列截至范圍
:return: 存有數列的list
"""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
# 只有當前文件(模塊)被當作腳本執行時 if 語句才為真
if __name__ == "__main__":
import sys
# argv[0]始終為文件名
print(sys.argv[0], end=" ")
# 傳入的第一個參數
print(sys.argv[1], end=" ")
# 傳入的第二個參數
print(sys.argv[2])
# 測試 print_fib 函數
print_fib(int(sys.argv[1]))
# 測試 get_fib 函數
fibSeries = get_fib(int(sys.argv[2]))
print(fibSeries)
輸入以下命令並執行:
PS D:\code\python\python-learn> python fib.py 10 20
fib.py 10 20
0 1 1 2 3 5 8
[0, 1, 1, 2, 3, 5, 8, 13]
包
如果你了解Java
,那么Python
中的包你就不會感到陌生。包是命名空間的一種實現方式,不同包中的同名模塊互不影響。包的寫法類似於A.B.C
,A
是包,B
是A
的子包,C
可以是B的子包也可以是模塊。包在磁盤上的表現就是目錄或者說是路徑,以包結構A.B.C
為例,若C
為模塊,那么對應的路徑為項目路徑/A/B/C.py
。同時Python
只把含 __init__.py
文件的目錄當成包。(后面解釋這個文件的用處)
以之前創建的python-learn
項目為例,在根目錄下創建包com.winlsr
,然后將fib.py
移動到com.winlsr
下,目錄結構如下:

從包中導入fib
模塊:
# 導入 fib 模塊
>>> import com.winlsr.fib
# 使用時必須引用模塊的全名
>>> com.winlsr.fib.print_fib(10)
0 1 1 2 3 5 8
使用from package import ...
導入模塊、函數(建議重啟一下解釋器):
# 導入 fib 模塊
>>> from com.winlsr import fib
# 使用時直接輸入模塊名
>>> fib.print_fib(10)
0 1 1 2 3 5 8
# 直接導入 fib 模塊中的 print_fib 函數
>>> from com.winlsr.fib import print_fib
# 直接使用方法即可
>>> print_fib(10)
0 1 1 2 3 5 8
使用 from package import item
時,item 可以是包的子模塊(或子包),也可以是包中定義的函數、類或變量等其他名稱。使用 import item
時,item
只可以是模塊或包。有的同學會疑問,導入包有什么用?以當前項目為例,我們導入com
包(建議重啟一下解釋器):
# 導入 com 包
>>> import com
>>> com
# 包其實也是模塊,對應的文件是包下的 __init__.py 文件?
<module 'com'="" from="" 'd:\\code\\python\\python-learn\\com\\__init__.py'="">
# 驗證猜想:
# 將該 __init__.py 文件中添加如下內容
def print_info():
print("name :", __name__)
# 重啟解釋器后再次導入 com 包
>>> import com
# 成功調用 __init__.py 文件中定義的函數,猜想正確
>>> com.print_info()
name : com
從包中導入 *
from package import *
不會導入package
中的任何模塊或子包,除非你在該package
下的__init__.py
文件中添加了如下顯示說明:
__all__ = ["子模塊名1", "子包名1"]
添加該說明后,執行from package import *
語句會導入指定的子模塊、子包。
同樣以之前創建的python-learn
項目為例,我們執行如語句(建議重啟一下解釋器):
# 希望導入 com.winlsr 包下的 fib 模塊
>>> from com.winlsr import *
# 發現並沒有導入
>>> fib
Traceback (most recent call last):
File "<input>", line 1, in <module>
NameError: name 'fib' is not defined
在 com.winlsr
下的__init__.py
文件中添加如下內容:
__all__ = ["fib"]
執行同樣的語句(建議重啟一下解釋器):
>>> from com.winlsr import *
# 導入成功
>>> fib
<module 'com.winlsr.fib'="" from="" 'd:\\code\\python\\python-learn\\com\\winlsr\\fib.py'="">
注意:通常不建議采用該小結講解的方法導入模塊或子包,而是采用from package import specific_submodule
。
dir()
函數
內置函數 dir()
用於查找模塊或子包中定義的名稱(變量、模塊(子包)、函數等),返回結果是經過排序的字符串列表。沒有參數時,dir()
列出當前定義的名稱。
>>> from com.winlsr import fib
>>> dir(fib)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'get_fib', 'print_fib']
>>> dir()
['__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fib', 'sys']
類
封裝
Python
中定義類使用class
關鍵字,類中可以定義變量和函數,起到封裝的作用。沿用之前創建的項目,在com.winlsr
包中創建rectangle
模塊並定義矩形類:
class Rectangle:
# 一個類只能有一個構造函數
def __init__(self): # 無參構造,self 類似於 this,指向實例對象本身
self.length = 0.0 # 構造函數中對實例對象的成員變量width和length進行初始化
self.width = 0.0 # 只有創建的實例才有這兩個變量
# 計算面積
def area(self):
return self.length * self.width
# 計算周長
def perimeter(self):
return (self.length + self.width) * 2
打開Python Console
,輸入以下語句來使用Rectangle
類:
>>> from com.winlsr.rectangle import Rectangle
# 使用無參構造創建類的實例對象
>>> rec = Rectangle()
# 調用rec實例的方法
>>> rec.perimeter()
0.0
>>> rec.area()
0.0
>>> rec.length
0.0
# Rectangle 類中沒有length這個成員變量,只有實例對象中才有
>>> Rectangle.length
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: type object 'Rectangle' has no attribute 'length'
Python
中實例對象的成員變量也可以不在__init__()
中定義和初始化,而是直接在類中定義並初始化。原來的Rectangle
類可以改寫為:
class Rectangle:
# 成員變量直接定義在類中並初始化,類和實例都有兩個變量
length = 0.0
width = 0.0
def area(self):
return self.length * self.width
def perimeter(self):
return (self.length + self.width) * 2
注意,如果變量直接定義在類中,那么創建實例時,實例中的變量是類中變量的淺拷貝。重啟解釋器執行以下語句:
>>> from com.winlsr.rectangle import Rectangle
>>> rec0 = Rectangle()
>>> rec0.width is Rectangle.width
True
>>> rec1 = Rectangle()
>>> rec1.length is Rectangle.length
True
實例rec0
和rec1
創建后的內存分布如下:

對實例rec0
和rec1
中的成員變量進行修改:
# 修改實例的成員變量
>>> rec0.width = 1.0
... rec0.length = 2.0
... rec1.width = 3.0
... rec1.length = 4.0
# 打印
>>> print(Rectangle.width)
... print(Rectangle.length)
... print(rec0.width)
... print(rec0.length)
... print(rec1.width)
... print(rec1.length)
0.0
0.0
1.0
2.0
3.0
4.0
根據結果可以發現rec0
和rec1
實例之間的成員變量(不可變 immutable 類型)相互之間不影響,修改后它們的內存分布如下:

注意,如果直接定義在類中的變量是可變(mutable)類型,使用時就應該謹慎。如下,依然在com.winlsr
包中創建actor
模塊並定義演員類:
class Actor:
# 參演的電影
movies = []
def get_movies(self):
return self.movies
def add_movie(self, movie):
self.movies.append(movie)
使用Actor
類:
>>> from com.winlsr.actor import Actor
... lixiaolong = Actor()
... xuzheng = Actor()
... lixiaolong.add_movie("猛龍過江")
... xuzheng.add_movie("我不是葯神")
... print(lixiaolong.get_movies())
... print(xuzheng.get_movies())
# 發現兩個對象的 movies list 是共享的
['猛龍過江', '我不是葯神']
print(xuzheng.get_movies())
['猛龍過江', '我不是葯神']
以上情況是因為多個Actor
類的實例中的movies
變量(引用)指向同一個list
對象,實例lixiaolong
和xuzheng
內存分布如下:
創建后實例后,調用add_movie()
之前的內存分布:

調用add_movie()
之后的內存分布:

為了使每個演員的參演電影列表相互獨立,在創建Actor
類的實例時應該為每個實例創建一個新的list
:
class Actor:
# 參演的電影
# 該語句沒有用,可以刪掉。實例在創建時會通過構造函數改變指向movies的指向
movies = []
def __init__(self):
self.movies = []
def get_movies(self):
return self.movies
def add_movie(self, movie):
self.movies.append(movie)
重啟解釋器,再次執行以下語句:
>>> from com.winlsr.actor import Actor
... lixiaolong = Actor()
... xuzheng = Actor()
... lixiaolong.add_movie("猛龍過江")
... xuzheng.add_movie("我不是葯神")
... print(lixiaolong.get_movies())
... print(xuzheng.get_movies())
['猛龍過江']
['我不是葯神']
綜上,根據前面的示例,如果你不希望多個實例之間共享變量,建議直接將變量定義在__init__
函數中。最后,Python
支持靜態語言不支持的實例屬性動態綁定特性:
# 給 lixiaolong 這個實例動態添加 age 屬性
>>> lixiaolong.age = 41
>>> lixiaolong.age
41
# 不影響其他實例
>>> xuzheng.age
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Actor' object has no attribute 'age'
訪問控制
前面的實例中,我們可以通過instance.var
的形式來直接訪問實例的成員變量。但通常我們不希望實例中的成員變量被直接訪問,而是通過getter
和setter
來訪問,這需要我們將成員變量設置為private
。
Python
中:
- 帶有一個下划線的變量,形如
_var
應該被當作是API
(常用於模塊中)的非公有部分 (函數或是數據成員)。雖然可以正常訪問,但我們應遵循這樣一個約定; - 類中私有成員變量應當用兩個前綴下划線,至多一個后綴下划線標識,形如:
__var
。但該變量並不是真正的不能訪問,這是因為Python
實現的機制是”名稱改寫“。這種機制在執行時會將__var
改為_classname__var
,但你仍然可以通過_classname__var
來訪問。 - 形如
__var__
的變量是特殊變量,可以訪問,但通常我們不需要定義此類變量。
實驗驗證,將Rectangle
類改為如下代碼:
class Rectangle:
def __init__(self, length, width):
self.__length = length
self.__width = width
def area(self):
return self.__length * self.__width
def perimeter(self):
return (self.__length + self.__width) * 2
重啟解釋器,執行如下語句:
>>> from com.winlsr.rectangle import Rectangle
>>> rec = Rectangle(12.0, 24.0)
# 直接訪問私有變量 __width,失敗
>>> print(rec.__width)
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Rectangle' object has no attribute '__width'
# 訪問私有變量改寫后的名稱 _Rectangle__width,成功
>>> print(rec._Rectangle__width)
24.0
對應Python Console
如下:
繼承
Python
繼承語法如下,所有類都默認繼承:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-n>
下面我們在com.winlsr
包下創建person
模塊並定義Person
類,作為Actor
和Teacher
類的基類:
class Person:
def __init__(self, name, id_number):
self.__name = name
self.__id_number = id_number
def set_name(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_id_number(self, id_number):
self.__id_number = id_number
def get_id_number(self):
return self.__id_number
在com.winlsr
包下創建actor
模塊並定義Actor
類,它是Person
類的派生類:
from .person import Person
class Actor(Person):
def __init__(self, name, id_number):
self.__movies = []
# 三種調用父類構造函數的方式
super().__init__(name, id_number)
# super(Actor, self).__init__(name, id_number)
# Person.__init__(self, name, id_number)
def add_movie(self, movie):
self.__movies.append(movie)
def print_info(self):
print(self.get_name(), self.get_id_number(), self.__movies, sep=" : ")
在com.winlsr
包下創建teacher
模塊並定義Teacher
類,它也是Person
類的派生類:
from .person import Person
class Teacher(Person):
# 無構造函數,創建對象時會調用父類構造函數
def print_info(self):
print(self.get_name(), self.get_id_number(), sep=" : ")
重啟解釋器,執行如下語句:
>>> from com.winlsr.actor import Actor
>>> actor = Actor("xuzheng", 123456789)
>>> actor.add_movie("我不是葯神")
>>> actor.print_info()
xuzheng : 123456789 : ['我不是葯神']
>>> from com.winlsr.teacher import Teacher
>>> teacher = Teacher()
# Teacher 類中沒有定義 __init__(),這里會調用父類 Person 的構造並傳入參數
>>> teacher = Teacher("lsl", 123459876)
>>> teacher.print_info()
lsl : 123459876
對應Python Console
如下:
Python
也支持一種多重繼承。 帶有多個基類的類定義語句如下所示:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-n>
派生類實例如果某一屬性在 DerivedClassName
中未找到,則會到 Base1
中搜索它,然后(遞歸地)到 Base1
的基類中搜索,如果在那里未找到,再到 Base2
中搜索,依此類推。
其他
結語
教程到這里就結束了,最后推薦大家再去看看廖雪峰老師講解的異常處理
和IO
的內容(也可以用到的時候再看),他比官網講解的更有條理。學完這些內容就基本入門了,今后可以根據自己應用的領域再進一步學習即可,比如深度學習、web開發等。