快乐学习
前程无忧、中华英才非你莫属!

Pandas之read_excel函数的重要性

zhangtongle阅读(93)

1、普通使用


excel_path = r'某公司销售数据-全国订单明细.xls' # Excel 数据路径
sales_data = pd.read_excel(excel_path) # 返回的DataFrame
sales_data.head(1) # 使用head 获取前1个数据

2、读取指定sheet

excel_sheet

pd.read_excel(excel_path,sheet_name='成绩表') # 读取成绩表

pd.read_excel(excel_path,sheet_name=2) # 读取全国订单明细,读取sheet的索引为2的表

3、指定表头

excel_header

把考勤表的第二行当表头

pd.read_excel(excel_path,sheet_name='考勤表',header=1)

4、只读取表中需要的列

# 通过列名
pd.read_excel(excel_path, usecols=['顾客姓名','订单数量','销售额'])[:2]
顾客姓名 订单数量 销售额
0 李鹏晨 6 261.54
1 王勇民 2 6.00
# 通过Excel 列的标识符。
pd.read_excel(excel_path, usecols="A:E")[:2]
订单号 订单日期 顾客姓名 订单等级 订单数量
0 3 2010-10-13 李鹏晨 低级 6
1 6 2012-02-20 王勇民 其它 2

5、将某列(字段)作为查询的的条件和依据)

# 将订单日期作为查询条件
df = pd.read_excel(excel_path,index_col=1,usecols="A:E")[:2]
df.loc['2011-07-15'] # 查询订单日期2011-07-15的所有数据
订单号 顾客姓名 订单等级 订单数量
订单日期
2011-07-15 32 姚文文 高级 26
2011-07-15 32 姚文文 高级 24

6、将多个字段作为查询条件

# 将订单日期和 订单等级作为查询索引(条件)
df = pd.read_excel(excel_path,index_col=[1,3],usecols="A:E")
df = df.sort_index()  # 需要将索引(查询字段)排序,要不不然会报警告

df.loc['2012-11-15','低级'].reset_index() # 查询日期和订单等级
订单日期 订单号 顾客姓名 订单数量
0 2012-11-15 13346 周雨生 44

7、跳过表中最后无用的数据

skip_footer

# 跳过工资表中的最后7行内容,不读取
pd.read_excel(excel_path,sheet_name='工资表',usecols="A:B",skipfooter=7)

8、指定Excel中的数据类型

# Excel 中salary数字列,夹杂了文字。需要转str,进一步的过滤和处理
df = pd.read_excel(excel_path,sheet_name='工资表',usecols="A:B",skipfooter=7, \
              dtype={'name': str, 'salary': str})

Python-导入模块的最佳实践

zhangtongle阅读(375)

导入模块的“最佳实践”是什么

通常请勿使用 from modulename import * 。因为这会扰乱 importer 的命名空间,且会造成未定义名称更难以被 Linter 检查出来。

请在代码文件的首部就导入模块。这样代码所需的模块就一目了然了,也不用考虑模块名是否在作用域内的问题。每行导入一个模块则增删起来会比较容易,每行导入多个模块则更节省屏幕空间。

按如下顺序导入模块就是一种好做法:

standard library modules -- e.g. sys, os, argparse, re
third-party library modules (anything installed in Python's site-packages directory) -- e.g. dateutil, requests, PIL.Image
locally developed modules

为了避免循环导入引发的问题,有时需要将模块导入语句移入函数或类的内部。Gordon McMillan 的说法如下:

当两个模块都采用 "import " 的导入形式时,循环导入是没有问题的。
但如果第 2 个模块想从第 1 个模块中取出一个名称("from module import name")并且导入处于代码的最顶层,那导入就会失败。原因是第 1 个模块中的名称还不可用,这时第 1 个模块正忙于导入第 2 个模块呢。

如果只是在一个函数中用到第 2 个模块,那这时将导入语句移入该函数内部即可。当调用到导入语句时,第 1 个模块将已经完成初始化,第 2 个模块就可以进行导入了。

如果某些模块是平台相关的,可能还需要把导入语句移出最顶级代码。这种情况下,甚至有可能无法导入文件首部的所有模块。于是在对应的平台相关代码中导入正确的模块,就是一种不错的选择。

只有为了避免循环导入问题,或有必要减少模块初始化时间时,才把导入语句移入类似函数定义内部的局部作用域。如果根据程序的执行方式,许多导入操作不是必需的,那么这种技术尤其有用。如果模块仅在某个函数中用到,可能还要将导入操作移入该函数内部。请注意,因为模块有一次初始化过程,所以第一次加载模块的代价可能会比较高,但多次加载几乎没有什么花费,代价只是进行几次字典检索而已。即使模块名超出了作用域,模块在 sys.modules 中也是可用的只有为了避免循环导入问题,或有必要减少模块初始化时间时,才把导入语句移入类似函数定义内部的局部作用域。如果根据程序的执行方式,许多导入操作不是必需的,那么这种技术尤其有用。如果模块仅在某个函数中用到,可能还要将导入操作移入该函数内部。请注意,因为模块有一次初始化过程,所以第一次加载模块的代价可能会比较高,但多次加载几乎没有什么花费,代价只是进行几次字典检索而已。即使模块名超出了作用域,模块在 sys.modules 中也是可用的。

解决循环import的方法主要有几种:

1.延迟导入(lazy import)

  即把import语句写在方法或函数里面,将它的作用域限制在局部。
  这种方法的缺点就是会有性能问题。

2.将from xxx import yyy改成import xxx;xxx.yyy来访问的形式

3.组织代码

  出现循环import的问题往往意味着代码的布局有问题。
  可以合并或者分离竞争资源。
  合并的话就是都写到一个文件里面去。
  分离的话就是把需要import的资源提取到一个第三方文件去。
  总之就是将循环变成单向。

PyWin32系列文章-在过去8小时内发现的系统事件

zhangtongle阅读(360)

Python对Eventlog的win32访问

如果您需要扫描许多服务器的事件日志或根据事件日志进行特定筛选,python的win32evtlogwin32evdlogutil库为您提供了一种有效的方法。

最重要的库是win32evtlog。使用它,您可以通过调用连接到服务器的事件日志。

实例
以下是基本调用:

logtype='System'
hand=win32evtlog.OpenEventLog(server,logtype)

这将返回一个句柄,您可以从该句柄进行调用,例如一个可以提供事件总数的句柄,或者另一个可以检查每个事件的详细信息的句柄。logtype变量设置为要查看的日志类型。默认值为:应用程序、安全性和系统。

掌握句柄后,您可以询问诸如记录数量或特定事件记录之类的信息:

total=win32evtlog.GetNumberOfEventLogRecords(hand)
flags = win32evtlog.EVENTLOG_BACKWARDS_READ|win32evtlog.EVENTLOG_SEQUENTIAL_READ
events=win32evtlog.ReadEventLog(hand,flags,0)

ReadEventLog返回一些事件对象,这些对象可能不是全部。您需要不断地检查循环,直到ReadEventLog不再返回事件。您可能注意到ReadEventLog有一个flags参数。标志(win32evtlog库中通常提供)指定如何读取事件日志。

下面是一个从ReadEventLog返回的事件中获取数据的简单循环:

for ev_obj in events:

    the_time=ev_obj.TimeGenerated.Format() #'12/23/99 15:54:09'

    evt_id=str(winerror.HRESULT_CODE(ev_obj.EventID))

    computer=str(ev_obj.ComputerName)

    cat=ev_obj.EventCategory

    seconds=date2sec(the_time)

    record=ev_obj.RecordNumber

    msg=str(win32evtlogutil.SafeFormatMessage(ev_obj, logtype))

    source=str(ev_obj.SourceName)

我们使用库win32evtlogutil获取事件的实际文本正文。要获取事件的易读日期,需要为事件对象的TimeGenerated部分指定“Format”方法。

win32evtlogutil可以方便地为我们提供事件日志消息的实际文本正文。如果您想根据关闭时间进行排序,这里有一个方便的函数,它使用python的time和regexp库将事件日志的时间格式转换为秒:

import re
import sys

import time
import traceback

import win32con
import win32evtlog
import win32evtlogutil
import winerror

def date2sec(evt_date):
    regexp = re.compile('(.*)\\s(.*)')  # store result in site
    reg_result = regexp.search(evt_date)
    time_res = reg_result.group() # 年月日时分秒
    res = time.strptime(time_res) # 由字符串转换成日期类型
    (mon, day, yr) = res.tm_mon,res.tm_mday,res.tm_year
    (hr, min, sec) = res.tm_hour,res.tm_min,res.tm_sec
    tup = yr, mon, day, hr, min, sec, 0, 0, 0
    sec = time.mktime(tup)
    return sec

flags = win32evtlog.EVENTLOG_BACKWARDS_READ | win32evtlog.EVENTLOG_SEQUENTIAL_READ

# This dict converts the event type into a human readable form
evt_dict = {win32con.EVENTLOG_AUDIT_FAILURE: 'EVENTLOG_AUDIT_FAILURE',
            win32con.EVENTLOG_AUDIT_SUCCESS: 'EVENTLOG_AUDIT_SUCCESS',
            win32con.EVENTLOG_INFORMATION_TYPE: 'EVENTLOG_INFORMATION_TYPE',
            win32con.EVENTLOG_WARNING_TYPE: 'EVENTLOG_WARNING_TYPE',
            win32con.EVENTLOG_ERROR_TYPE: 'EVENTLOG_ERROR_TYPE'}

computer = 'localhost'  # 本地localhost
logtype = 'System'
begin_sec = time.time()
begin_time = time.strftime('%H:%M:%S  ', time.localtime(begin_sec))
# open event log
hand = win32evtlog.OpenEventLog(computer, logtype)
print(logtype, ' events found in the last 8 hours since:', begin_time)

try:
    events = 1
    while events:
        events = win32evtlog.ReadEventLog(hand, flags, 0)
        for ev_obj in events:
            # check if the event is recent enough
            # only want data from last 8hrs
            the_time = ev_obj.TimeGenerated.Format()
            seconds = date2sec(the_time)
            if seconds < begin_sec - 28800: break
            # data is recent enough, so print it out
            computer = str(ev_obj.ComputerName)
            cat = str(ev_obj.EventCategory)
            src = str(ev_obj.SourceName)
            record = str(ev_obj.RecordNumber)
            evt_id = str(winerror.HRESULT_CODE(ev_obj.EventID))
            evt_type = str(evt_dict[ev_obj.EventType])
            msg = str(win32evtlogutil.SafeFormatMessage(ev_obj, logtype))
            print(':'.join([the_time, computer, src, cat, record,evt_id, evt_type, msg[0:15]]))

        if seconds < begin_sec - 28800: break

    win32evtlog.CloseEventLog(hand)

except:
    print(traceback.print_exc(sys.exc_info()))
System  events found in the last 8 hours since: 23:19:40  
Fri Dec  2 23:18:03 2022:zhangtl:Microsoft-Windows-DNS-Client:0:186338:1014:EVENTLOG_WARNING_TYPE:在没有配置的 DNS 服务器响
Fri Dec  2 23:16:28 2022:zhangtl:DCOM:0:186337:10016:EVENTLOG_ERROR_TYPE:<The descriptio
Fri Dec  2 23:14:53 2022:zhangtl:Service Control Manager:0:186336:7036:EVENTLOG_INFORMATION_TYPE:Windows Error R
Fri Dec  2 23:12:53 2022:zhangtl:Service Control Manager:0:186335:7036:EVENTLOG_INFORMATION_TYPE:Windows Error R
Fri Dec  2 23:07:13 2022:zhangtl:Service Control Manager:0:186334:7040:EVENTLOG_INFORMATION_TYPE:Background Inte
Fri Dec  2 23:06:28 2022:zhangtl:DCOM:0:186333:10016:EVENTLOG_ERROR_TYPE:<The descriptio

PyWin32系列文章-递归目录删除和特殊文件

zhangtongle阅读(335)

Python的win32访问文件属性以启用删除

有时您可能需要执行一些操作,例如删除整个目录树。Python有一些很棒的实用程序可以做到这一点,但具有特殊属性的文件通常无法删除。

为了解决这个问题,您需要使用对SetFileAttributes的win32调用作为普通文件。

C++调用如下所示:

BOOL SetFileAttributes(LPCTSTR lpFileName,DWORD dwFileAttributes);

您为它提供了两个参数,即文件名和特定属性,并返回是否成功。

相应的python调用是:int=win32api。SetFileAttributes(路径名,属性)

唯一的问题是你从哪里得到属性。它包含在非常方便的win32con模块中,特别是win32con.FILEATTRIBUTE*。您可以将文件设置为只读、存档、隐藏等。我们关注的是将其设置为正常,因此我们希望:win32con.file_ATTRIBUTE_normal

下面的示例可能很有用,但当然要小心,因为它会删除很多内容。这是一个递归函数。该示例还使用了os模块中的一些方便的函数。

实例

以下是如何删除目录树的基本示例:


import os

import win32api
import win32con

def del_dir(self, path):
    for file in os.listdir(path):
        file_or_dir = os.path.join(path, file)
        if os.path.isdir(file_or_dir) and not os.path.islink(file_or_dir):
            del_dir(file_or_dir)  # it's a directory reucursive call to function again
        else:
            try:
                os.remove(file_or_dir)  # it's a file, delete it
            except:
                # probably failed because it is not a normal file
                win32api.SetFileAttributes(file_or_dir, win32con.FILE_ATTRIBUTE_NORMAL)
                os.remove(file_or_dir)  # it's a file, delete it
        os.rmdir(path)  # delete the directory here

Pytest全栈自动化测试指南-入门

zhangtongle阅读(490)

Pytest介绍

Pytest是一个成熟的全功能Python测试工具,可以帮助您编写更好的程序。该pytest框架使编写可读测试变得容易,并且可以扩展以支持应用程序和库的复杂功能测试。pytest需要:Python 3.7+ 或 PyPy3。

Pytest特点

开箱即用.自动发现测试用例简化断言语句,统一为assert丰富的插件架构,超过 800 多个外部插件具有灵活的扩展性和方便的参数化方法

安装&使用

$ pip install  pytest
$ pytest --version
pytest 7.1.2

命名规则&断言

  1. 模块:test_.py 或_test.py。
  2. 函数:test*。
  3. 类:Test*,测试类不能有init函数。
  4. 断言:assert ,支持所有python的布尔表达式

用例执行顺序

  1. 在包含用例的项目根路径下(root-dir)执行:pytest -v
  2. 目录和py文件:按照ascii码排序方式,进行文件递归顺序收集和运行
  3. py文件中:按照代码从上到下
  4. 验证执行顺序:pytest --collect-only

命令行选项

pytest自动化测试

assert 布尔表达式

  • [x] 比较运算符:【>】【<】【<=】【>=】【==】【!=】
    例如:assert actual == expected

  • [x] 身份和成员运算符: 【is】、【is not】、【in】、【not in】
    例如: assert expected in actual

  • [x] 逻辑运算符和布尔函数:【not】【and】【or】【startswith()】
    例如 :assert not expected
    例如 :assert version.startswith("3.8")

待测student功能

# ---------------------------------student.py--------------------------------------
# 以字典的形式返回学生的姓名和成绩
def student_score():
    return {'小明': 99, '小红': 100}

# 以列表的形式返回学生的姓名
def student_name():
    return ['小明', '小红']

编写pass 与fail 用例

# ---------------------------------test_01_pass_fail.py---------------------------
# 这是通过用例
def test_pass():
    # 期望值
    expected = 1
    # 实际值
    actual = 1
    # 通过(实际值)跟(预期值)进行相等比较,相等通过,不相等报错
    assert actual == expected

# 这是失败,不通过用例
def test_fail():
    # 期望值
    expected = 2
    # 实际值
    actual = 3
    # 通过(实际值)跟(预期值)进行相等比较,相等通过,不相等报错
    assert actual == expected

# ---------------------------------test_02_datatype.py----------------------------
from btest.Day1.student import student_score, student_name

# 这是student_score的验证全数据等式用例
def test_student_score():
    # 期望值
    expected = {'小红': 100, '小明': 99}
    # 实际值
    actual = student_score()
    # 通过(实际值)跟(预期值)进行相等比较,相等通过,不相等报错
    assert actual == expected

# 这是student_score 的验证部分数据等式用例
# 验证小红是否等于100,是:pass 、否:fail
def test_student_score_other():
    # 期望值
    expected = 100
    # 实际值
    actual = student_score()['小红']
    # 通过(实际值)跟(预期值)进行相等比较,相等通过,不相等报错
    assert actual == expected

# 这是student_name 的包含用例:
# 验证小明是否在学生姓名列表中,是:pass(通过), 否:fail(失败)
def test_student_name():
    # 期望值
    expected = '小明123123'
    # 实际值
    actual = student_name()
    # 通过判断(预期数据)是否在实际数据中,在:pass,不在:fail
    assert len(actual) == 2
    assert expected in actual

多断言

# ---------------------------------test_03_many_assert.py-------------------------
# 安装: pip install pytest-assume -i https://pypi.douban.com/simple
from pytest_assume.plugin import assume

from btest.Day1.student import student_score

def test_student_score_hard():
    actual = student_score()

    with assume: # 实际数据类型是否跟预期数据类型一致。
        assert isinstance(actual, dict)

    with assume: # 实际的数据长度是否跟预期长度相同
        assert len(actual) == 3

    with assume: # 预期数据是否在实际数据内
        assert '小红123123' in actual

    with assume: # 实际数据是否等于预期数据
        assert actual['小红'] == 60

自定义异常信息

# ---------------------------------test_04_custom_fail.py-------------------------
import pytest
from pytest_assume.plugin import assume

from btest.Day1.student import student_name

def test_student_name_hard_custom_fail():
    actual = student_name()
    with assume:  # 实际的数据长度是否跟预期长度相同
        assert len(actual) == 3, \
            f"实际数据长度:{len(actual)} 与预期数据长度:{3}不相同!!"

    with assume:  # 预期数据是否在实际数据内
        assert '小红' in actual, \
            f"预期数据:{'小红'}没在实际数据中:{actual}!!"

    if '小乐' not in actual:
        pytest.fail(f"预期数据:小乐,没有在实际数据{actual}中")

处理异常

# ---------------------------------test_05_exceptions.py--------------------------
import pytest

from btest.Day1.student import student_score

# 通用try/finally 处理用例异常
def test_student_score_error():
    actual = student_score()
    try:
        actual['小红'] = actual['小红'] + '10'
    except:
        pass
    finally:
        assert '小乐' in actual, "小乐没有在学生成绩表里!!"

# pytest用例异常处理
def test_student_score_raise():
    actual = student_score()
    with pytest.raises(Exception):  # 更具可读性且不易出错。
        actual['小红'] = actual['小红'] + '10'
        # assert '小乐' in actual # 不会执行
    # 会执行
    assert '小乐' in actual, \
        f" 预期数据:小乐,没有在实际数据中{actual}!!"

# ---------------------------------test_06_get_error.py---------------------------
"""
1、获取用例的所在的模块、
2、获取用例的名称、
3、获取用例的具体错误信息。
"""

import inspect  # 从实时 Python 对象中获取有用的信息

import pytest

# python-inspect 捕获详细异常信息
def test_inspect_info():
    # 期望值为 模块名::用例名::错误信息
    expected = 'test_06_get_error::test_inspect_info::division by zero'

    with pytest.raises(Exception) as exc_info:
        c = 2 / 0

    # 获取错误文本:exc_info.value.args[0]
    # 如获取错误类型:exc_info.type
    error_info = exc_info.value.args[0]  # 获取用例具体错误信息

    module_name = inspect.getmodulename(__file__)  # 获取用例所在模块
    method_name = inspect.stack()[0][3]  # [0][3]获取用例名字,[1][4][0]获取用例名字和参数

    actual = f"{module_name}::{method_name}::{error_info}"
    assert actual == expected

# Pytest内置功能捕获异常信息
def test_get_raises(request):
    # 期望值为 模块名::用例名::错误信息
    expected = 'test_06_get_error::test_get_raises::division by zero'

    with pytest.raises(Exception) as exc_info:
        c = 2 / 0

    error_info = exc_info.value.args[0]

    module_name = request.module.__name__.split('.')[-1]  # 获取模块 包名.test_06xxx
    method_name = request.function.__name__  # 获取用例名字
    actual = f"{module_name}::{method_name}::{error_info}"
    assert actual == expected

数据类

Dataclass这个模块提供了一个类装饰器和一些函数,用于自动添加生成的 special method,例如 init() 和 repr() 到用户定义的类。 它最初描述于 PEP 557 .

对标准库的补充,称为数据类。可以将数据类视为“具有默认值的可变命名元组”

数据类使用普通的类定义语法,你可以自由地使用继承、元类、文档字符串、用户定义的方法、类工厂和其他 Python 类特性.

装饰器: https://www.ztloo.com/2021/11/06/decorators/

Python术语对照社区文档: https://docs.python.org/zh-cn/3/glossary.html?highlight=decorator

special method(特殊方法) :https://docs.python.org/zh-cn/3/glossary.html#term-special-method

PEP 557(Python 增强建议) :https://www.python.org/dev/peps/pep-0557

标准库:https://docs.python.org/zh-cn/3/library/index.html

命名元组: https://docs.python.org/zh-cn/3/library/collections.html?highlight=namedtuple

# ---------------------------------face_to_object.py------------------------------
from dataclasses import asdict, dataclass, astuple, replace

# https://docs.python.org/3/library/dataclasses.html?highlight=dataclass#module-dataclasses

@dataclass
class GirlFriend:
    # 节省了__init__函数
    name: str
    age: int
    height: int = 170  # cm
    weight: int = 50  # kg

    @classmethod
    def from_dict(cls, d):
        return GirlFriend(**d)

    def to_dict(self):
        return asdict(self)

    def to_tuple(self):
        return astuple(self)

    def update(self, **changes):
        return replace(self, **changes)

if __name__ == '__main__':
    # 把字典转行成类对象
    ym_dict = {'name': '杨幂', 'age': 18, 'height': 170, 'weight': 68}
    ym = GirlFriend.from_dict(ym_dict)
    print('我是类对象型杨幂', ym)

    # 把类对象转化成字典
    ym_dict = ym.to_dict()
    print('我是字典型杨幂:', ym_dict)

    # 把类对象转化成元组
    ym_tuple = ym.to_tuple()
    print(f'我是元组型杨幂:{ym_tuple}')

    # 把杨幂修改成刘亦菲
    lyf = ym.update(name='刘亦菲', age=22)
    print(f'我是刘亦菲:{lyf}')

自定义辅助断言函数

import pytest

from btest.Day2.face_to_object import GirlFriend

def assert_identical(gf_1, gf_2):
    if gf_1.name != gf_2.name:
        pytest.fail(f"name不匹配. {gf_1.name} != {gf_2.name}")

def test_identical():
    gf_1 = GirlFriend("刘亦菲", 18, 170, 60)
    gf_2 = GirlFriend("刘亦菲", 18, 170, 60)
    assert_identical(gf_1, gf_2)

更多案例,更多内容,请点击: http://weike.fm/KzXbA994f

python的__get__方法看这一篇就足够了

zhangtongle阅读(484)

没有get普通类的属性访问

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

if __name__ == '__main__':
    t = TestMain()
    print(t.a)

#输出
TestMain:__init__
1

如果访问一个不存在的属性:

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b) # 访问了一个不存在的属性

# 输出:
TestMain:__init__
Traceback (most recent call last):
print(t.b)
AttributeError: 'TestMain' object has no attribute 'b'

添加getattr 函数,解决上述问题

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 2

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)

# 输出:
TestMain:__init__
1
TestMain:__getattr__
2

说明:我们仍然访问了一个本来不存在的t.b,为什么这里没有报错呢,因为我们定义了getattr函数,而且让它直接返回了2,也就是说,如果定义了这个函数后,访问不存在的属性,会自动调用这个函数作为返回值。

接下来_getattribute__函数:

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 2

    def __getattribute__(self, item):
        print('TestMain:__getattribute__')
        return 3

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)

# 输出结果
TestMain:__init__
TestMain:__getattribute__
3
TestMain:__getattribute__
3

可以看到,无论是访问存在的t.a还是不存在的t.b,都访问到了getattribute这个函数,也就是说,只要定义了这个函数,那么属性的访问,都会走到这个函数里面。

我们知道只要定义了getattribute函数,就肯定执行这个函数来获取属性,这次我们增加了判断如果访问c这个属性,我们抛出异常,最后的结果是:

TestMain:__init__
TestMain:__getattribute__
3
TestMain:__getattribute__
3
TestMain:__getattribute__
TestMain:__getattr__
2

也就是说,如果getattribute抛出了AttributeError异常,那么会继续访问getattr函数的。

总结:

  1. 如果定义了getattribute,那么无论访问什么属性,都是通过这个函数获取,包括方法,t.f()这种也是访问的这个函数,此时这个函数应该放回一个方法,如果像例子中,仍然返回一个数字,你会获得一个TypeError: 'int' object is not callable错误

  2. 只要定义了getattribute方法,不管你访问一个存在的还是不存在的属性,都由这个方法返回,比如访问t.a,虽然a存在,但是只要定义了这个访问,那么就不是访问最开始的a了

  3. 如果getattribute抛出了AttributeError异常,并且定了了getattr函数,那么会调用getattr这个函数,不论这个属性到底是不是存在

  4. 也就是说属性访问的一个大致优先级是:getattribute > getattr > dict


get函数

上面说了getattributegetattr,这里单独说一下get,因为这个涉及到其它的概念,就是描述器(Descriptor)

一个类只要实现了getsetdelete中任意一个方法,我们就可以叫它描述器(descriptor)。如果只定义了get我们叫非资料描述器(non-data descriptor),如果setdelete任意一个/或者同时出现,我们叫资料描述器(data descriptor)。

首先明确一点,拥有这个方法的类,应该(也可以说是必须)产生一个实例,并且这个实例是另外一个类的类属性(注意一定是类属性,通过self的方式产生就不属于get范畴了)。

也就是说拥有这个方法的类,那么它的实例应该属于另外一个类/对象的一个属性。 直接看代码吧:


class TestDes:
    def __get__(self, instance, owner):
        print(instance, owner)
        return 'TestDes:__get__'

class TestMain:
    des = TestDes()

if __name__ == '__main__':
    t = TestMain()
    print(t.des)
    print(TestMain.des)

# 输出:
<__main__.TestMain object at 0x0000022563D5D3C8> <class '__main__.TestMain'>
TestDes:__get__
None <class '__main__.TestMain'>
TestDes:__get__

其中TestDes定义了get方法,在TestMain中,定义了一个类属性des,是TestDes的一个实例,我们访问t.des或者TestMain.des的时候访问的就是访问了TestDes的get方法。

其中,get方法的第一个参数是实际拥有者的实例,如果没有则为None,第二个参数是实际所属的类。

看一下下面的代码:

class TestDes:
    def __get__(self, instance, owner):
        print(instance, owner)
        return 'TestDes:__get__'

class TestMain:
    def __init__(self):
        self.des = TestDes()

if __name__ == '__main__':
    t = TestMain()
    print(t.des)
    # print(TestMain.des) #很明显这里会报错

我们通过init来产生了一个实例的des属性,这时候,print(t.des)访问的就不是get函数了,实际打印结果是:

<__main__.TestDes object at 0x00000165A77ECCF8>

也就是当成一个普通的实例来处理的。

非资料描述器,也就是只有get,不管是类还是实例去访问,默认都获得的是get的返回值,但是,如果中间有任何一次重新赋值,那么,这个实例获得的是新的值(对象),已经和原来的描述器完全脱离了关系
资料描述器,比如有set方法,后期通过实例对描述器进行赋值,那么访问的是set,并且永远关联起来。但是如果通过修改类属性的方式复制,那么也会被重新获取新的值(对象)。

看下面的代码:

class TestDes:
    def __get__(self, instance, owner):
        print('TestDes:__get__', instance, owner)
        return 'TestDes:__get__'

class TestMain:
    des = TestDes()

if __name__ == '__main__':
    t = TestMain()
    print(t.des)
    print(TestMain.des)

    print()

    t.des = 1
    print(t.des)
    print(TestMain.des)

    print()

    TestMain.des = 1
    print(t.des)
    print(TestMain.des)

上面是一个非资料描述器,打印结果是:

TestDes:__get__ <__main__.TestMain object at 0x000002C9BCCF0080> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__

1
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__

1
1

具体根据上面的描述行为进行分析,就可以得出结果了。

我们在看一下资料描述器:

class TestDes:
    def __get__(self, instance, owner):
        print('TestDes:__get__', instance, owner)
        return 'TestDes:__get__'

    def __set__(self, instance, value):
        print('TestDes:__set__', instance, value)

# 其它代码没有修改

打印结果如下

TestDes:__get__ <__main__.TestMain object at 0x000002140A46D390> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__

TestDes:__set__ <__main__.TestMain object at 0x000002140A46D390> 1
TestDes:__get__ <__main__.TestMain object at 0x000002140A46D390> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__

1
1

总结

  1. getattributegetattr用于实例访问属性使用,拥有get方法的类是只能其实例属于类属性的时候生效
  2. 只要有getattribute,任何属性访问都是这个的返回值,以下都是在getattribute不存在或者有AttributeError异常发生的情况下描述的
  3. 访问不存在的属性,getattr生效
  4. 访问存在的属性,如果是描述器,描述器生效
  5. 如果通过实例对描述器进行赋值操作,又有资料和非资料描述器的区分,如果定义了set,那么此方法生效,并且仍然是原始的资料描述器,否则被赋值为新对象
  6. 描述器赋值如果是通过类的属性方式赋值,而不是类的实例方式赋值,描述器失效
  7. 针对描述器的说明: 描述器是被getattribute调用的,如果重写了这个方法,将会阻止自动调用描述器,资料描述器总是覆盖了实例的dict, 非资料描述器可能覆盖实例的dict

30个常用的Python代码片段

zhangtongle阅读(538)

例子1:检查列表中的所有元素是否相等。

def all_equal(lst):
    return lst[1:] == lst[:-1]

# 使用例子
all_equal([1, 2, 3, 4, 5, 6])  # False
all_equal([1, 1, 1, 1])  # True

例子2:检查列表中所有的值是否唯一

def all_unique(lst):
    return len(lst) == len(set(lst))

# 参考案例
x = [1, 2, 3, 4, 5, 6]
y = [1, 2, 2, 3, 4, 5]
all_unique(x)  # True
all_unique(y)  # False

例子3:根据元素对应的布尔值,过滤为2组

def bifurcate(lst, filters):  # lst:列表  filter:依据的过滤内容
    return [
        [x for i, x in enumerate(lst) if filters[i] == True],
        [x for i, x in enumerate(lst) if filters[i] == False]
    ]

# 使用案例
bifurcate(['beep', 'boop', 'foo', 'bar'], [True, True, False, True])
# 输出结果: [ ['beep', 'boop', 'bar'], ['foo'] ]

例子4:根据函数将值拆分为两个组,该函数指定输入列表中的元素属于哪个组。


def bifurcate_by(lst, fn):
    return [
        [x for x in lst if fn(x)],
        [x for x in lst if not fn(x)]

bifurcate_by(['beep', 'boop', 'foo', 'bar'], lambda x: x[0] == 'b')
# 输出结果: [ ['beep', 'boop', 'bar'], ['foo'] ]

例子5:将列表分成较小的指定大小的列表。

'''
使用list()和range()创建所需的列表size。
map()在列表上使用并用给定列表的拼接填充它。最后,返回使用创建列表。
'''

from math import ceil

def chunk(lst, size):
    return list(
        map(lambda x: lst[x * size:x * size + size],
            list(range(0, ceil(len(lst) / size)))))

# 使用案例
chunk([1, 2, 3, 4, 5], 2)
# 输出结果:[[1,2],[3,4],5]

例子6:从列表中删除falsey值。

例如 使用filter()过滤掉falsey值(False,None,0和"")。


def compact(lst):
    return list(filter(bool, lst))

# 使用案例
compact([0, 1, False, 2, '', 3, 'a', 's', 34])
#  输出结果
[ 1, 2, 3, 'a', 's', 34 ]

例子7:根据给定函数对列表元素进行分组,并返回每个组中元素的数量


def count_by(arr, fn=lambda x: x):
    key = {}
    for el in map(fn, arr):
        key[el] = 0 if el not in key else key[el]
        key[el] += 1
    return key

# 使用
from math import floor
count_by([6.1, 4.2, 6.3], floor)
# 输出结果
{4: 1, 6: 2}

count_by(['one', 'two', 'three'], len)
# 输出结果
{3: 2, 5: 1}

例子7:计算列表中值的出现次数


def count_occurrences(lst, val):
    return len([x for x in lst if x == val and type(x) == type(val)])

# 使用
count_occurrences([1, 1, 2, 1, 2, 3], 1)  
# 输出结果
3

例子8:深度压扁列表


'''
使用递归。定义一个函数,spread它使用列表中的每个元素list.extend()或
其中的任何list.append()元素来展平它。
使用list.extend()空列表和spread函数来展平列表。递归地展平作为列表的每个元素。

'''

def spread(arg):
    ret = []
    for i in arg:
        if isinstance(i, list):
            ret.extend(i)
        else:
            ret.append(i)
    return ret

def deep_flatten(lst):
    result = []
    result.extend(
        spread(list(map(lambda x: deep_flatten(x) if type(x) == list else x, lst))))
    return result

deep_flatten([1, [2], [[3], 4], 5])  # [1,2,3,4,5]

例子9:返回两个iterables之间的差异。

def difference(a, b):
    _b = set(b)
    return [item for item in a if item not in _b]

difference([1, 2, 3], [1, 2, 4])  # [3]

例子10、将提供的函数应用于两个列表元素后,返回两个列表之间的差异。

def difference_by(a, b, fn):
    _b = set(map(fn, b))
    return [item for item in a if fn(item) not in _b]

from math import floor

difference_by([2.1, 1.2], [2.3, 3.4], floor)  
# [1.2]
difference_by([{'x': 2}, {'x': 1}], [{'x': 1}], lambda v: v['x']) 
# [ { x: 2 } ]

例子11、通过函数判断,列表是否满足 lambda表达式,满足的true,不满足的False。

def every(lst, fn=lambda x: not not x):
    for el in lst:
        if not fn(el):
            return False
    return True

every([4, 2, 3], lambda x: x > 1)  # True
every([2, 2, 3])  # True

例子12、返回列表中的每个第n个元素

def every_nth(lst, nth):
    return lst[1::nth]

every_nth([1, 2, 3, 4, 5, 6], 2)  # [ 2, 4, 6 ]

例子13、过滤掉列表中的非唯一值。

def filter_non_unique(lst):
    return [item for item in lst if lst.count(item) == 1]

filter_non_unique([1, 2, 2, 3, 4, 4, 5])  # [1, 3, 5]

例子14、根据给定的函数对列表的元素进行分组。

def group_by(lst, fn):
    groups = {}
    for key in list(map(fn, lst)):
        groups[key] = [item for item in lst if fn(item) == key]
    return groups

import math
group_by([6.1, 4.2, 6.3], math.floor)  
# {4: [4.2], 6: [6.1, 6.3]}

例子15、列表中是否存在重复值,


def has_duplicates(lst):
    return len(lst) != len(set(lst))

x = [1, 2, 3, 4, 5, 5]
y = [1, 2, 3, 4, 5]
has_duplicates(x)  # True
has_duplicates(y)  # False

例子16:返回列表的头部。


def head(lst):
    return lst[0]

head([1, 2, 3])  # 1

例子17:返回列表中除最后一个元素之外的所有元素。

def initial(lst):
    return lst[0:-1]

initial([1, 2, 3])
# [1,2]

例子18:初始化给定宽度和高度以及值的2D列表。

def initialize_2d_list(w, h, val=None):
    return [[val for x in range(w)] for y in range(h)]
initialize_2d_list(2, 2, 0)  # [[0,0], [0,0]]

例子19: 初始化包含指定范围内的号码,其中的列表start和end已包括其共同差step。

def initialize_list_with_range(end, start=0, step=1):
    return list(range(start, end + 1, step))

initialize_list_with_range(5)  # [0, 1, 2, 3, 4, 5]
initialize_list_with_range(7, 3)  # [3, 4, 5, 6, 7]
initialize_list_with_range(9, 0, 2)  # [0, 2, 4, 6, 8]

例子20、使用指定的值初始化并填充列表。

def initialize_list_with_values(n, val=0):
    return [val for x in range(n)]

initialize_list_with_values(5, 2)  # [2, 2, 2, 2, 2]

例子21、返回两个列表中存在的元素列表。

def intersection(a, b):
    _b = set(b)
    return [item for item in a if item in _b]

intersection([1, 2, 3], [4, 3, 2])  # [2, 3]

例子22、 将提供的函数应用于两个列表元素后,返回两个列表中存在的元素列表。


def intersection_by(a, b, fn):
    _b = set(map(fn, b))
    return [item for item in a if fn(item) in _b]

from math import floor
intersection_by([2.1, 1.2], [2.3, 3.4], floor)
# [2.1]

例子23、返回列表中的最后一个元素

def last(lst):
    return lst[-1]

last([1, 2, 3])  # 3

24、获取具有length属性的任意数量的可迭代对象或对象,并返回最长的属性

def longest_item(*args):
    return max(args, key=len)

longest_item('this', 'is', 'a', 'testcase')  # 'testcase'
longest_item([1, 2, 3], [1, 2], [1, 2, 3, 4, 5])  # [1, 2, 3, 4, 5]
longest_item([1, 2, 3], 'foobar')  # 'foobar'

25、返回n提供列表中的最大元素

def max_n(lst, n=1):
    return sorted(lst, reverse=True)[:n]

max_n([1, 2, 3])  # [3]
max_n([1, 2, 3], 2)  # [3,2]

26、返回n提供列表中的最小元素

def min_n(lst, n=1):
    return sorted(lst, reverse=False)[:n]

min_n([1, 2, 3])  # [1]
min_n([1, 2, 3], 2)  # [1,2]

27、如果提供的函数返回True列表中的至少一个元素,True否则。

def none(lst, fn=lambda x: not not x):
    for el in lst:
        if fn(el):
            return False
    return True

none([0, 1, 2, 0], lambda x: x >= 2)  # False
none([0, 0, 0])  # True

28、将指定数量的元素移动到列表的末尾。

def offset(lst, offset):
    return lst[offset:] + lst[:offset]

offset([1, 2, 3, 4, 5], 2)  # [3, 4, 5, 1, 2]
offset([1, 2, 3, 4, 5], -2)  # [4, 5, 1, 2, 3]

29、从数组中返回一个随机元素。

from random import randint

def sample(lst):
    return lst[randint(0, len(lst) - 1)]
sample([3, 7, 9, 11])  # 9

30、随机化列表值的顺序,返回一个新列表。

from copy import deepcopy
from random import randint

def shuffle(lst):
    temp_lst = deepcopy(lst)
    m = len(temp_lst)
    while (m):
        m -= 1
        i = randint(0, m)
        temp_lst[m], temp_lst[i] = temp_lst[i], temp_lst[m]
    return temp_lst

foo = [1, 2, 3]
shuffle(foo)  # [2,3,1] , foo = [1,2,3]

31 、返回两个列表中存在的元素列表。


def similarity(a, b):
    return [item for item in a if item in b]

similarity([1, 2, 3], [1, 2, 4])  # [1, 2]

python-pip包管理器详解-2

zhangtongle阅读(642)

优化1:使用国内镜像仓库

当你使用pip 安装的时候,会经常出现慢,timeout异常,这个时候咋解决,别怕非常简单:

pip install requests==2.6.0 -i https://pypi.douban.com/simple/

这里指定国内的模块仓库地址就可以解决了,这个地址我们网友叫豆瓣源。
如果初选timeout 异常。你就看一大堆的信息里面,有没有timeout关键字,如果有就在后面通过 -i 选项后面接着豆瓣源的地址,就成。

优化2:让原有的模块升级到新版本

如果你的机器上安装了requests,你在次执行安装命令没有任何效果
为了能使用上最新版本的requests, 直接添加--upgrade 升级选项。

python -m pip install --upgrade requests

优化3:条件限定安装模块的版本

如果有这样的需求,安装requests模块不能低于某个版本。或者指定的版本在仓库中不见了,该如何是好,有解决办法

python -m pip install "requests>=2.6.0"  # minimum version

当使用比较运算符例如 >, < 或其他某些可以被终端所解析的特殊字符时,包名称与版本号应当用双引号括起来。

优化4:无权时需指定用户安装模块

如果你的服务器不是自己一个人在用,有很多用户再用。并且有严格权限控制。需要把python模块安装到某个用户的下面。并且不需要root权限就能安装模块。

pip install --user poetry

# 会自动安装到C:\Users\Administrator\AppData\Roaming\Python\Python37\Scripts
#linux 通常是 ~/.local/
# Windows 用户的 %APPDATA%\Python

# 你还可以指定用户的基础目录
set PYTHONUSERBASE=c:/myappenv
pythohn -m pip install --user SomePackage

pip 异常: ensurepip

一般linux 默认是不安装pip 的,或者windows 上升级pip,以及环境变量配置问题导致,使用不了pip,解决方式

python -m ensurepip
python -m pip install --upgrade pip

优化5:本地安装第三方模块

利用镜像和缓存,快速进行本地第三方模块安装,

1、在能联网的电脑上,下载你需要的二进制安装包

pip download -r requirements.txt -d "./package" -i https://pypi.douban.com/simple/

2、在本地不能联网的机器上安装离线的二进制安装包

切换到虚拟环境,然后再进行安转:

(ztloo) D:\rd\PyJob\AutoTest\lib>pip install --no-index --find-links=./package
r requirements.txt

注明:不通过联网,即可安装。 还有搭建镜像服务、二进制web服务,这里不过多介绍。

优化6:安装被自己修改过的源码包

源码包一定要去https://pypi.org/ 去寻找对应模块的二进制包

不要去github上作者上传的,因为不知道它用什么打包的,按照他的安装方式,是不行的。
so, 他能把包传到中央仓库,说明那个才是最规范的。

下载好源码包,用解压软件解压,我推荐7-zip 解压缩软件,

解压好之后,把源码中某些文件或代码,替换成你自定义的,你要保证代码没有错误哈,
然后在用用7-ZIP压缩两次即可。
第一次压缩成tar格式,第二次再将第一次的压缩的.tar文件再次用7-ZIP压缩成gzip格式,最终得到的文件就是.tar.gz格式文件。 然后再用pip 指定压缩好的文件,进行安装即可。

pip install  pytest-html-3.1.1.tar.gz

优化7:直接使用别人编译好的whl

如果你是window 用户,我推荐你这么干。
https://www.lfd.uci.edu/~gohlke/pythonlibs/

这个网站里面,全都是编译好的windows版本的离线文件,很多,通过ctrl+F 关键字很快就能找到我们想要的包。安装起来也很简单

pip install pandas‑1.4.3‑pp38‑pypy38_pp73‑win_amd64.whl

pandas‑1.4.3‑pp38‑pypy38_pp73‑win_amd64.whl 这个就是我们从这个网站上找到下载的的padnas 的离线安装包。 把它放到你所知道的目录,然后再用pip install 进行安装。

pandas‑1.4.3: pandas 的版本。
pp38‑pypy38_pp73: 应该是支持python3.8
win_amd64: window 平台 amd64位架构的

以上,都是小编,亲测,没有太大的问题,如果遇到问题,可以呼叫小编,如果他没理你,有可能他在睡觉,你尝试用红包叫醒它。

如果你连pip 还是不肾了解:那请看看上一篇文章,pip包管理器详解-1

python-pip包管理器详解-1

zhangtongle阅读(583)

pip 是python 中的包管理器

我们在使用python第三方模块(有些语言叫包),其实叫包更为贴切些,
大家都知道在python 安装第三方模块,其实很简单:

pip install jieba

pip工具就会自动通过联网 到https://pypi.org 这个地址找到你要安装的jieab,进行下载安装到默认的位置。 你还可以指定下载哪个版本

pip install jieba==0.41

为啥要指定版本,直截了当的告诉你,因为新版本的jieba使用python 最新版编译的,你目前的python为老版本,多多少少会出现异常,你只能被逼无奈降低jieba 的版本。这就是为啥我们要指定版本。

这里值得注意的是,pip包管理器最常用的三个子命令


install: 安装模块 
uninstall : 卸载模块
freeze:以需求格式输出已安装的包

还有值得注意的是,一旦你的电脑中安装了多个python环境,例如你同时安装了python3.5,3,6 ,3,8 然后你又不知道当前的环境变量是哪个python版本。如果贸然执行pip install,就会不清楚安装到哪个版本上了。

有的时候还会出现pip 命令不是系统命令。比较稳妥的做法,比如你你只想给python3.8 版本安装requests模块,你应该通过cmd 控制台,切换到python3.8 的安装目录,在执行python -m 来安装。

cd D:\rd\py38\
python -m pip install requests==2.6.0

显示包的信息

pip show requests
D:\rd\py38>pip show requests
Name: requests
Version: 2.26.0
Summary: Python HTTP for Humans.
Home-page: https://requests.readthedocs.io
Author: Kenneth Reitz
Author-email: me@kennethreitz.org
License: Apache 2.0
Location: d:\rd\py38\lib\site-packages
Requires: certifi, charset-normalizer, idna, urllib3
Required-by: DingtalkChatbot, oss2, premailer, pytest-base-url, pytest-selenium,
 yarg

显示所有模块

D:\rd\py38>pip list
Package                           Version
--------------------------------- ----------
aliyun-python-sdk-core            2.13.36
aliyun-python-sdk-kms             2.15.0
allure-pytest                     2.9.45
allure-python-commons             2.9.45
Appium-Python-Client              1.3.0
argcomplete                       2.0.0
asgiref                           3.3.4
click                             7.1.2
colorama                          0.4.5

把所有模块导入文件中

pip freeze > requirements.txt

从文件中安装所有模块

python -m pip install -r requirements.txt

python-mitmproxy-交互式-https-代理-2

zhangtongle阅读(974)

使用代理

为啥要使用代理,是因为我们谷歌浏览器可以通过插件配置好代理,我们通过谷歌浏览器访问的所有网站请求,都会经过mitmproxy,但是我们写的脚本和shell 不会,所以需要告诉大家 如何在程序上加代理。我们程序访问的所有网站urL和接口都会经过mitmproxy-代理服务器,这样才能进行保存我们的所有记录,过滤,统计等额外操作。

curl --proxy http://127.0.0.1:8080 "http://wttr.in/Innsbruck?0" 

UI自动化加代理

options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://127.0.0.1:8080")

#你如果代理服务器多

proxy_arr = [
     '--proxy-server=http://60.168.81.177:1133',
     '--proxy-server=http://118.212.107.177:9999',
     '--proxy-server=http://36.248.132.145:9999',
     '--proxy-server=http://163.125.112.207:8118',
     '--proxy-server=http://119.134.110.69:8118',
     '--proxy-server=http://123.163.27.101:9999',
     '--proxy-server=http://1.198.73.167:9999'
 ]

proxy = random.choice(proxy_arr)  # 随机选择一个代理
chrome_options.add_argument('--proxy-server='+proxy)
browser = webdriver.Chrome(options=chrome_options)

Requests模块代理:接口自动化加代理

import requests

proxies = {
  'http': 'http://10.10.1.10:3128',
  'https': 'http://10.10.1.10:1080',
}

requests.get('http://example.org', proxies=proxies)

插件

Mitmproxy 的插件机制由一组支持任何复杂性组件的 API 组成。插件通过响应事件与 mitmproxy 交互,这允许它们连接并更改 mitmproxy 的行为

如果你想要实现,抓取特定的接口、内容、保存到你想要保存的地方。

你要使用mitmproxy,给我们的提供的插件功能,这个插件就是一个py程序

在这个py程序中,你可以定义好抓取内容,存储到哪的逻辑代码。 在开启mitmproxy 的同时指定脚本一起运行。

控制台执行命令:

mitmdump -s ./proxy_util.py

建议把mitmdump 配置到环境变量里面,要不然每次启动还得切换到mitmdump的安装目录甚是麻烦。

proxy_util.py 脚本长这样:

"""
Basic skeleton of a mitmproxy addon.

Run as follows: mitmproxy -s proxy_util.py
"""
from mitmproxy import ctx

class Counter:
    def __init__(self):
        self.num = 0

    def request(self, flow):
        self.num = self.num + 1
        ctx.log.info("We've seen %d flows" % self.num)

addons = [
    Counter()
]

"""

关于上面的代码,有几点需要注意:

Mitmproxy 获取addons全局列表的内容并将其找到的内容加载到插件机制中。
插件只是对象——在这种情况下,我们的插件是Counter.
该request方法是一个事件的例子。插件只是为他们想要处理的每个事件实现一个方法。
每个事件都有一个由传递给方法的参数组成的签名。因为request,这是 的一个实例mitmproxy.http.HTTPFlow。
最后,该ctx模块是一个holdall 模块,它公开了一组在插件中常用的标准对象。我们可以将一个ctx对象作为第一个参数传递给每个事件,但我们发现将其公开为可导入的全局对象更为简洁。在这种情况下,我们使用该ctx.log对象来进行日志记录。

"""

插件之给response添加header


"""Add an HTTP header to each response."""

class AddHeader:
    def __init__(self):
        self.num = 0

    def response(self, flow):
        self.num = self.num + 1
        flow.response.headers["count"] = str(self.num)

addons = [
    AddHeader()
]

支持的事件

这列只说明我们用到的HTTP事件,*(还包括通用事件和webscocket事件,不过我们自动化项目暂时用不到),如果需要请看官方文档,下面给出具体链接:
https://docs.mitmproxy.org/archive/v5/addons-events/

上面插件里已经包含了了两个事件

request 客户端请求事件、response服务端返回数据事件

我们用这些事件,就可以轻松拦截用户的请求,获取服务端返回的数据篡改、保存到我们想要保存的地方一些列操作,操控性,可玩性大大增加。其实核心的我们知道request和response 怎么用就行了。

具体所有1HTTP事件如下:

"""HTTP-specific events."""
import mitmproxy.http

class Events:
    def http_connect(self, flow: mitmproxy.http.HTTPFlow):
        """
            An HTTP CONNECT request was received. Setting a non 2xx response on
            the flow will return the response to the client abort the
            connection. CONNECT requests and responses do not generate the usual
            HTTP handler events. CONNECT requests are only valid in regular and
            upstream proxy modes.
        """

    def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
        """
            HTTP request headers were successfully read. At this point, the body
            is empty.
        """

    def request(self, flow: mitmproxy.http.HTTPFlow):
        """
            The full HTTP request has been read.
        """

    def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
        """
            HTTP response headers were successfully read. At this point, the body
            is empty.
        """

    def response(self, flow: mitmproxy.http.HTTPFlow):
        """
            The full HTTP response has been read.
        """

    def error(self, flow: mitmproxy.http.HTTPFlow):
        """
            An HTTP error has occurred, e.g. invalid server responses, or
            interrupted connections. This is distinct from a valid server HTTP
            error response, which is simply a response with an HTTP error code.
        """

有了这些事件,我们就可以做很多事情了,比如可以获取到用户与服务端链接状态。获取请求的header,获取到用户请求失败的状态码啊,等等。

脚本

脚本是插件的进阶,之前的插件,都需要创建个类,还得再结尾添加
addons = [Counter()],非常的麻烦,为了简化这一操作,mitmproxy 有个脚本的概念。

有时,我们想编写一个快速的脚本,而不用经历创建类的麻烦。插件机制有一个简写,它允许将一个模块作为一个整体视为一个插件对象。这让我们可以在模块范围内放置事件处理函数。例如,这是一个完整的脚本,它为每个请求添加了一个标头。

案例:截获对特定 URL 的请求并发送任意响应的示例

"""Send a reply from the proxy without sending any data to the remote server."""
from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
    if flow.request.pretty_url == "http://example.com/path":
        flow.response = http.HTTPResponse.make(
            200,  # (optional) status code
            b"Hello World",  # (optional) content
            {"Content-Type": "text/html"}  # (optional) headers
        )

API

官方文档他没有提供API脚本中flow对象包含那些属性和方法的,原文说浪漫我们自己参考源码自己看就OK了。

官方的原话是酱紫:

本文档将向您展示如何使用事件、选项 和命令构建插件。但是,这不是 API 手册,mitmproxy 源代码仍然是规范参考。从命令行探索 API 的一种简单方法是使用pydoc。例如,这里是一个显示 mitmproxy 的 HTTP 流类的 API 文档的命令:

好吧,我们使用

python -m pydoc mitmproxy.http

好了,官方文档也比较简单,也就这么多。

备注

这个还是比较重点,有的小伙伴不会编程,工作中也需要经常抓包改包,fiddler、charles 也是能够满足,如果你想要非常的灵活的抓包,假设服务端抓包、架设服务端抓包功能,一定要在linux 安装mitmproxy ,因为windows不支持在cmd窗口中,进行命令式交互,只有linux 支持。

但是因为我们的项目是需要全脚本,无手工参与的自动化过程,所以可以安装在windows。

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715
error: Sorry,暂时内容不可复制!