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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章