为啥要写这个东西?
最近在写一个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()