python 模块与包
?什么是模塊?
模塊就是一組功能的集合體,我們的程序可以導入模塊來復用模塊里的功能。即一個模塊就是一個包含了一組功能的python文件,比如spam.py,模塊名為spam,可以通過import spam使用。
在python中,模塊的使用方式都是一樣的,但其實細說的話,模塊可以分為四個通用類別:
- 使用python編寫的.py文件
- 已被編譯為共享庫或DLL的C或C++擴展
- 把一系列模塊組織到一起的文件夾(注:文件夾下有一個__init__.py文件,該文件夾稱之為包)
- 使用C編寫并鏈接到python解釋器的內置模塊
模塊的優勢
- 從文件級別組織程序,更方便管理
- 拿來主義,提升開發效率
ps:如果退出python解釋器然后重新進入,那么你之前定義的函數或者變量都將丟失,因此我們通常將程序寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script
模塊使用
模塊可以包含可執行的語句和函數的定義,這些語句的目的是初始化模塊,它們只在模塊名第一次遇到導入import語句時才執行(import語句是可以在程序中的任意位置使用的,且針對同一個模塊很import多次,為了防止你重復導入,python的優化手段是:第一次導入后就將模塊名加載到內存了,后續的import語句僅是對已經加載到內存中的模塊對象增加了一次引用,不會重新執行模塊內的語句)。
ps:可以從sys.module中找到當前已經加載的模塊,sys.module是一個字典,內部包含模塊名與模塊對象的映射,該字典決定了導入模塊時是否需要重新導入
模塊初次導入所做的三件事,重復導入會直接引用內存中已經加載好的結果
被導入模塊有獨立的名稱空間:每個模塊都是一個獨立的名稱空間,定義在這個模塊中的函數,把這個模塊的名稱空間當做全局名稱空間,這樣我們在編寫自己的模塊時,就不用擔心我們定義在自己模塊中全局變量會在被導入時,與使用者的全局變量沖突
為模塊名起別名 關鍵字 as:為已經導入的模塊起別名的方式對編寫可擴展的代碼很有用
import spam as smprint(sm.money)一行導入多個模塊
import sys,os,refrom ... import... 語句
from...import 與import的對比,使用from...import...則是將模塊spam中的名字直接導入到當前的名稱空間中,所以在當前名稱空間中,直接使用名字就可以了、無需加前綴:spam,使用起來方便,但容易與當前執行文件中的名字沖突,支持as
一行導入多個名字
from...import * 把模塊中所有的不是以下劃線(_)開頭的名字都導入到當前位置,大部分情況下不建議使用,而使用__all__來控制*(用來發布新版本),在模塊中新增一行,_all__=['test','text'] 這樣在另外一個文件中用from 模塊名 import *就這能導入列表中規定的兩個名字
模塊循環導入問題
模塊循環/嵌套導入拋出異常的根本原因是由于在python中模塊被導入一次之后,就不會重新導入,只會在第一次導入時執行模塊內代碼,在項目中應該盡量避免出現循環/嵌套導入,如果出現多個模塊都需要共享的數據,可以將共享的數據集中存放到某一個地方,在程序出現了循環/嵌套導入后的異常分析、解決方法如下:
#示范文件內容如下 #m1.py print('正在導入m1') from m2 import yx='m1'#m2.py print('正在導入m2') from m1 import xy='m2'#run.py import m1#測試一 執行run.py會拋出異常 正在導入m1 正在導入m2 Traceback (most recent call last): File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/aa.py", line 1, in <module> import m1 File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module> from m2 import y File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in <module> from m1 import x ImportError: cannot import name 'x'#測試一結果分析 先執行run.py--->執行import m1,開始導入m1并運行其內部代碼--->打印內容"正在導入m1" --->執行from m2 import y 開始導入m2并運行其內部代碼--->打印內容“正在導入m2”--->執行from m1 import x,由于m1已經被導入過了,所以不會重新導入,所以直接去m1中拿x,然而x此時并沒有存在于m1中,所以報錯#測試二:執行文件不等于導入文件,比如執行m1.py不等于導入了m1 直接執行m1.py拋出異常 正在導入m1 正在導入m2 正在導入m1 Traceback (most recent call last): File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module> from m2 import y File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in <module> from m1 import x File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module> from m2 import y ImportError: cannot import name 'y'#測試二分析 執行m1.py,打印“正在導入m1”,執行from m2 import y ,導入m2進而執行m2.py內部代碼--->打印"正在導入m2",執行from m1 import x,此時m1是第一次被導入,執行m1.py并不等于導入了m1,于是開始導入m1并執行其內部代碼--->打印"正在導入m1",執行from m1 import y,由于m1已經被導入過了,所以無需繼續導入而直接問m2要y,然而y此時并沒有存在于m2中所以報錯# 解決方法: 方法一:導入語句放到最后 m1.py print('正在導入m1')x='m1'from m2 import y#m2.py print('正在導入m2') y='m2'from m1 import x方法二:導入語句放到函數中 #m1.py print('正在導入m1')def f1(): from m2 import y print(x,y)x = 'm1'# f1() m2.py print('正在導入m2')def f2(): from m1 import x print(x,y)y = 'm2'#run.py import m1m1.f1()模塊重載
考慮到性能的原因,每個模塊只被導入一次,放入字典sys.module中,如果改變了模塊的內容,則必須重啟程序,python不支持重新加載或卸載之前導入的模塊,如果想到直接從sys.module中刪除一個模塊則不就可以卸載了嗎,注意刪了sys.module中的模塊對象仍然可能被其他程序的組件所引用,因而不會被清楚,特別的引用了這個模塊中的一個類,用這個類產生了很多對象,因而這些對象都有關于這個模塊的引用。
模塊與腳本
- 腳本,一個文件就是整個程序,用來被執行
- 模塊,文件中存放著一堆功能,用來被導入使用
python為我們內置了全局變量__name__,文件被當做腳本執行時:__name__ 等于'__main__',文件被當做模塊導入時:__name__等于模塊名,其作用是用來控制.py文件在不同的應用場景下執行不同的邏輯
if __name__ == '__main__':
模塊搜索路徑:模塊的查找順序是:內存中已經加載的模塊->內置模塊->sys.path路徑中包含的模塊
1、在第一次導入某個模塊時(比如spam),會先檢查該模塊是否已經被加載到內存中(當前執行文件的名稱空間對應的內存),如果有則直接引用
ps:python解釋器在啟動時會自動加載一些模塊到內存中,可以使用sys.modules查看
2、如果沒有,解釋器則會查找同名的內建模塊
3、如果還沒有找到就從sys.path給出的目錄列表中依次尋找spam.py文件。
包介紹
包就是一個包含有__init__.py文件的文件夾,所以其實我們創建包的目的就是為了用文件夾將文件/模塊組織起來,在python3中,即使包下沒有__init__.py文件,import 包仍然不會報錯,而在python2中,包下一定要有該文件,否則import 包報錯,創建包的目的不是為了運行,而是被導入使用,記住,包只是模塊的一種形式而已,包的本質就是一種模塊。包的作用
包的本質就是一個文件夾,那么文件夾唯一的功能就是將文件組織起來,隨著功能越寫越多,我們無法將所以功能都放到一個文件中,于是我們使用模塊去組織功能,而隨著模塊越來越多,我們就需要用文件夾將模塊文件組織起來,以此來提高程序的結構性和可維護性
注意
1.關于包相關的導入語句也分為import和from ... import ...兩種,但是無論哪種,無論在什么位置,在導入時都必須遵循一個原則:凡是在導入時帶點的,點的左邊都必須是一個包,否則非法??梢詭в幸贿B串的點,如item.subitem.subsubitem,但都必須遵循這個原則。但對于導入后,在使用時就沒有這種限制了,點的左邊可以是包,模塊,函數,類(它們都可以用點的方式調用自己的屬性)。
2、import導入文件時,產生名稱空間中的名字來源于文件,import 包,產生的名稱空間的名字同樣來源于文件,即包下的__init__.py,導入包本質就是在導入該文件
3、包A和包B下有同名模塊也不會沖突,如A.a與B.a來自倆個命名空間
包的使用
示例文件 glance/ #Top-level package ├── __init__.py #Initialize the glance package ├── api #Subpackage for api │ ├── __init__.py│ ├── policy.py│ └── versions.py├── cmd #Subpackage for cmd │ ├── __init__.py│ └── manage.py└── db #Subpackage for db ├── __init__.py└── models.py包的使用之import
import glance.db.models glance.db.models.register_models('mysql')ps:單獨導入包名稱時不會導入包中所有包含的所有子模塊
包的使用之from ... import ...
ps:需要注意的是from后import導入的模塊,必須是明確的一個不能帶點,否則會有語法錯誤,如:from a import b.c是錯誤語法
from glance.db import models models.register_models('mysql')from glance.db.models import register_models register_models('mysql')from glance.api import *
此處是想從包api中導入所有,實際上該語句只會導入包api下__init__.py文件中定義的名字,可以在這個文件中定義__all___:
#在__init__.py中定義 x=10def func(): print('from api.__init.py')__all__=['x','func','policy']此時位于glance同級的文件中執行from glance.api import *就導入__all__中的內容絕對導入與相對導入
絕對導入:以glance作為起始
from glance.cmd import manage manage.main()相對導入:用.或者..的方式最為起始(只能在一個包中使用,不能用于不同目錄內)
from ..cmd import manage manage.main()總結
絕對導入: 以執行文件的sys.path為起始點開始導入,稱之為絕對導入
優點: 執行文件與被導入的模塊中都可以使用
缺點: 所有導入都是以sys.path為起始點,導入麻煩
相對導入: 參照當前所在文件的文件夾為起始開始查找,稱之為相對導入
符號: .代表當前所在文件的文件加,..代表上一級文件夾,...代表上一級的上一級文件夾
優點: 導入更加簡單
缺點: 只能在導入包中的模塊時才能使用
注意:
1. 相對導入只能用于包內部模塊之間的相互導入,導入者與被導入者都必須存在于一個包內
2. attempted relative import beyond top-level package # 試圖在頂級包之外使用相對導入是錯誤的,言外之意,必須在頂級包內使用相對導入,每增加一個.代表跳到上一級文件夾,而上一級不應該超出頂級包
ps:包以及包所包含的模塊都是用來被導入的,而不是被直接執行的,而環境變量都是以執行文件為準的
開發規范
簡單示例
#===============>star.py import sys,os BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR)from core import srcif __name__ == '__main__': src.run() #===============>settings.py import osBASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DB_PATH=os.path.join(BASE_DIR,'db','db.json') LOG_PATH=os.path.join(BASE_DIR,'log','access.log') LOGIN_TIMEOUT=5""" logging配置 """ # 定義三種日志輸出格式 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \ '[%(levelname)s][%(message)s]' #其中name為getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'# log配置字典 LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到終端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': LOG_PATH, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, 'encoding': 'utf-8', # 日志文件的編碼,再也不用擔心中文log亂碼了 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '': { 'handlers': ['default', 'console'], # 這里把上面定義的兩個handler都加上,即log數據既寫入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)傳遞 }, }, }#===============>src.py from conf import settings from lib import common import timelogger=common.get_logger(__name__)current_user={'user':None,'login_time':None,'timeout':int(settings.LOGIN_TIMEOUT)} def auth(func): def wrapper(*args,**kwargs): if current_user['user']: interval=time.time()-current_user['login_time'] if interval < current_user['timeout']: return func(*args,**kwargs) name = input('name>>: ') password = input('password>>: ') db=common.conn_db() if db.get(name): if password == db.get(name).get('password'): logger.info('登錄成功') current_user['user']=name current_user['login_time']=time.time() return func(*args,**kwargs) else: logger.error('用戶名不存在')return wrapper@auth def buy(): print('buy...')@auth def run():print(''' 購物 查看余額 轉賬 ''') while True: choice = input('>>: ').strip() if not choice:continue if choice == '1': buy()#===============>db.json {"egon": {"password": "123", "money": 3000}, "alex": {"password": "alex3714", "money": 30000}, "wsb": {"password": "3714", "money": 20000}}#===============>common.py from conf import settings import logging import logging.config import jsondef get_logger(name): logging.config.dictConfig(settings.LOGGING_DIC) # 導入上面定義的logging配置 logger = logging.getLogger(name) # 生成一個log實例 return loggerdef conn_db(): db_path=settings.DB_PATH dic=json.load(open(db_path,'r',encoding='utf-8')) return dic#===============>access.log [2017-10-21 19:08:20,285][MainThread:10900][task_id:core.src][src.py:19][INFO][登錄成功] [2017-10-21 19:08:32,206][MainThread:10900][task_id:core.src][src.py:19][INFO][登錄成功] [2017-10-21 19:08:37,166][MainThread:10900][task_id:core.src][src.py:24][ERROR][用戶名不存在] [2017-10-21 19:08:39,535][MainThread:10900][task_id:core.src][src.py:24][ERROR][用戶名不存在] [2017-10-21 19:08:40,797][MainThread:10900][task_id:core.src][src.py:24][ERROR][用戶名不存在] [2017-10-21 19:08:47,093][MainThread:10900][task_id:core.src][src.py:24][ERROR][用戶名不存在] [2017-10-21 19:09:01,997][MainThread:10900][task_id:core.src][src.py:19][INFO][登錄成功] [2017-10-21 19:09:05,781][MainThread:10900][task_id:core.src][src.py:24][ERROR][用戶名不存在] [2017-10-21 19:09:29,878][MainThread:8812][task_id:core.src][src.py:19][INFO][登錄成功] [2017-10-21 19:09:54,117][MainThread:9884][task_id:core.src][src.py:19][INFO][登錄成功]?
轉載于:https://www.cnblogs.com/dragon-123/p/10295149.html
總結
以上是生活随笔為你收集整理的python 模块与包的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode10.正则表达式匹配 J
- 下一篇: Mysql8.0 3306端口无法远程连