Python踩坑之路

爲啥要寫這個東西?
最近在寫一個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目錄中,則可以在dirAdirBdirC下分別添加__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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章