一、何為模塊
1.一個模塊就是一個包含了python定義和聲明的文件,文件名就是模塊名字加上.py的后綴。
2.模塊目的和函數,對象等一樣,是為了代碼的復用性而存在的。即一個py程序寫了很多功能,也可以被其他py程序調用。被調用后被作為模塊,此時會產生一個pyc的文件即為這個模塊的緩存文件,相當於編譯了模塊文件使該py模塊被調用的速度變快。一個大的項目一個人肯定是不行的,所以模塊也為程序員相互間的合作提供了便利。
二、模塊妙用
1.import
只需要在py程序開頭加上import 文件名(不能有.py后綴)。例如,模塊的文件名是span.py,那么只需在自己的py程序加import span。
2. import可以在程序的任意位置調用,但一次調用后會駐留內存,下次再調用會直接調用內存中的模塊(python的優化)。
3.個模塊都是一個獨立的名稱空間,定義在這個模塊中的函數,把這個模塊的名稱空間當做全局名稱空間,這樣我們在編寫自己的模塊時,就不用擔心我們定義在自己模塊中全局變量會在被導入時,與使用者的全局變量沖突。
print('from the spam.py') money=1000 def read1(): print('spam->read1->money',1000) def read2(): print('spam->read2 calling read') read1() def change(): global money money=0

#test.py import spam #只在第一次導入時才執行spam.py內代碼,此處的顯式效果是只打印一次'from the spam.py',當然其他的頂級代碼也都被執行了,只不過沒有顯示效果. import spam import spam import spam ''' 執行結果: from the spam.py '''

#測試一:money與spam.money不沖突 #test.py import spam money=10 print(spam.money) ''' 執行結果: from the spam.py 1000 '''

#測試二:read1與spam.read1不沖突 #test.py import spam def read1(): print('========') spam.read1() ''' 執行結果: from the spam.py spam->read1->money 1000 '''

#測試三:執行spam.change()操作的全局變量money仍然是spam中的 #test.py import spam money=1 spam.change() print(money) ''' 執行結果: from the spam.py 1 '''
so,在首次導入模塊后,解釋器做了三件事:①為源文件(spam模塊)創建新的名稱空間,在spam中定義的函數和方法若是使用到了global時訪問的就是這個名稱空間。②在新創建的命名空間中執行模塊中包含的代碼③創建名字spam來引用該命名空間
4.模塊可以起別名
if file_format == 'xml': import xmlreader as reader elif file_format == 'csv': import csvreader as reader data=reader.read_date(filename) #這也體現的程序的歸一化思想
5.一行導入多個模塊(不建議,為了增加可讀性一般一行只加一個)
import sys,os,re
三、import高級使用(form ... import ...)
1. from 語句相當於import,也會創建新的名稱空間,但是將spam中的名字直接導入到當前的名稱空間中,在當前名稱空間中,不需要使用“模塊.”的方式,直接使用名字就可以了。
from spam import read1,read2
2.上述方法也可以使用as起別名
from spam import read1 as read
3.from spam import *
把spam中所有的不是以下划線(_)開頭的名字都導入到當前位置,大部分情況下我們的python程序不應該使用這種導入方式,因為*你不知道你導入什么名字,很有可能會覆蓋掉你之前已經定義的名字。而且可讀性極其的差,在交互式環境中導入時沒有問題。
from spam import * #將模塊spam中所有的名字都導入到當前名稱空間 print(money) print(read1) print(read2) print(change) ''' 執行結果: from the spam.py 1000 <function read1 at 0x1012e8158> <function read2 at 0x1012e81e0> <function change at 0x1012e8268> '''
可以在模塊文件使用__all__=[]來控制*導入什么,通常用來發布新版本
__all__=['money','read1'] #在模塊文件加入這個可以選擇導入什么變量
4.考慮到性能的原因,每個模塊只被導入一次,放入字典sys.module中,如果你改變了模塊的內容,你必須重啟程序,python不支持重新加載或卸載之前導入的模塊,
有的同學可能會想到直接從sys.module中刪除一個模塊不就可以卸載了嗎,注意了,你刪了sys.module中的模塊對象仍然可能被其他程序的組件所引用,因而不會被清楚。
特別的對於我們引用了這個模塊中的一個類,用這個類產生了很多對象,因而這些對象都有關於這個模塊的引用。
5.把模塊當作腳本使用
使用"__name__"等於"__main__"
#fib.py def fib(n): # write Fibonacci series up to n a, b = 0, 1 while b < n: print(b, end=' ') a, b = b, a+b print() def fib2(n): # return Fibonacci series up to n result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result if __name__ == "__main__": import sys fib(int(sys.argv[1])) #可以自己測試使用
6.模塊搜索路徑
python解釋器在啟動時會自動加載一些模塊,可以使用sys.modules查看。
模塊的查找順序是:內存中已經加載的模塊->內置模塊->sys.path路徑中包含的模塊。
在初始化后,python程序可以修改sys.path,路徑放到前面的優先於標准庫被加載。
import sys sys.path.append('/a/b/c/d') sys.path.insert(0,'/x/y/z') #排在前的目錄,優先被搜索
#首先制作歸檔文件:zip module.zip foo.py bar.py import sys sys.path.append('module.zip') import foo,bar #也可以使用zip中目錄結構的具體位置 sys.path.append('module.zip/lib/python')
至於.egg文件是由setuptools創建的包,這是按照第三方python庫和擴展時使用的一種常見格式,.egg文件實際上只是添加了額外元數據(如版本號,依賴項等)的.zip文件。
需要強調的一點是:只能從.zip文件中導入.py,.pyc等文件。使用C編寫的共享庫和擴展塊無法直接從.zip文件中加載(此時setuptools等打包系統有時能提供一種規避方法),且從.zip中加載文件不會創建.pyc或者.pyo文件,因此一定要事先創建他們,來避免加載模塊是性能下降。
7.小提示
①模塊名字區分大小寫
②可以使用-O或者-OO轉換python命令來減少編譯模塊的大小
1 -O轉換會幫你去掉assert語句 2 -OO轉換會幫你去掉assert語句和__doc__文檔字符串 3 由於一些程序可能依賴於assert語句或文檔字符串,你應該在在確認需要的情況下使用這些選項。
③在速度上從.pyc文件中讀指令來執行不會比從.py文件中讀指令執行更快,只有在模塊被加載時,.pyc文件才是更快的
④只有使用import語句是才將文件自動編譯為.pyc文件,在命令行或標准輸入中指定運行腳本則不會生成這類文件,因而我們可以使用compieall模塊為一個目錄中的所有模塊創建.pyc文件
模塊可以作為一個腳本(使用python -m compileall)編譯Python源 python -m compileall /module_directory 遞歸着編譯 如果使用python -O -m compileall /module_directory -l則只一層 命令行里使用compile()函數時,自動使用python -O -m compileall 詳見:https://docs.python.org/3/library/compileall.html#module-compileall
8.標准模塊
ython提供了一個標准模塊庫,一些模塊被內置到解釋器中,這些提供了不屬於語言核心部分的操作的訪問,但它們是內置的,無論是為了效率還是提供對操作系統原語的訪問。這些模塊集合是依賴於底層平台的配置項,如winreg模塊只能用於windows系統。特別需要注意的是,sys模塊內建在每一個python解釋器。
9.dir
內建函數dir是用來查找模塊中定義的名字,返回一個有序字符串列表
import spam
dir(spam)
如果沒有參數,dir()列舉出當前定義的名字
dir()不會列舉出內建函數或者變量的名字,它們都被定義到了標准模塊builtin中,可以列舉出它們,
import builtins
dir(builtins)
四、包
一、概念
無論是import形式還是from...import形式,凡是在導入語句中(而不是在使用時)遇到帶點的,都要第一時間提高警覺:這是關於包才有的導入語法。
包的本質就是一個包含__init__.py文件的目錄。
注意:
1.關於包相關的導入語句也分為import和from ... import ...兩種,但是無論哪種,無論在什么位置,在導入時都必須遵循一個原則:凡是在導入時帶點的,點的左邊都必須是一個包,否則非法。可以帶有一連串的點,如item.subitem.subsubitem,但都必須遵循這個原則。
2.對於導入后,在使用時就沒有這種限制了,點的左邊可以是包,模塊,函數,類(它們都可以用點的方式調用自己的屬性)。
3.對比import item 和from item import name的應用場景:
如果我們想直接使用name那必須使用后者
二、from ... import ...
需要注意的是from后import導入的模塊,必須是明確的一個不能帶點,否則會有語法錯誤,如:from a import b.c是錯誤語法
三、__init__.py文件
不管是哪種方式,只要是第一次導入包或者是包的任何其他部分,都會依次執行包下的__init__.py文件(我們可以在每個包的文件內都打印一行內容來驗證一下),這個文件可以為空,但是也可以存放一些初始化包的代碼。
四、from glance.api import *
此處是想從包api中導入所有,實際上該語句只會導入包api下__init__.py文件中定義的名字,我們可以在這個文件中定義__all___:
#在__init__.py中定義 x=10 def func(): print('from api.__init.py') __all__=['x','func','policy']
此時我們在於glance同級的文件中執行from glance.api import *就導入__all__中的內容(versions仍然不能導入)。
五、絕對導入和相對導入
們的最頂級包glance是寫給別人用的,然后在glance包內部也會有彼此之間互相導入的需求,這時候就有絕對導入和相對導入兩種方式:
絕對導入:以glance作為起始
相對導入:用.或者..的方式最為起始(只能在一個包中使用,不能用於不同目錄內)
在glance/api/version.py #絕對導入 from glance.cmd import manage manage.main() #相對導入 from ..cmd import manage manage.main()
注意:在使用pycharm時,有的情況會為你多做一些事情,這是軟件相關的東西,會影響你對模塊導入的理解,因而在測試時,一定要回到命令行去執行,模擬我們生產環境,你總不能拿着pycharm去上線代碼吧!!!(因為本人一直使用pycharm)
特別需要注意的是:可以用import導入內置或者第三方模塊,但是要絕對避免使用import來導入自定義包的子模塊,應該使用from... import ...的絕對或者相對導入,且包的相對導入只能用from的形式。
六、單獨導入包
單獨導入包名稱時不會導入包中所有包含的所有子模塊。
#在與glance同級的test.py中 import glance glance.cmd.manage.main() ''' 執行結果: AttributeError: module 'glance' has no attribute 'cmd' '''
解決方法:
#glance/__init__.py from . import cmd #glance/cmd/__init__.py from . import manage
執行:
#在於glance同級的test.py中 import glance glance.cmd.manage.main()
常用的模塊

#traceback模塊被用來跟蹤異常返回信息 try: raise SyntaxError, "traceback test" except: traceback.print_exc() #用於打印完整的錯誤信息。

#subprocess 我們通過標准庫中的subprocess包來fork一個子進程,並運行一個外部的程序。 import subprocess retcode = subprocess.call(["ls", "-l"]) #和shell中命令ls -a顯示結果一樣 print retcode """ shell默認為False,在Linux下,shell=False時, Popen調用os.execvp()執行args指定的程序;shell=True時,如果args是字符串,Popen直接調用系統的Shell來執行args指定的程序,如果args是一個序列,則args的第一項是定義程序命令字符串,其它項是調用系統Shell時的附加參數。 """ retcode = subprocess.call("ls -l",shell=True)
subprocess 待續
paramiko模塊提供了ssh及sft進行遠程登錄服務器執行命令和上傳下載文件的功能

# 建立一個sshclient對象 ssh = paramiko.SSHClient() # 允許將信任的主機自動加入到host_allow 列表,此方法必須放在connect方法的前面 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 調用connect方法連接服務器 ssh.connect(hostname='192.168.2.129', port=22, username='super', password='super') # 執行命令 stdin, stdout, stderr = ssh.exec_command('df -hl') # 結果放到stdout中,如果有錯誤將放到stderr中 print(stdout.read().decode()) # 關閉連接 ssh.close()

''' 方法1是傳統的連接服務器、執行命令、關閉的一個操作,有時候需要登錄上服務器執行多個操作,比如執行命令、上傳/下載文件,方法1則無法實現,可以通過如下方式來操作 ''' # 實例化一個transport對象 trans = paramiko.Transport(('192.168.2.129', 22)) # 建立連接 trans.connect(username='super', password='super') # 將sshclient的對象的transport指定為以上的trans ssh = paramiko.SSHClient() ssh._transport = trans # 執行命令,和傳統方法一樣 stdin, stdout, stderr = ssh.exec_command('df -hl') print(stdout.read().decode()) # 關閉連接 trans.close()

# 指定本地的RSA私鑰文件,如果建立密鑰對時設置的有密碼,password為設定的密碼,如無不用指定password參數 pkey = paramiko.RSAKey.from_private_key_file('/home/super/.ssh/id_rsa', password='12345') # 建立連接 ssh = paramiko.SSHClient() ssh.connect(hostname='192.168.2.129', port=22, username='super', pkey=pkey) # 執行命令 stdin, stdout, stderr = ssh.exec_command('df -hl') # 結果放到stdout中,如果有錯誤將放到stderr中 print(stdout.read().decode()) # 關閉連接 ssh.close()

# 指定本地的RSA私鑰文件,如果建立密鑰對時設置的有密碼,password為設定的密碼,如無不用指定password參數 pkey = paramiko.RSAKey.from_private_key_file('/home/super/.ssh/id_rsa', password='12345') # 建立連接 trans = paramiko.Transport(('192.168.2.129', 22)) trans.connect(username='super', pkey=pkey) # 將sshclient的對象的transport指定為以上的trans ssh = paramiko.SSHClient() ssh._transport = trans # 執行命令,和傳統方法一樣 stdin, stdout, stderr = ssh.exec_command('df -hl') print(stdout.read().decode()) # 關閉連接 trans.close()
傳文件 SFTP

# 實例化一個trans對象# 實例化一個transport對象 trans = paramiko.Transport(('192.168.2.129', 22)) # 建立連接 trans.connect(username='super', password='super') # 實例化一個 sftp對象,指定連接的通道 sftp = paramiko.SFTPClient.from_transport(trans) # 發送文件 sftp.put(localpath='/tmp/11.txt', remotepath='/tmp/22.txt') # 下載文件 # sftp.get(remotepath, localpath) trans.close()

''' 以上操作都是基本的連接,如果我們想實現一個類似xshell工具的功能,登錄以后可以輸入命令回車后就返回結果: ''' import paramiko import os import select import sys # 建立一個socket trans = paramiko.Transport(('192.168.2.129', 22)) # 啟動一個客戶端 trans.start_client() # 如果使用rsa密鑰登錄的話 ''' default_key_file = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') prikey = paramiko.RSAKey.from_private_key_file(default_key_file) trans.auth_publickey(username='super', key=prikey) ''' # 如果使用用戶名和密碼登錄 trans.auth_password(username='super', password='super') # 打開一個通道 channel = trans.open_session() # 獲取終端 channel.get_pty() # 激活終端,這樣就可以登錄到終端了,就和我們用類似於xshell登錄系統一樣 channel.invoke_shell() # 下面就可以執行你所有的操作,用select實現 # 對輸入終端sys.stdin和 通道進行監控, # 當用戶在終端輸入命令后,將命令交給channel通道,這個時候sys.stdin就發生變化,select就可以感知 # channel的發送命令、獲取結果過程其實就是一個socket的發送和接受信息的過程 while True: readlist, writelist, errlist = select.select([channel, sys.stdin,], [], []) # 如果是用戶輸入命令了,sys.stdin發生變化 if sys.stdin in readlist: # 獲取輸入的內容 input_cmd = sys.stdin.read(1) # 將命令發送給服務器 channel.sendall(input_cmd) # 服務器返回了結果,channel通道接受到結果,發生變化 select感知到 if channel in readlist: # 獲取結果 result = channel.recv(1024) # 斷開連接后退出 if len(result) == 0: print("\r\n**** EOF **** \r\n") break # 輸出到屏幕 sys.stdout.write(result.decode()) sys.stdout.flush() # 關閉通道 channel.close() # 關閉鏈接 trans.close()

import paramiko import os import select import sys import tty import termios ''' 實現一個xshell登錄系統的效果,登錄到系統就不斷輸入命令同時返回結果 支持自動補全,直接調用服務器終端 ''' # 建立一個socket trans = paramiko.Transport(('192.168.2.129', 22)) # 啟動一個客戶端 trans.start_client() # 如果使用rsa密鑰登錄的話 ''' default_key_file = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') prikey = paramiko.RSAKey.from_private_key_file(default_key_file) trans.auth_publickey(username='super', key=prikey) ''' # 如果使用用戶名和密碼登錄 trans.auth_password(username='super', password='super') # 打開一個通道 channel = trans.open_session() # 獲取終端 channel.get_pty() # 激活終端,這樣就可以登錄到終端了,就和我們用類似於xshell登錄系統一樣 channel.invoke_shell() # 獲取原操作終端屬性 oldtty = termios.tcgetattr(sys.stdin) try: # 將現在的操作終端屬性設置為服務器上的原生終端屬性,可以支持tab了 tty.setraw(sys.stdin) channel.settimeout(0) while True: readlist, writelist, errlist = select.select([channel, sys.stdin,], [], []) # 如果是用戶輸入命令了,sys.stdin發生變化 if sys.stdin in readlist: # 獲取輸入的內容,輸入一個字符發送1個字符 input_cmd = sys.stdin.read(1) # 將命令發送給服務器 channel.sendall(input_cmd) # 服務器返回了結果,channel通道接受到結果,發生變化 select感知到 if channel in readlist: # 獲取結果 result = channel.recv(1024) # 斷開連接后退出 if len(result) == 0: print("\r\n**** EOF **** \r\n") break # 輸出到屏幕 sys.stdout.write(result.decode()) sys.stdout.flush() finally: # 執行完后將現在的終端屬性恢復為原操作終端屬性 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) # 關閉通道 channel.close() # 關閉鏈接 trans.close()
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple nympy 使用清華源,5分更新一次。