爲啥要寫這個東西?
最近在寫一個Python版本的web漏洞掃描器,開發途中遇到不少坑(python語言特性和第三方庫)。記錄一下以免下次再踩。
在開發的過程中大量使用了asyncio庫,庫中多個函數的用法參見這篇博客,協程的原理可以閱讀這篇博客,本文對有趣的幾個記錄一下。
目錄
1. isinstance 和 type 的區別:
isinstance()
在判斷對象時考慮繼承關係,即子類歸屬於父類,此時返回值爲True。
type()
不考慮繼承關係,直接檢測對象的類型,這在集合類按需添加元素時會經常碰到。
2. py3的zip對象:
zip()
函數的返回值在Python3中變成了一個對象,該對象很好用,可以用list()
、dict()
等轉換成對應的結構。
值得注意的是在轉成字典、集合等結構時key值的唯一性(或者稱之碰撞),特別是在存儲一些看似零重複的數據時。
3.裝飾器的寫法
參考閱讀:https://www.jianshu.com/p/4c588eec1be1
派中的裝飾器其實是AOP(Aspect Oriented Programming)思想的體現,手寫裝飾器的方式如下:
無參形式
## 裝飾器定義
def test_decorate(func):
print ("welcome to learn AOP.")
def wrapper():
func()
return wrapper
## 爲某函數添加裝飾器
@test_decorate
def hello():
print("hello everybody!")
## 調用效果
hello()
## 輸出:
## welcome to learn AOP.
## hello everybody!
對被修飾函數傳參
def test_decorate(func):
print ("welcome to learn AOP.")
## 在這裏爲被包裹函數傳參
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
@test_decorate
def hello(firstName, lastName):
print("hello everybody! I'm "+firstName+" "+lastName)
## 調用效果
hello("James","Bond")
## 輸出:
## welcome to learn AOP.
## hello everybody! I'm James Bond
向裝飾器自身傳參
def test2_decorate(arg1, arg2):
print ("welcome to learn AOP.")
print ("Here're two decorator params: {", arg1, ", ", arg2, "}")
def outerWrapper(func):
def wapper(*args,**kwargs):
func(*args, **kwargs)
return wapper
return outerWrapper
## 注意,在添加裝飾器時傳入參數
@test2_decorate("acfun","bilibili")
def hello():
print("hello everybody!")
## 調用效果
hello()
## 輸出:
## welcome to learn AOP.
## Here're two decorator params: { acfun , bilibili }
## hello everybody!
說明: 裝飾器帶參數的情形常見於一些需要鑑權的場景——通過入參決定是否調用被包裹函數等。觀察可發現第三種寫法實際是在第二種寫法外面又封裝了一層,並將原來的裝飾函數作爲最終結果返回。也就是說這個新的裝飾器裏在處理了自己的參數後,又定義了一個第二種寫法的裝飾器,並將結果返回給自己。(粗淺認識,有待進一步確認)
4.包引入的問題
在py中經常會碰到不同級目錄間的import問題。主要有同級目錄、下級目錄、上級目錄見的調用。
問題的根源sys.path
首先說明,py尋找模塊(也就是.py
文件)的路徑是由sys.path
決定的。它的值是一個列表,默認包含模塊所在目錄及第三方包的目錄。舉個實例,如下圖的項目路徑:
Cat.py
中只是簡單地輸出默認路徑:
import sys
if __name__ == "__main__":
print(sys.path)
當我們用cmd切到Cat.py
所在目錄,運行得到結果:(注意這裏使用的虛擬目錄venv的python.exe)
['F:\\PycharmProjects\\NewTest\\Outer',
'C:\\Program Files\\Python37\\python37.zip',
'C:\\Program Files\\Python37\\DLLs',
'C:\\Program Files\\Python37\\lib',
'C:\\Program Files\\Python37',
'F:\\PycharmProjects\\NewTest\\venv',
'F:\\PycharmProjects\\NewTest\\venv\\lib\\site-packages',
'F:\\PycharmProjects\\NewTest\\venv\\lib\\site-packages\\setuptools-39.1.0-py3.7.egg',
'F:\\PycharmProjects\\NewTest\\venv\\lib\\site-packages\\pip-10.0.1-py3.7.egg']
如果直接用pycharm的運行按鈕,結果可能會不同:
['F:\\PycharmProjects\\NewTest\\Outer',
'F:\\PycharmProjects\\NewTest',
'C:\\Program Files\\Python37\\python37.zip',
'C:\\Program Files\\Python37\\DLLs',
'C:\\Program Files\\Python37\\lib',
'C:\\Program Files\\Python37',
'F:\\PycharmProjects\\NewTest\\venv',
'F:\\PycharmProjects\\NewTest\\venv\\lib\\site-packages',
'F:\\PycharmProjects\\NewTest\\venv\\lib\\site-packages\\setuptools-39.1.0-py3.7.egg',
'F:\\PycharmProjects\\NewTest\\venv\\lib\\site-packages\\pip-10.0.1-py3.7.egg',
'E:\\JetBrains\\PyCharm 2018.1.6\\helpers\\pycharm_matplotlib_backend']
這是因爲pycharm的運行配置文件會默認爲你添加path:
包的概念
在派中,當一個目錄包含__init__.py
文件時,它就被視作一個包,可以被import。所以在當前目錄下,如果要引入的文件在dirA/dirB/dirC/D.py
目錄中,則可以在dirA
、dirB
、dirC
下分別添加__init__.py
文件,使之成爲包,就可以用這種寫法引入:
import dirA.dirB.dirC.D
處理方式
對於同級目錄下模塊:
import moduleB
from moduleB import func
對於下級目錄模塊,推薦使用將下級目錄設置成包再引入,當然,直接用sys.path.append()
也可以。
import dirA.moduleC
from dirA import moduleC
對於上級目錄模塊,一般都要往py系統環境中加路徑。以上面的工程項目爲例,Cat.py
想添加上級目錄MainTest.py
中的hello()
函數,可以有以下方式:
import sys
## 方式一、添加相對路徑
sys.path.append('../')
## 方拾二、添加絕對路徑
## sys.path.append('F:/PycharmProjects/NewTest')
## 方式三、使用os模塊
## import os
## absPath = os.path.abspath(os.path.dirname(__file__),os.path.pardir)
## sys.path.append(absPath)
from MainTest import hello
if __name__ == "__main__":
hello()
方式三的寫法更通用,不用將項目路徑硬編碼了。於是引入平行目錄下模塊,也可以利用這種方式了。就比如Cat.py
想添加平行目錄Inner/Mimi.py
中的hello()
函數,在環境添加了上級目錄的前提下可以直接引用:
import Inner.Mimi.hello
from Inner.Mimi import hello
深層次探討
可能是我菜,但想說py的引入簡直是災難。在一個大中型項目中,什麼時候使用相對引入/絕對引入、py2與py3的區別往往搞得人頭疼。這篇博文https://zhuanlan.zhihu.com/p/63143493總結了一些引入的經驗,我覺得不如整理一套可行的工程規範。在實施時儘可能參照規範建立項目:
項目名
| -> 包名稱(同項目名)
| -> __init__.py
| -> 內部包1
| -> __init__.py
| -> A.py
| -> B.py
| -> 內部包2
| -> __init__.py
| -> C.py
| -> 內部包3
| -> __init__.py
| -> D.py
| -> 測試文件夾
| -> test.py
| -> 入口運行文件(main.py)
如圖中的項目結構,規範如下:
- 運行代碼即通過main.py執行,而且不是以模塊的形式(
python -m 包名.main
)。如果使用模塊運行的方式,需在sys.path
中添加項目所在目錄或切到該目錄執行。 - 所有py文件都是用絕對引入的方式,如下示。這樣的話對每個模塊的單獨測試放入測試文件夾,而不是在模塊下直接運行(一般直接運行會報錯,畢竟找不到path)。
## 以A.py爲例
from 項目名包.內部包2.內部包3 import D
D.func()