鹤啸九天 自律更自由,平凡不平庸 Less is More

Python技能

Notes(温馨提示):

  1. ★ 首次阅读建议浏览:导航指南
  2. 右上角工具条搜索文章,右下角二维码关注微信公众号(鹤啸九天),底栏分享、赞赏、评论
  3. ★ 转载请注明文章来源,知识点积累起来不容易,水滴石穿,绳锯木断,谢谢理解
  4. ★ 如有疑问,邮件讨论,欢迎贡献优质资料


总结

  • 【2021-1-23】python代码调用过程可视化工具,ryven

Python

  • 【2021-12-8】Python基础知识闯关游戏:CheckiO

Python简介

1989年,吉多·范罗苏姆(Guido van Rossum),外号龟叔,1956年出生,荷兰人,26岁在阿姆斯特丹大学获得数学和计算机科学硕士学位。1989年圣诞节,33岁的龟叔为打发时间,决定为当时正构思的一个新的脚本语言编写一个解释器。当时电视上非常流行一个电视剧蒙提·派森的飞行马戏团,作为这个马戏团的狂热粉丝,他以Python命名该项目,使用C进行开发,于是Python就诞生了。

Python(脚本语言)← C语言

  • Python的底层是用C语言写的,很多标准库和第三方库也都是用C写的,运行速度非常快
  • Python是纯粹的自由软件, 源代码和解释器CPython遵循 GPL(GNU General Public License)协议 。Python语法简洁清晰,特色之一是强制用空白符(white space)作为语句缩进。Python具有丰富和强大的库。它常被昵称为胶水语言,能够把用其他语言制作的各种模块(尤其是C/C++)很轻松地联结在一起。
  • 最初的Python用 C语言编写实现,又称为 CPython。一般所讨论的Python其实就是 CPython。
  • 随着编程语言的不断发展,Python 的实现方式也发生了变化,除了用 C 语言实现外,Python 还有其他的实现方式。
  • Java 语言实现的 Python 称为 JPython
  • .net 实现的 Python 称为 IronPython 等等。
  • PyPy 可能是最令人兴奋的 Python 实现,因为其目标就是将 Python 重写为 Python。在 PyPy 中,Python 解释器本身是用 Python 编写的。

更多编程语言介绍:

Python 2 vs Python 3

python2 不支持的 Python3语法,案例如下:

# ** 用法
outputs = model.generate(**inputs, max_new_tokens=20)

【2020-8-24】Python代码版本转换

2to3 -help # 帮助
2to3 -w .  #将当前整个文件夹代码从python2转到python3
# python2文件会在.py后面再加上一个后缀.bak,而新生成的python3文件使用之前python2文件的命名

安装python

工具包安装

mac下安装命令

# === brew 工具 ====
# 安装homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 搜索Python版本
brew search python3 
# 安装
brew install python@3.8

编译安装

自动编译

  • 【2020-7-3】脚本如下:
#-----自定义区------
# 下载python3
src_file='https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tgz'
file_name="${src_file##*/}"
install_dir=~/bin
#-------------------
[ -e ${file_name} ]||{
        wget ${src_file}
        echo "下载完毕..."
}&& echo "文件已存在, $file_name"
# 解压
tar zxvf ${file_name}
echo "安装目录: $install_dir"
# 安装
new_dir=${file_name%.*}
cd $new_dir
./configure --prefix=${install_dir}/python38
# 如果不设置安装目录prefix, 就会提示sudo权限
make && make install
echo "安装完毕,请设置环境变量"

编译注意:

  • 错误:No module named ‘_ssl’
  • 原因:虽然mac系统已经装了openssl包及相关的头文件,但是默认python3.7的源码包中指定的ssl头文件目录不包含mac上openssl的路径
  • 备注:mac os 没有openssl-devel这个安装包,内容都已经在openssl中了
#./configure --with-ssl # 注明ssl
# [2023-8-5] Debian 10实践,pip install命令瘫痪,编译安装时没有纳入openssl
./configure --prefix=/home/wangqiwen.at/bin --with-ssl=/usr/bin/openssl
# 另一种配置
./configure --prefix=/web-soft/Python-3.11.4 --with-openssl-rpath=auto --with-openssl=/usr/local/openssl
make
sudo make install

openssl 3.1 安装python3.11的过程记录

【2023-8-5】Python3.8 SSL模块报错 No module named ‘_ssl’, 但Python3.6.9却没有报错。 解法

  • 下载 Python 3.8.2
  • 解压的文件中找到 Python-3.8.2/Modules/Setup 文件
  • 注释掉 5行,删掉原bin文件,重新编译
  • 再次 import ssl,验证
# Socket module helper for socket(2)
_socket socketmodule.c timemodule.c

# Socket module helper for SSL support; you must comment out the other
# socket line above, and possibly edit the SSL variable:
SSL=/usr/local/ssl
_ssl _ssl.c \
       -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
       -L$(SSL)/lib -lssl -lcrypto
# -------- 再次编译 ------
./configure --prefix=/usr/local/python3.8/
make
make install

环境变量

# 设置环境变量
#vim ~/.bash_profile
echo "
alias python3='${install_dir}/python38/bin/python3.8'
alias pip3='${install_dir}/python38/bin/pip3'
" >> ~/.bash_profile
 
echo '生效'
source ~/.bash_profile

echo '修改pip源'
mkdir ~/.pip
echo "
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
[install]
trusted-host=mirrors.aliyun.com
" > ~/.pip/pip.conf
# 或者一行命令设置
#pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

Python 编译器

Python解释器可用多种语言来实现,如

  • CPython(用C编写)
  • Jython(用Java编写)
  • Iron Python(用.NET编写)
  • PyPy(用Python编写)

CPython

CPython是Python解释器的最初实现,使用最广,最多维护

从Python官方网站下载并安装好Python 3.x后,就直接获得了一个官方版本的解释器:CPython

由于CPython是高级解释语言,因此有一定局限性,并且在速度方面没有任何优势

CPython 使用一种称为引用计数的技术。

  • 每当引用对象时,Python对象的引用计数都会增加,而在取消引用该对象时则递减计数。
  • 当引用计数为零时,CPython会自动为该对象调用内存释放函数。

陷阱

  • 当大型对象树的引用计数变为零时,所有相关对象将被释放。因此,可能有很长的暂停时间,在此期间您的程序根本无法执行。
  • 引用循环: 引用计数根本不起作用
class A(object):
    pass

a = A()
a.some_property = a
del a

CPython 用循环垃圾回收器 解决

  • 从已知根(如类型对象)开始遍历内存中的所有对象。
  • 然后,标识所有可访问对象,并释放不可访问的对象,因为不再存在。

这样就解决了引用循环问题。

但是,当内存中存在大量对象时,可能会创建更明显的暂停。

PyPy 不用 引用计数,只使用第二种技术,即循环查找器

  • 定期从根开始遍历活动对象。
  • 这使PyPy比CPython 优势,不需要考虑引用计数,从而使内存管理花费的总时间少于CPython。此方法只在每个次要集合之后添加几毫秒,而不像CPython那样一次添加数百毫秒

pypy

PyPy是CPython的一种快速且功能强大的替代方案, 不改代码的情况下大大提高速度。但它也不是万能,有一些局限性

Python 程序运行不快,要切换到另一种编程语言吗?

  • 不一定。可以放弃 python.py 运行方式,转而使用 PyPy 即时编译器。
  • Python 创建者 Guido von Rossum 都建议将 PyPy 用于关键性能的 Python 程序。
  • PyPy是不是真的比Python快?

PyPy 是 Python的JIT版本,如果存在大量高耗时重复执行的代码, pypy运行速度比Python快很多.

  • PyPy JIT有开销,代码第一次执行时要做jit故比Python第一次执行慢
  • 若多次执行那么pypy执行的是都是jit后的代码,运算速度会大大提高,明显超过Python。
  • PyPy安装

PyPy 提升速度的秘诀: 「即时编译( just-in-time compilation)」,即 JIT 编译。

  • 提前编译
    • C、C ++、Swift、Haskell、Rust 等编程语言提前编译(AOT 编译)。
    • 用这些语言编写代码之后,编译器会将源代码转换成特定计算机架构可读的机器码
    • 执行程序时,执行的并不是原始源代码,而是机器码
  • 解释语言:
    • Python、JavaScript、PHP 等。
    • 与将源代码转换为机器码相比,解释过程中, 源代码是保持不变。
    • 每次运行程序时,解释器都会逐行查看代码并运行。例如,每个 Web 浏览器都内置了 JavaScript 解释器。

PyPy 不同于解释器,并不会逐行运行代码,而是先将部分代码编译成机器码。

Pypy 安装

  • 命令安装: 如下
  • 源码安装:官网下载,然后添加路径到 PATH
brew install pypy3

效率对比

  • 测试代码中, PyPy 速度大约是Python的94倍, 实测 45 倍
  • 平均而言,速度是Python的4.3
  • 参考

测试代码

import time

start_time = time.time()

total = 0
for i in range(1, 10000):
    for j in range(1, 10000):
        total += i + j

print(f"The result is {total}")

end_time = time.time()
print(f"It took {end_time-start_time:.2f} seconds to compute")

Mac OS 上实测

  • 10.44/0.23=45.4
python
# Python 3.10.10 (main, Mar 21 2023, 13:41:39) [Clang 14.0.6 ] on darwin
# Type "help", "copyright", "credits" or "license" for more information.

brew install pypy3
# 安装后直接输入pypy3即可
pypy3
# Python 3.10.13 (fc59e61cfbff, Jan 15 2024, 14:27:53)
# [PyPy 7.3.15 with GCC Apple LLVM 15.0.0 (clang-1500.1.0.2.5)] on darwin
# Type "help", "copyright", "credits" or "license" for more information.

python test.py 
# The result is 999800010000
# It took 10.44 seconds to compute
pypy3 test.py 
# The result is 999800010000
# It took 0.23 seconds to compute

PyPy的局限性

  • PyPy并非万能,并不适合所有任务。
  • 甚至可能执行速度比CPython慢得多。

不适用于

  • C扩展: C扩展模块运行速度都要比在CPython中慢得多。
    • 原因: PyPy无法优化C扩展模块,因为不受完全支持。PyPy必须模拟代码中的引用计数,使其更慢。
    • PyPy团队建议去掉CPython扩展并将其替换为纯Python版本。
    • 如果不行的话,则必须使用CPython。
    • 核心团队正在处理C扩展。有些软件包已被移植到PyPy,并且工作速度也同样快。

PyPy最适合纯Python应用程序, 长时间运行的程序

  • PyPy运行时,执行许多操作以使代码运行得更快。
  • 如果脚本本身很简单,则运行速度会低于CPython。
  • 如果长时间运行,可能会带来显著的性能提升。

PyPy不是一个完全编译型的Python实现

pip

Python工具包查询网站 pypi: Find, install and publish Python packages with the Python Package Index

直接下载文件

注意

  • 跟 Python版本匹配,不要错位。 如 Python 3.6就用pip

pip 安装

【2023-2-3】 阿里云服务器不管用,卡住了

# 下载安装脚本
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # [2023-2-3] 阿里云服务器不管用,卡住了
wget https://pip.pypa.io/en/stable/installation/#get-pip-py # 从pip官网直接下文件
wget https://bootstrap.pypa.io/get-pip.py # 安装
# 【2023-8-5】微云下载地址
https://share.weiyun.com/2Dr0n4G8
# 直接从whl包安装pip
python -m pip install --upgrade xxx # 注意:pip轮子的文件名.whl
python -m ensurepip --upgrade # 更新
# 【2023-8-5】debian系统,改用命令:
sudo apt install python3-pip
# =========
sudo python get-pip.py # python2
sudo python3 get-pip.py # python3
# mac 下安装
sudo easy_install pip
# 源码安装
wget "https://pypi.python.org/packages/source/p/pip/pip-1.5.4.tar.gz#md5=834b2904f92d46aaa333267fb1c922bb" --no-check-certificate
tar zxvf pip-1.5.4.tar.gz
python setup.py install
# 版本升级 
python -m pip install --upgrade pip==20.0.2

【2023-3-2】

  • 错误: Defaulting to user installation because normal site-packages is not writeable
  • 原因:
    • 使用错误Python版本导致: python3 -m pip install $pkg_name
    • 当前用户权限不足:
      • pip install numpy –user
      • sudo pip install numpy

配置pip源

pip源采用阿里云的pip源用于加速下载文件

  • 阿里云:http://mirrors.aliyun.com/help/pypi 在 ~/.pip/pip.conf
[global] 
index-url = http://mirrors.aliyun.com/pypi/simple/
[install] 
trusted-host=mirrors.aliyun.com
# [install] trusted-host=mirrors.aliyun.com

【2023-8-5】由于pip.conf文件填写错误([install]未换行)导致:

目前收集的比较好的镜像地址有:

  • http://pypi.v2ex.com/simple/
  • http://pypi.douban.com/simple/
  • http://mirrors.aliyun.com/pypi/simple/

pip安装时速度慢或者报超时?

解决办法:指定库地址,最好是国内

#(1)-i 指定源
sudo pip install face_recognition -i https://pypi.tuna.tsinghua.edu.cn/simple
# (2)设置超时时间
pip --default-timeout=100 install ....
#(3)新建文件pip.conf,内容:
mkdir ~/.pip
# [global]
# timeout = 6000
# index-url = http://pypi.tuna.tsinghua.edu.cn/simple/ 
# [install]
# use-mirrors = true
# mirrors = http://pypi.tuna.tsinghua.edu.cn/simple/ 
# trusted-host = pypi.tuna.tsinghua.edu.cn
pip install $p --trusted-host didiyum.sys.xiaojukeji.com -i http://didiyum.sys.xiaojukeji.com/didiyum/pip/simple/

pip 问题

【2023-8-7】pip install报错

  • ModuleNotFoundError: No module named ‘_ctypes’
  • 原因:Python3 有个内置模块 ctypes,外部函数库模块,兼容C语言的数据类型,并通过它调用Linux系统下的共享库(Shared library)

解决方法: 参考

  • (1)、安装 libffi-devel
    • centos下,可以使用命令 yum install libffi-devel -y
    • ubuntu下,可以使用命令 sudo apt-get install libffi-dev
  • (2)、重新安装python3版本即可
    • python3源码安装包下执行命令:make install,重新进行安装

错误

  • ImportError: No module named bz2
  • 分析: debian 系统上没有安装bz2工具包
  • 解法: 安装bz2后重新编译Python
sudo apt-get install libbz2-dev

pip 用法

pip命令:

  • show:显示包详细信息
  • list:list –outdate查看过期的包
  • install :
    • pip install pyecharts==0.4.1 (安装指定版本)
    • pip install --upgrade pyecharts (升级)
  • uninstall
  • search
# pip版本
pip -v
# --- python ----
import sys
#sys模块提供了一系列有关Python运行环境的变量和函数。
print(sys.version)
#sys.version用来获取Python解释程序的版本信息
# ------------
pip -h
# 从备用索引安装
pip install --index-url http://my.package.repo/simple/ SomeProject 
# 在安装期间搜索附加索引,PyPI
pip install --extra-index-url http://my.package.repo/simple SomeProject
# 指定源
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package matplotlib
# 设为默认
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 指定版本
pip install 模块名==版本号
# 安装特定的源存档文件。
pip install ./downloads/SomeProject-1.0.4.tar.gz
# whl包
pip install test.whl
# 批量安装
pip install -r requirements.txt
# 升级
pip install --upgrade package_name
pip install -U transformers==4.38.2 # [2024-3-16]
# 查看信息
pip show -f package_name
# 查看已安装的库
pip list
# 已经安装的库中,看哪些需要版本升级
pip list -o
# 把已经安装的库信息保存到到本地txt文件中
pip freeze > requirements.txt
# 验证已安装的库是否有兼容依赖问题
pip check package-name
# 将库下载到本地指定文件,保存为whl格式
pip download package_name -d "要保存的文件路径"
# 卸载
pip uninstall package_name

特殊用法

pip install "project[extra]"
pip install splinter django # 安装两个名为 splinter 和 django 的包。 
pip install splinter[django] # 安装 splinter 包的变体,其中包含对 django 的 _支持_。请注意,与 django 包本身无关,而只是由 splinter 包定义的字符串,用于启用的特定功能集

installing 对 splinter 新增支持的软件包django。方括号([])不是特定的语法,只是约定。安装名为的软件包”splinter[django]“。

解释@chetner:

  • 该命令pip install splinter django将安装两个名为splinter和的软件包djangosplinter[django]
  • 安装的一个变体splinter,其包含包支持对django。

注意,与django程序包本身无关,只是splinter程序包为启用的特定功能集定义的字符串。

【2023-5-30】pip search命令废弃

故障

(py310) TQV9MF4NXR:tree-of-thoughts bytedance$ pip install scikit-llm
# Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
# ERROR: Could not find a version that satisfies the requirement scikit-llm (from versions: none)
# ERROR: No matching distribution found for scikit-llm
(py310) TQV9MF4NXR:tree-of-thoughts bytedance$ pip search scikit-llm
# ERROR: XMLRPC request failed [code: -32500]
# RuntimeError: PyPI no longer supports 'pip search' (or XML-RPC search). Please use https://pypi.org/search (via a browser) instead. See https://warehouse.pypa.io/api-reference/xml-rpc.html#deprecated-methods for more information.

替代品

git clone https://github.com/shubhodeep9/pipsearch
cd pipsearch
python setup.py install
pip search your_pkg

Python依赖包

依赖包总结

【2020-8-23】python项目依赖包管理

  • 安装:
    • pip install -r requirements.txt
  • 自动生成requirements.txt
    • pip install pipreqs
    • pipreqs /path/to/project
  • 生成requirements.txt文件
pip freeze  #显示所有依赖
pip freeze > requirement.txt  #生成requirement.txt文件
pip install -r requirement.txt  #根据requirement.txt生成相同的环境
  • 对比分析
工具包 优点 缺点
pip freeze 包含列表完全 不相关的依赖包也会包含进来
pipreqs 只会包含项目 imports 的包 包含列表不是很完全
pip-compile 精准控制项目依赖包 需要手动操作,不方便

pip requirements

python项目必须包含一个 requirements.txt 文件,用于记录所有依赖包及其精确的版本号,以便新环境部署。

① 生成requirements文件:

  • 方法一:pip freeze
    • pip freeze > requirements.txt
    • pipfreeze 命令用于生成将当前项目的pip类库列表生成 requirements.txt 文件
  • 方法二:使用 pipreqs
    • pipreqs 用于生成 requirements.txt 文件可以根据需要导入的任何项目

为什么不使用pip的Freeze命令呢?

  • pip 的 freeze 命令保存了保存当前Python环境下所有类库包,其它包括那些没有在当前项目中使用的类库。 (如果你没有的virtualenv)。
  • pip 的 freeze 命令只保存与安装在环境python所有软件包。但有时你只想将当前项目使用的类库导出生成为 requirements.txt;

生成 requirements.txt 文件的方法有很多,上面三种方法各有优劣

名称 优点 缺点
pip freeze 包含列表完全 不相关的依赖包也会包含进来
pipreqs 只会包含项目 imports 的包 包含列表不是很完全
pip-compile 精准控制项目依赖包 需要手动操作,不方便

注:requirements.txt 写法

# ==也可以换成>=,  <=。
sed -i 's/==.*$//g' requirements.txt # 安装最新版本要去除后面的==再安装
spacy>=2.1.0,<2.2.0 # 版本范围
https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-2.1.0/en_core_web_lg-2.1.0.tar.gz#egg=en_core_web_lg # 直接从某地址下载
pymagnitude>=0.1.120 # 版本范围
# 【2023-4-12】直接使用git地址
git+https://github.com/microsoft/deepspeed.git
git+https://github.com/huggingface/transformers

更多实例见地址

② 创建环境副本,全新安装

pip install -r requirements.txt
# 安装远程机器上的requirements文件:
# 服务端搭建server:
python -m SimpleHTTPServer
# 获取:
pip install -i http://127.0.0.1:8000/ -r requirements.txt

使用方法:

pip install pipreqs # 安装
pipreqs /path/to/project # 按需导出requirements.txt
# 示例:
pipreqs mycode/recommond/ivr
# INFO: Successfully saved requirements file in mycode/recommond/ivr/requirements.txt
# 内容:只包含对应项目下用到的库
#    xgboost==0.7.post4
#    requests==2.18.4
#    numpy==1.14.2
#    pandas==0.22.0
#    config_reader==0.8
#    matplotlib==2.2.2
#    scikit_learn==0.19.1
#    tensorflow==1.8.0

参考:Python使用requirements.txt安装类库

virtualenv

(1)安装:

pip install virtualenv
#或者由于权限问题使用sudo临时提升权限
sudo pip install virtualenv
virtualenv -h #获得帮助

(2)创建:

  • 用virtualenv管理python环境
  • 生成ENV目录,包含bin(解释器)、include 和 lib(python库)文件夹
  • 运行, 会继承/usr/lib/python2.7/site-packages下的所有库, 最新版本virtualenv把把访问全局site-packages作为默认行为
virtualenv ENV  # 创建一个名为ENV的目录, 并且安装了ENV/bin/python
virtualenv --system-site-packages ENV # 继承系统python环境

(3)激活虚拟环境:

  • 当前目录下生成 ENV,进入,启动虚拟环境
virtualenv ENV  # 创建一个名为ENV的目录, 并且安装了ENV/bin/python
#virtualenv --system-site-packages ENV # 继承已有环境
cd ENV
source ./bin/activate  #激活当前virtualenv
# (ENV)➜  ENV git:(master) ✗ #注意终端发生了变化

【2020-5-14】注意:windows下没有bin目录,参考,执行方法:.\scripts\activate.bat

(4)关闭

# 关闭
deactivate
# 删除虚拟环境
rm -r venv

(5)指定python版本

  • 可以使用-p PYTHON_EXE选项在创建虚拟环境的时候指定python版本
virtualenv venv --python=python3.5 # 指定python 3.5
#创建python2.7虚拟环境
virtualenv -p /usr/bin/python2.7 ENV2.7 # 指定本地python
virtualenv -p "C:\Program Files (x86)\Python\Python27" py2 # Windows下

添加 环境变量(env),并执行,便于启动

# vim ~/.bash_profile
alias env="source ${ENV}/bin/activate"
# 执行
source ~/.bash_profile
# 启动环境
env # 进入虚拟环境

anaconda 系列

anaconda 及 miniconda(小型版)

Conda 分为 AnacondaMiniconda

  • Anaconda 是一个包含了许多常用库的集合版本
  • Miniconda精简版本(只包含conda、pip、zlib、python 及所需的包),剩余的通过 conda install command 命令自行安装

anaconda

官方下载

# mac环境下安装
#brew cask install anaconda
brew install anaconda
export PATH="/usr/local/anaconda3/bin:$PATH"

# 官方地址,https://www.anaconda.com/products/individual,上面有最新版本地址
wget https://repo.anaconda.com/archive/Anaconda3-2021.05-Linux-x86_64.sh
# 获取mincoda linux最新版
wget -c https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
# mincoda mac用户
curl -O https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh
# 清华镜像地址: https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/, 找最新下载地址
wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-5.3.1-Linux-x86_64.sh

bash Anaconda3-2019.03-Linux-x86_64.sh
# [2021-6-15]注意:直接使用以上命令会出错:from conda.cli import main ModuleNotFoundError: No module named 'conda'
# 解法:加-u
bash Anaconda3-2019.03-Linux-x86_64.sh -u 

miniconda

【2023-2-27】miniconda安装过程

  • 先后选择:enter(同意协议)→ yes(同意默认目录~/miniconda)→ yes(同意初始化)
  • 执行环境变量: source ~/.bashrc
# 下载linux版本miniconda
wget -c https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
# 安装
sh Miniconda3-latest-Linux-x86_64.sh # enter(同意协议)→ yes(同意默认目录~/miniconda)→ yes(同意初始化)
# 环境变量配置
install_dir='/mnt/bn/flow-algo-intl/wangqiwen/bin'
export PATH=${PATH}:${install_dir}/miniconda3/bin # conda 命令路径
source ${install_dir}/miniconda3/etc/profile.d/conda.sh # 【2024-3-17】避免 Ubuntu上环境激活失效问题(conda init无效)

环境设置

conda config --set auto_activate_base true # 设置conda自动启动
# 自动在 ~/.bashrc 里添加环境变量
# 如果不执行,就报错, 且无法 activate 环境
conda activate base # 提示 CondaError: Run 'conda init' before 'conda activate'
# 添加bin路径, 并执行
source ~/.bashrc/ # 或 ~/.bash_profile
# 启动conda
conda
# 创建新的Python环境
conda create -n py310 python=3.10
conda activate py310 # 激活环境
conda deactivate

安装完成之后会多几个应用

  • Anaconda Navigtor :用于管理工具包和环境的图形用户界面,后续涉及的众多管理命令也可以在 Navigator 中手工实现。
  • Jupyter notebook :基于web的交互式计算环境,可以编辑易于人们阅读的文档,用于展示数据分析的过程。
  • qtconsole :一个可执行 IPython 的仿终端图形界面程序,相比 Python Shell 界面,qtconsole 可以直接显示代码生成的图形,实现多行代码输入执行,以及内置许多有用的功能和函数。
  • spyder :一个使用Python语言、跨平台的、科学运算集成开发环境。
  • 加入环境变量
    • 安装器若提示“Do you wish the installer to prepend the Anaconda install location to PATH in your /home/<user>/.bash_profile ?,建议输入“yes”。
    • 如果输入“no”,则需要手动添加路径。添加 export PATH=”//bin:$PATH" 在 .bashrc 或者 .bash_profile 中。
  • 注意:
    • 不要擅自在bash_profile中添加alias别名!会导致虚拟环境切换后python、pip转换失效
# anaconda 环境
export PATH="~/anaconda3/bin:$PATH"
# 以下语句不要添加!
alias python='/home/wangqiwen004/anaconda3/bin/python'
alias pip='/home/wangqiwen004/anaconda3/bin/pip'
# 如果仍然失效,强制使用变量切换
sra(){
    CONDA_ROOT="~/anaconda3"
    env=$1
    conda activate $env
    export LD_LIBRARY_PATH="$CONDA_ROOT/envs/$env/lib:$LD_LIBRARY_PATH" 
    export PATH=$CONDA_ROOT/envs/$env/bin:$PATH
}
# 【2020-7-10】以上方法不支持默认环境base的切换,优化如下:
sra(){
    CONDA_ROOT="~/anaconda3"
    # 获取当前虚拟环境名称列表(不含base)
    env_list=(`conda info -e | awk '{if($1!~/#|base/)printf $1" "}'`)
    env=$1
    conda activate $env
    echo "env=$env, str=${env_list[@]}"
    # 判断是否匹配已有环境名称
    # echo "${env_list[@]}" | grep $env && echo "yes" || echo "no"
    #[[ "$1" =~ "${env_str}" ]] && echo "yes" || echo "no"
    #[[ ${env_list[@]/${env}/} != ${env_list[@]} ]] && {
    res="no"
    for i in ${env_list[@]}
    do
         [ "$i" == "$env" ] && res="yes"
    done
    [ $res == "yes" ] && {
        echo "找到目标环境$env"
        export LD_LIBRARY_PATH="$CONDA_ROOT/envs/$env/lib:$LD_LIBRARY_PATH"
        export PATH=$CONDA_ROOT/envs/$env/bin:$PATH
    }||{
        echo "启用默认环境base"
        env="base"
        export LD_LIBRARY_PATH="$CONDA_ROOT/lib:$LD_LIBRARY_PATH"
        export PATH=$CONDA_ROOT/bin:$PATH
    }
    echo "环境切换完毕: --> $env"
}
alias srd='conda deactivate'
# 激活的使用方法
sra learn
  • 注意:不要这样加!

常用命令

汇总如下:

conda --version # 查看版本

# 创建新的Python环境
conda create -n py310 python=3.10
conda activate py310 # 激活环境
conda deactivate

activate # 切换到base环境
activate py3 # 切换到py3环境
conda create -n py3 python=3 # 创建一个名为py3的环境并指定python版本为3(的最新版本,也可以是2.7、3.6等))
conda create -n py3 numpy matplotlib python=2.7 # 创建环境同时安装必要的包
conda create -n py36_tf1 python=3.6 tensorflow==1.11 # [2020-7-21]
conda create -n myenv python=3.6 -y # 免交互提示
conda create --prefix="D:\\my_python\\envs\\my_py_env"  python=3.6.3 # 自定义虚拟环境
conda create --name env_name --clone py3 # 克隆环境 py3 -> env_name
conda env list # 列出conda管理的所有环境, conda info -e 
source activate py3 # linux下激活虚拟环境conda activate,windows下为:activate py3
source deactivate # linux下关闭虚拟环境conda deactivate,windows下为:deactivate
conda list # 列出当前环境的所有包
conda list -n py3 # 列出某环境下的所有包
conda install requests #安装requests包, 同pip install
# 【2023-2-27】
conda install -q pandas # 静默安装
conda install -y pandas # 默认yes,免交互提示
conda install --yes pandas # 默认yes,免交互提示
# 或者使用环境变量
# enable yes to all in current env
conda config --env --set always_yes true
# disable it in current env
conda config --env --remove always_yes
conda install -n your_env_name [package] # 即可安装package到your_env_name中
conda remove requests #卸载requets包
conda remove -n py3 --all # 删除py3环境及下属所有包
conda remove --name py3  package_name  # 删除py3环境下某个包
# pip install openpyxl -U
# conda update openpyxl
conda update requests # 更新requests包
# conda update --all # 更新所有库
conda search pyqtgraph # 搜索包(模糊查找)
conda search --full-name pyqtgraph # 精确查找
conda env export > environment.yaml # 导出当前环境的包信息
conda env create -f environment.yaml # 用配置文件创建新的虚拟环境

下载源设置

# 添加Anaconda的TUNA镜像
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
# TUNA的help中镜像地址加有引号,需要去掉
# -------- 清华镜像 ---------
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
#Conda Forge
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
#msys2(可略)
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
#bioconda(可略)
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/
#menpo(可略)
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/
#pytorch
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
# for legacy win-64(可略)
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/peterjc123/
conda config --set show_channel_urls yes
#-------- 中科大镜像 --------
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/msys2/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/bioconda/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/menpo/
conda config --set show_channel_urls yes

#-------- 北外镜像 ---------
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/pkgs/main/
#Conda Forge
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/conda-forge/
#msys2(可略)
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/msys2/
#bioconda(可略)
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/bioconda/
#menpo(可略)
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/menpo/
#pytorch
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/pytorch/
# for legacy win-64(可略)
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/peterjc123/
# 设置搜索时显示通道地址
conda config --set show_channel_urls yes

# 【2021-6-15】直接修改文件
vim ~/.condarc

channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
ssl_verify: true

问题解决

install 速度慢

解决

  • 修改conda镜像路径
  • 执行如下命令,更换仓库径路为清华镜像路径
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
  • 在自己用户目录C:\Users<你的用户名>下生成一个文件,名字为:~/.condarc
conda config --set show_channel_urls yes
  • 修改.condarc文件为如下:
channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
show_channel_urls: true
ssl_verify: true
  • 执行完上述三步,conda的镜像路径就更换完毕,如不放心可以 conda info 查看 channel URLs 信息已经更改。

【2021-9-22】更换清华源后,依然很慢:出现卡顿/失败

  • Collecting package metadata (repodata.json)
  • 解决办法: 将.condarc中的清华源的https全部换成http,或者将代理软件(clash for windows)设置为全局代理
  • conda清除国内源:conda config –remove-key channels
channel 问题

【2020-8-22】执行 conda install flask-restplus 时,pip没问题

  • Solving environment: failed with initial frozen solve. Retrying with flexible solve
  • 解决:执行
    • conda config --add channels conda-forge
    • conda config --set channel_priority flexible

【2021-7-15】问题:

  • PackagesNotFoundError: The following packages are not available from current channels
  • 解决:conda config --add channels https://anaconda.org (缺失的源)
env 激活失败

【2023-7-21】conda activate 激活失败

  • 错误提示:
    • 【2024-3-8】 CondaError: Run ‘conda init’ before ‘conda activate’
    • CommandNotFoundError: Your shell has not been properly configured to use ‘conda activate’
  • 按照提示执行 conda init bash 并重新打开窗口依然不行

解决方法: refer

  • anaconda: source ~/anaconda3/etc/profile.d/conda.sh
  • minconda: source ~/miniconda3/etc/profile.d/conda.sh # 实践成功
# 环境变量配置
install_dir='/mnt/bn/flow-algo-intl/wangqiwen/bin'
export PATH=${PATH}:${install_dir}/miniconda3/bin # conda 命令路径
source ${install_dir}/miniconda3/etc/profile.d/conda.sh # 【2024-3-17】避免 Ubuntu上环境激活失效问题(conda init无效)
Python 3.12 问题

安装 wandb 时,出错

pip install -U byted-wandb -i https://bytedpypi.byted.org/simple

错误信息

ModuleNotFoundError: No module named ‘imp’

原因:

  • Python 3.12版本问题, imp 丢弃

解法:

  • (1) 替换 imp: import imp -> import importlib
  • (2) 使用 Python 3.11
conda 环境迁移

【2024-3-27】将conda安装目录复制到新目录后,修改路径后,用 pip install 安装软件时,依然调用旧目录

  • 更新 ~/.bashrc 里的旧目录, source 后,
conda init # 安装conda后,自动通过 conda init 命令在 ~/.bashrc 尾部追加以下代码片段
# modified      /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/condabin/conda
# no change     /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/conda
# modified      /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/conda-env
# modified      /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/activate
# modified      /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/deactivate
# no change     /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/etc/profile.d/conda.sh
# modified      /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/etc/fish/conf.d/conda.fish
# no change     /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/shell/condabin/Conda.psm1
# modified      /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/shell/condabin/conda-hook.ps1
# modified      /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/lib/python3.12/site-packages/xontrib/# conda.xsh
# modified      /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/etc/profile.d/conda.csh
# no change     /root/.bashrc

# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/etc/profile.d/conda.sh" ]; then
        . "/mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/etc/profile.d/conda.sh"
    else
        export PATH="/mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<
# 指向新路径
which conda pip python
# /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/conda
# /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/pip
# /mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/python
# 但 执行 conda 时,还是指向旧路径
conda env list
# bash: /mnt/bn/larkai-fr-gpt/wangqiwen/bin/miniconda3/bin/conda: No such file or directory

原因:

  • 当前版本下的 conda 和 pip 文件是python代码, 且第一行就指定了Python旧路径
  • 修改配置文件里的目录(CONDA_EXECONDA_PYTHON_EXE), miniconda3/etc/profile.d/conda.sh
  • vim 修改命令: %s/larkai-fr-gpt/flow-algo-cn/g
head ../../bin/miniconda3/etc/profile.d/conda.sh
# 显示内容
export CONDA_EXE='/mnt/bn/larkai-fr-gpt/wangqiwen/bin/miniconda3/bin/conda'
export _CE_M=''
export _CE_CONDA=''
export CONDA_PYTHON_EXE='/mnt/bn/larkai-fr-gpt/wangqiwen/bin/miniconda3/bin/python'
# 以上旧路径都改成新路径
#!/mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/python

# Copyright (C) 2012 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
__conda_exe() (
    "$CONDA_EXE" $_CE_M $_CE_CONDA "$@"
)

这个配置文件会影响其他文件,如: conda, pip

  • 单独修改 这两个文件没用
conda_dir="/mnt/bn/flow-algo-cn/wangqiwen/bin"
cat ${conda_dir}/miniconda3/bin/conda
cat ${conda_dir}/miniconda3/bin/pip

You will probably get the following:

# ----------------- 【conda 文件】 ----------------
#!/mnt/bn/larkai-fr-gpt/wangqiwen/bin/miniconda3/bin/python
# 改成新路径
#!/mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/python

# -*- coding: utf-8 -*-
import sys
# Before any more imports, leave cwd out of sys.path for internal 'conda shell.*' commands.
# see https://github.com/conda/conda/issues/6549
if len(sys.argv) > 1 and sys.argv[1].startswith('shell.') and sys.path and sys.path[0] == '':
    # The standard first entry in sys.path is an empty string,
    # and os.path.abspath('') expands to os.getcwd().
    del sys.path[0]

# ----------------- 【pip 文件】 ----------------
#!/mnt/bn/larkai-fr-gpt/wangqiwen/bin/miniconda3/bin/python
# 改成新路径
#!/mnt/bn/flow-algo-cn/wangqiwen/bin/miniconda3/bin/python

# -*- coding: utf-8 -*-
import re
import sys

from pip._internal.cli.main import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])

可执行文件

【2024-4-22】将 Python 文件当做 shell 命令

  • deepspeed 工具包安装完后,自动增加 shell 命令
    • deepspeed -> /usr/local/bin/deepspeed
  • 直接输入 deepspeed 即可启用 deepspeed
# cat /usr/local/bin/deepspeed

#!/usr/bin/python3

from deepspeed.launcher.runner import main

if __name__ == '__main__':
    main()

Python教程

python入门教程:

变量

搞懂Python变量原理

命名规则

变量的命名规则

  • 1、需要见名知义,通过变量名,就知道它所指向的值是什么。
  • 2、变量名可以由字母、下划线和数字组成。
  • 3、但是不能以数字开头。
  • 4、页不能以关键字重名。
  • 5、建议不要与内置函数或者是类重名,不然会覆盖原始内置函数的功能。
  • 6、被视为一种惯例,并无绝对性与强制性。
  • 7、目的是为了增加代码的可读性。

注意

  • Python中的标识符区分大小写

变量类型

数据类型可以分为数字型非数字型

  • 数字型:整型(int)、浮点数(float)、布尔(bool)、复数(complex)
  • 非数字型:字符串、列表、元组、字典

提示:

  • 在Python2.0中,整数根据保存数值的长度还分为:int(整数)、long(长整数)

传值 or 传引用

在 C/C++ 中,传值和传引用是函数参数传递的两种方式,在Python中参数是如何传递的?

  • python函数参数既不是传参,也不是传引用,而是传对象
  • Python 函数中,参数的传递本质上是一种赋值操作,而赋值操作是一种名字到对象的绑定过程

全局变量

注意

  • 引用全局变量,不需要golbal声明
  • 修改全局变量,需要使用global声明
  • 特别地,列表、字典等如果只是修改其中元素的值,可以直接使用全局变量,不需要global声明
#!/usr/bin/python
# Filename: func_global.py
def func():
    global x
    print 'x is', x
    x = 2
    print 'Changed local x to', x
x = 50
func()
print 'Value of x is', x

类型提示 typing

Python 主要卖点之一是动态类型,这一点也不会改变。

  • 2014年9月,Guido van Rossum (Python BDFL) 创建了一个Python增强提议(PEP-484),为Python添加类型提示(Type Hints)。并在一年后,于2015年9月作为Python3.5.0的一部分发布了。于是对于存在了二十五年的Python,有了一种标准方法向代码中添加类型信息
  • 仅仅提示作用:输入函数的第一个参数 first_name,输入点号(.)然后敲下 Ctrl+Space 来触发代码补全
def test(a: str) -> str:
    print(a)
    return 3

def document_frequency(term: str, corpus: str) -> tuple[int, int]:
    """
    Calculate the number of documents in a corpus that contain a
    given term
    @params : term, the term to search each document for, and corpus, a collection of
             documents. Each document should be separated by a newline.
    @returns : the number of documents in the corpus that contain the term you are
               searching for and the number of documents in the corpus
    @examples :
    >>> document_frequency("first", "This is the first document in the corpus.\\nThIs\
is the second document in the corpus.\\nTHIS is \
the third document in the corpus.")
    (1, 3)
    """
    corpus_without_punctuation = corpus.lower().translate(
        str.maketrans("", "", string.punctuation)
    )  # strip all punctuation and replace it with ''
    docs = corpus_without_punctuation.split("\n")
    term = term.lower()
    return (len([doc for doc in docs if term in doc]), len(docs))

以上类型提示方式偏弱,对于复杂类型不管用, 即便已经声明为了对应的类型,但实际上并不能反映整个列表、元组的结构

names: list = ['Germey', 'Guido'] # 并不知道元素时string类型
version: tuple = (3, 7, 4) # 并不知道元素是 int
operations: dict = {'show': False, 'sort': True}

typing 模块提供了非常 “强 “的类型支持,比如 List[str]、Tuple[int, int, int] 则可以表示由 str 类型的元素组成的列表和由 int 类型的元素组成的长度为 3 的元组。

# 强类型提示
from typing import List, Tuple, Dict
# List(列表) 是 list 的泛型
names: List[str] = ['Germey', 'Guido']
var: List[int or float] = [2, 3.5] # 多种元素类型
var: List[List[int]] = [[1, 2], [2, 3]] # 嵌套
# Tuple(元组)是 tuple 的泛型
version: Tuple[int, int, int] = (3, 7, 4)
person: Tuple[str, int, float] = ('Mike', 22, 1.75) # 多种类型
# Sequence,是 collections.abc.Sequence 的泛型,不区分是列表还是元组
def square(elements: Sequence[float]) -> List[float]:
    return [x ** 2 for x in elements]
# Dict(字典)是 dict 的泛型;Dict 用于注解返回类型
operations: Dict[str, bool] = {'show': False, 'sort': True}
# Mapping(映射)是 collections.abc.Mapping 的泛型。Mapping 用于注解参数
def size(rect: Mapping[str, int]) -> Dict[str, int]:
    return {'width': rect['width'] + 100, 'height': rect['width'] + 100}
# Set(集合)是 set 的泛型;Set 用于注解返回类型
# AbstractSet 是 collections.abc.Set 的泛型,用于注解参数。
def describe(s: AbstractSet[int]) -> Set[int]:
    return set(s)
# NoReturn,当一个方法没有返回结果时,注解它的返回类型
def hello() -> NoReturn:
    print('hello')
# Any是一种特殊的类型,可以代表所有类型,静态类型检查器的所有类型都与 Any 类型兼容,所有的无参数类型注解和返回类型注解的都会默认使用 Any 类型
def add(a):
    return a + 1

def add(a: Any) -> Any:
    return a + 1
# TypeVar 自定义兼容特定类型的变量
height = 1.75
Height = TypeVar('Height', int, float, None)
def get_height() -> Height:
    return height
# NewType 声明一些具有特殊含义的类型
Person = NewType('Person', Tuple[str, int, float])
person = Person(('Mike', 22, 1.75))
# Callable,可调用类型
print(Callable, type(add), isinstance(add, Callable)) # typing.Callable <class 'function'> True
# Union,联合类型,Union[X, Y] 代表要么是 X 类型,要么是 Y 类型。 联合类型的联合类型等价于展平后的类型
Union[Union[int, str], float] == Union[int, str, float]
Union[int] == int # 仅有一个参数的联合类型会坍缩成参数自身
Union[int, str, int] == Union[int, str] # 多余的参数会被跳过
Union[int, str] == Union[str, int] # 在比较联合类型的时候,参数顺序会被忽略

【2023-11-9】LLM 推理代码

from typing import Any, Dict, List, Union

prompts: List[str] = [
        "user: 上海有什么好玩的地方 assistant: ",
        "user: 请给我一些学习上的建议 assistant: ",
]

print(prompts)

格式化输出

Python2.6中引入了 format 进行字符串格式化,相比在字符串中用 % 的类似C的方式,更加强大方便:

a = 'I'm like a {} chasing {}.'
# 按顺序格式化字符串,'I'm like a dog chasing cars.'
a.format('dog', 'cars')
b = 'I prefer {1} {0} to {2} {0}'# 在大括号中指定参数所在位置
b.format('food', 'Chinese', 'American')

格式化输出主要方法:Python 3’s f-Strings: An Improved String Formatting Syntax

  • %:老方法, 不适宜多变量,容易混淆、错位
  • format:变量多,字符串长时,仍然不方便
  • f’{x}’
a = 3.1415
b = 'hello'
c = 'world'

print('%.1f' % a)  # 取1位小数
print('{}'.format(a))
print('{:,}'.format(a)) # 千分位逗号区分
print('{0} {1} {0}'.format(b,c))  # 打乱顺序
print('{a} {tom} {a}'.format(tom='hello',a='world'))  # 带关键字
print('%.3e' % 1.11)  # 取3位小数,用科学计数法
print('{:.2f}'.format(a))
print(f'{a:.2f}') # format的隐含模式
# 对齐方式,分别使用<、>和^三个符号表示左对齐、右对齐和居中对齐
print('{:30}'.format(b)) # 宽度30
print('{:<30}'.format(b)) # 宽度30, 左对齐
print('{:30>}'.format(b)) # 宽度30, 右对齐
print('{:^30}'.format(b)) # 宽度30, 居中
print('{:*^30}'.format(b)) # 宽度30, 居中, *填充

填充常跟对齐一起使用

  • ^、<、>分别是居中、左对齐、右对齐,后面带宽度
  • :号后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充
'{:0>8}'.format('189') #'00000189'
# >代表右对齐,>前是要填充的字符,依次输出:
# 000001
# 000019
# 000256
for i in [1, 19, 256]:
    print('The index is {:0>6d}'.format(i))
# <代表左对齐,依次输出:
# *---------
# ****------
# *******---
for x in ['*', '****', '*******']:
    progress_bar = '{:-<10}'.format(x)
    print(progress_bar)
'{:.2f}'.format(321.33345)
for x in [0.0001, 1e17, 3e-18]:
    print('{:.6f}'.format(x))   # 按照小数点后6位的浮点数格式
    print('{:.1e}'.format(x))   # 按照小数点后1位的科学记数法格式
    print ('{:g}'.format(x))    # 系统自动选择最合适的格式

其他类型:

  • b、d、o、x分别是二进制、十进制、八进制、十六进制
'{:b}'.format(17) #10001
'{:,}'.format(1234567890) #'1,234,567,890',用,号还能用来做金额的千位分隔符
template = '{name} is {age} years old.' #按变量名引用
c = template.format(name='Tom', age=8)) # Tom is 8 years old.
d = template.format(age=7, name='Jerry')# Jerry is 7 years old.

字符串的format函数可以接受不限个参数,位置可以不按顺序,可以不用或者用多次,不过2.6不能为空{},2.7才可以。

  • list和tuple可以通过“打散”成普通参数给函数,而dict可以打散成关键字参数给函数(通过和*)。
  • 所以可以轻松的传个list/tuple/dict给format函数,非常灵活。
#(1)通过关键字参数
'{name},{age}'.format(age=18,name='kzc') # 'kzc,18'
# (2)通过对象属性
class Person: 
  def __init__(self,name,age): 
    self.name,self.age = name,age 
    def __str__(self): 
      return 'This guy is {self.name},is {self.age} old'.format(self=self) 
str(Person('kzc',18)) #'This guy is kzc,is 18 old'
# (3)通过下标
p=['kzc',18]
'{0[0]},{0[1]}'.format(p) #'kzc,18'

format在生成字符串和文档的时候非常有用

f 字符串:

  • Python 3.6 新特性,更方便、更快
  • As of Python 3.6, f-strings are a great new way to format strings. Not only are they more readable, more concise, and less prone to error than other ways of formatting, but they are also faster
a = 2000
name = "Eric"
age = 74
"Hello, %s." % name # 老方法, 不适宜多变量,容易混淆、错位
"Hello, {}. You are {}.".format(name, age) # 改进:format
"Hello, {1}. You are {0}.".format(age, name)
person = {'name': 'Eric', 'age': 74}
"Hello, {name}. You are {age}.".format(name=person['name'], age=person['age'])
"Hello, {name}. You are {age}.".format(**person) # format:变量多,字符串长时,仍然不方便
print(f"{a}.wav") # 直接引用变量
f"Hello, {name}. You are {age}."
f"{2 * 37}" # 直接放表达式
def to_lowercase(input):
    return input.lower()
name = "Eric Idle"
f"{to_lowercase(name)} is funny." # 调用函数
f"{name.lower()} is funny."
# 类中调用
class Comedian:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    def __str__(self):
        return f"{self.first_name} {self.last_name} is {self.age}."

    def __repr__(self):
        return f"{self.first_name} {self.last_name} is {self.age}. Surprise!"
new_comedian = Comedian("Eric", "Idle", "74")
f"{new_comedian}" # 'Eric Idle is 74.'
f"{new_comedian!r}" # 'Eric Idle is 74. Surprise!'

name = "Eric"
profession = "comedian"
affiliation = "Monty Python"
# 多行模式
# If you don't put an f in front of each individual line, then you'll just have regular, old, garden-variety strings and not shiny, new, fancy f-strings.
# 用tuple
message = (  # 多行模式
    f"Hi {name}. "
    f"You are a {profession}. "
    f"You were in {affiliation}."
)
# 换行模式
message = f"Hi {name}. " \
          f"You are a {profession}. " \
          f"You were in {affiliation}."
message # 'Hi Eric. You are a comedian. You were in Monty Python.'
message = f"""
    Hi {name}. 
    You are a {profession}. 
    You were in {affiliation}.
"""
message # 包含换行符:'\n    Hi Eric.\n    You are a comedian.\n    You were in Monty Python.\n'

性能测试

import timeit
timeit.timeit("""name = "Eric"
age = 74
'%s is %s.' % (name, age)""", number = 10000)  # 0.00332

timeit.timeit("""name = "Eric"
age = 74
'{} is {}.'.format(name, age)""", number = 10000) # 0.00424

timeit.timeit("""name = "Eric"
age = 74
f'{name} is {age}.'""", number = 10000) # 0.00248

字符串

示例

Python中字符串相关的处理都非常方便,来看例子:

a = 'Life is short, you need Python'
a.lower()              	# 'life is short, you need Python'
a.upper()               	# 'LIFE IS SHORT, YOU NEED PYTHON'
a.count('i')            	# 2
a.find('e')             	# 从左向右查找'e',3
a.rfind('need')         	# 从右向左查找'need',19
a.replace('you', 'I')   # 'Life is short, I need Python'
tokens = a.split()    	# ['Life', 'is', 'short,', 'you', 'need', 'Python']
b = ' '.join(tokens)	# 用指定分隔符按顺序把字符串列表组合成新字符串
c = a + '\n'            	# 加了换行符,注意+用法是字符串作为序列的用法
c.rstrip()              	# 右侧去除换行符
[x for x in a]          	# 遍历每个字符并生成由所有字符按顺序构成的列表
'Python' in a   			# True

print(a.capitalize()) #将字符串首字母大写
print(a.center(20)) #将字符串a置于20个空格字符的中间
print(a.startswith('H')) #判断字符串是否以某个字母开始
print(a.startswith('h'))
print(a.endswith('n')) #判断字符串是否以某个字母结束
print(a.lower()) #将字符串统一小写
print(a.upper()) #将字符串统一大写
print(a.islower()) #判断字符串是否小写
print(a.isupper()) #判断字符串是否大写
print(a.find('l')) #查找字符串,返回第一个查找到的字符串的首字符的索引,找不到不报错返回-1
print(a.index('P')) #查找字串,返回索引,查不到报错
print(a.replace('l', 'W')) #替换所有

编码方式

常见编码介绍:

  • GB2312编码:适用于汉字处理、汉字通信等系统之间的信息交换
  • GBK编码:是汉字编码标准之一,是在 GB2312-80 标准基础上的内码扩展规范,使用了双字节编码
  • ASCII编码:是对英语字符和二进制之间的关系做的统一规定
  • Unicode编码:这是一种世界上所有字符的编码。当然了它没有规定的存储方式。
  • UTF-8编码:是 Unicode Transformation Format - 8 bit 的缩写, UTF-8 是 Unicode 的一种实现方式。它是可变长的编码方式,可以使用 1~4 个字节表示一个字符,可根据不同的符号而变化字节长度。

Python内部的字符串一般都是 Unicode编码。代码中字符串的默认编码与代码文件指定编码是一致。所以要做一些编码转换通常是要以Unicode作为中间编码进行转换的,即先将其他编码的字符串解码(decode)成 Unicode,再从 Unicode编码(encode)成另一种编码。

  • decode: 将其他编码的字符串转换成 Unicode 编码
    • name.decode(“GB2312”),表示将GB2312编码的字符串name转换成Unicode编码
  • encode: 将Unicode编码转换成其他编码的字符串
    • name.encode(”GB2312“),表示将Unicode编码的字符串name转换成GB2312编码 所以在进行编码转换的时候必须先知道 name 是那种编码,然后 decode 成 Unicode 编码,最后载 encode 成需要编码的编码。当然了,如果 name 已经就是 Unicode 编码了,那么就不需要进行 decode 进行解码转换了,直接用 encode 就可以编码成你所需要的编码。值得注意的是:对 Unicode 进行编码和对 str 进行编码都是错误的。
#-*- coding: UTF-8 -*-
# 指定编码方式

import sys

print sys.getdefaultencoding() # 获取系统默认编码

name = '你好'

name.decode(GB2312) # 将GB2312编码的字符串name转换成Unicode编码
name.encode(GB2312) # 将Unicode编码的字符串name转换成GB2312编码

bytes.decode(encoding="utf-8", errors="strict")
str.encode(encoding="utf-8", errors="strict")

errors 是 错误处理方案。

  • 默认为 ‘strict’, 意为编码错误引起一个UnicodeError。
  • 其他可能得值有 ‘ignore’, ‘replace’, ‘xmlcharrefreplace’, ‘backslashreplace’ 以及通过 codecs.register_error() 注册的任何值。

语种识别

【2024-3-4】Google的语种检测工具包 cld3

  • 支持 100 种语言
  • 浅层神经网络实现语种检测

Python版本

安装

pip install pycld3
import cld3

cld3.get_language("影響包含對氣候的變化以及自然資源的枯竭程度")
# LanguagePrediction(language='zh', probability=0.999969482421875, is_reliable=True, proportion=1.0)

错误:

汉字识别

汉字的unicode编码范围 u4e00 到 u9fa5

  • 汉字的判断
    • 汉字的unicode编码范围 u4e00 到 u9fa5
  • 数字0-9的判断
    • 数字的unicode编码范围根据全角和半角,有两个不同区域,半角数字 u0030 到 u0039,全角数字 uff10 到 uff19。
  • 字母的unicode编码根据字母大小写,以及全角和半角共有四个区域。
    • 半角大写字母:u0041 - u005a ,半角小写字母:u0061 - u007a ;
    • 全角大写字母:uff21 - uff3a , 全角小写字母:uff41 - uff5a 。
  • 非汉字和数字字母的判断
    • 判断除汉字、数字0-9、字母之外的字符
ord('。') # str -> int
chr(12290) # int -> str

def is_chinese(uchar):
    """判断一个unicode是否是汉字"""
    if uchar >= u'\u4e00' and uchar<=u'\u9fa5':
        return True
    else:
        return False

def is_number(uchar):
    """判断一个unicode是否是半角数字"""
    if uchar >= u'\u0030' and uchar<=u'\u0039':
        return True
    else:
        return False
    
def is_Qnumber(uchar):
    """判断一个unicode是否是全角数字"""
    if uchar >= u'\uff10' and uchar <= u'\uff19':
        return True
    else:
        return False

def is_other(uchar):
    """判断是否非汉字,数字和英文字符"""
    if not (is_chinese(uchar) or is_number(uchar) or is_alphabet(uchar)):
        return True
    else:
        return False

中文全角、半角

【2022-11-18】在自然语言处理过程中,全角、半角的的不一致会导致信息抽取不一致,因此需要统一。

全角半角转换说明

有规律(不含空格):

  • 全角字符unicode编码从65281~65374 (十六进制 0xFF01 ~ 0xFF5E)
  • 半角字符unicode编码从33~126 (十六进制 0x21~ 0x7E)

特例:

  • 空格比较特殊,全角为 12288(0x3000),半角为 32(0x20)
  • 除空格外,全角/半角按unicode编码排序在顺序上是对应的(半角 + 0x7e= 全角),所以可以直接通过用+-法来处理非空格数据,对空格单独处理。

注:

  1. 中文文字永远是全角,只有英文字母、数字键、符号键才有全角半角的概念,一个字母或数字占一个汉字的位置叫全角,占半个汉字的位置叫半角。
  2. 引号在中英文、全半角情况下是不同的

库函数说明

  • chr() 函数用一个范围在 range(256)内的(就是0~255)整数作参数,返回一个对应的字符。
  • unichr() 跟它一样,只不过返回的是Unicode字符。
  • ord() 函数是 chr()函数(对于8位的ASCII字符串)或 unichr()函数(对于Unicode对象)的配对函数,它以一个字符(长度为1的字符串)作为参数,返回对应的ASCII数值,或者Unicode数值。
# -*- coding: cp936 -*-
def strQ2B(ustring):
    """全角转半角"""
    rstring = ""
    for uchar in ustring:
        inside_code=ord(uchar)
        if inside_code == 12288:                              #全角空格直接转换            
            inside_code = 32 
        elif inside_code == 12290: # 句号。【2022-11-18】补充
            inside_code = 46
        elif (inside_code >= 65281 and inside_code <= 65374): #全角字符(除空格)根据关系转化
            inside_code -= 65248
        #rstring += unichr(inside_code) # python 2
        rstring += chr(inside_code) # python 3
    return rstring
    
def strB2Q(ustring):
    """半角转全角"""
    rstring = ""
    for uchar in ustring:
        inside_code=ord(uchar)
        if inside_code == 32:                                 #半角空格直接转化                  
            inside_code = 12288
        elif inside_code == 46: # 句号.【2022-11-18】补充
            inside_code = 12290
        elif inside_code >= 32 and inside_code <= 126:        #半角字符(除空格)根据关系转化
            inside_code += 65248
        #rstring += unichr(inside_code) # python 2
        rstring += chr(inside_code) # python 3
    return rstring

b = strQ2B("mn123abc博客园")                           
print b
c = strB2Q("mn123abc博客园")                           
print c

注意

  • Python2 使用 chr() 将 Ascii 值转换成对应字符,unichr() 将 Unicode 值转换成对应字符
  • Python3中该内置函数消失了!实际上是Python3中的 chr() 不仅仅支持 Ascii 转换,直接支持了更为适用的 Unicode 转换。

中文编码

Python 2.x中,str和bytes的数据类型是Byte类型的对象,但在Python 3.x中,这一点现在已经改变。

Python b 字符串由字节数据组成,这意味着代表整数的字头在0 到255 之间**。

  • ** b string 和 string的主要区别在于其数据类型
    • 普通字符串有一串Unicode字符,如UTF-16或UTF-32,而Python b字符串有字节数据类型,意味着代表0到255之间的整数的字头(也被称为八位数)。
  • 通过在Python普通字符串前面添加前缀b,将其数据类型从 字符串修改 为字节
app_string = 'Happiest Season'
print(type(app_string)) # <class 'str'>
app_string_b = b'Happiest Season'
print(type(app_string_b)) # <class 'bytes'>

print('ÑÞ') # ÑÞ
print('ÑÞ'.encode('UTF-8')) # b'\xc3\x91\xc3\x9e'
print(len('ÑÞ'.encode('UTF-8'))) # 4 --- utf8编码
print(b'\xc3\x91\xc3\x9e'.decode('UTF-8')) # ÑÞ --- 解码(字节转字符串)
print(len(b'\xc3\x91\xc3\x9e'.decode('UTF-8'))) # 2

基础数据结构

Python中的容器是异常好用且异常有用的结构, 主要有列表(list),元组(tuple),字典(dict)和集合(set)

Python中的内置数据结构(Built-in Data Structure): 列表list、元组tuple、字典dict、集合set

  • list
    • list的显著特征:
      • 列表中的每个元素都可变的,意味着可以对每个元素进行修改和删除;
      • 列表是有序的,每个元素的位置是确定的,可以用索引去访问每个元素;
      • 列表中的元素可以是Python中的任何对象;
      • 可以为任意对象就意味着元素可以是字符串、整数、元组、也可以是list等Python中的对象。
      • Python中包含6中內建的序列:列表,元组,字符串、Unicode字符串、buffer对象和xrange对象
  • tuple
    • 元组Tuple,用法与List类似,但Tuple一经初始化,就不能修改,没有List中的append(), insert(), pop()等修改的方法,只能对元素进行查询
  • dict
    • 字典dictionary全称这个概念就是基于现实生活中的字典原型,生活中的使用名称-内容对数据进行构建,Python中使用键(key)-值(value)存储,也就是java、C++中的map。
    • 字典中的数据必须以键值对的形式出现,即k,v:
      • key:必须是可哈希的值,比如intmstring,float,tuple,但是,list,set,dict不行 ; value:任何值
    • 键不可重复,值可重复,键若重复字典中只会记该键对应的最后一个值
    • 字典中键(key)是不可变的,何为不可变对象,不能进行修改;而值(value)是可以修改的,可以是任何对象。在dict中是根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。
  • set
    • 集合更接近数学上集合的概念。集合中每个元素都是无序的、不重复的任意对象。可以通过集合去判断数据的从属关系,也可以通过集合把数据结构中重复的元素减掉。集合可做集合运算,可添加和删除元素。
    • 集合内数据无序,即无法使用索引和分片, 集合内部数据元素具有唯一性,可以用来排除重复数据, 集合内的数据:str,int,float,tuple,冰冻集合等,即内部只能放置可哈希数据
    • frozen set:冰冻集合是不可以进行任何修改的集合
  • frozenset 是一种特殊集合
    • 常用方法
      • intersection:交集
      • difference:差集
      • union:并集
      • issubset:检查一个集合是否为另一个子集
      • issuperset:检查一个集合是否为另一个超集

list 列表

列表的基本操作有访问,增加,删除,和拼接

a.pop()             # 把最后一个值4从列表中移除并作为pop的返回值
a.append(5)         # 末尾插入值,[1, 2, 3, 5]
a.extend([34, 37])  # 扩充列表
a.index(2)          # 找到第一个2所在的位置,也就是1
a[2]                # 取下标,也就是位置在2的值,也就是第三个值3
a += [4, 3, 2]      # 拼接,[1, 2, 3, 5, 4, 3, 2]
a.insert(1, 0)      # 在下标为1处插入元素0,[1, 0, 2, 3, 5, 4, 3, 2]
x.insert(6,6666)
a.remove(2)         # 移除第一个2,[1, 0, 3, 5, 4, 3, 2]
del(x[3]) #字符串删除操作,小心使用,不要在遍历时改变list结构
a.reverse()         # 倒序,a变为[2, 3, 4, 5, 3, 0, 1]
x.clear()           # 清空列表
x.copy()            # 复制
a[3] = 9            # 指定下标处赋值,[2, 3, 4, 9, 3, 0, 1]
b = a[2:5]          # 取下标2开始到5之前的子序列,[4, 9, 3]
c = a[2:-2]         # 下标也可以倒着数,方便算不过来的人,[4, 9, 3]
d = a[2:]           # 取下标2开始到结尾的子序列,[4, 9, 3, 0, 1]
e = a[:5]           # 取开始到下标5之前的子序列,[2, 3, 4, 9, 3]
f = a[:]            # 取从开头到最后的整个子序列,相当于值拷贝,[2, 3, 4, 9, 3, 0, 1]
a[2:-2] = [1, 2, 3] # 赋值也可以按照一段来,[2, 3, 1, 2, 3, 0, 1]
g = a[::-1]	    # 也是倒序,通过slicing实现并赋值,效率略低于reverse()
a.sort()
print(a)            # 列表内排序,a变为[0, 1, 1, 2, 2, 3, 3]
2017-9-5
c = [-10,-5,0,5,3,10,15,-20,25]
print(c.index(min(c)))  # 返回最小值
print(c.index(max(c))) # 返回最大值
print(x.count(23)) #统计元素出现个数

# 排序
c = sorted(a, reverse=True)

tuple 元组

元组和列表有很多相似的地方,最大的区别在于不可变

  • 还有如果初始化只包含一个元素的tuple和列表不一样,因为语法必须明确,所以必须在元素后加上逗号。
  • 另外直接用逗号分隔多个元素赋值默认是个tuple,这在函数多返回值的时候很好用:
a = (1, 2)
b = tuple(['3', 4]) # 也可以从列表初始化
c = (5,)
print(c)            # (5,)
d = (6)
print(d)            # 6
e = 3, 4, 5
print(e)            # (3, 4, 5)

dict 字典

字典是一种非常常见的“键-值”(key-value)映射结构

  • 键无重复,一个键不能对应多个值,不过多个键可以指向一个值

重要方法

  • keys():获取所有键
  • values():获取所有值
  • items():获取所有键值对

常见操作

a = {'Tom': 8, 'Jerry': 7}
print(a['Tom'])             # 8
b = dict(Tom=8, Jerry=7)    # 一种字符串作为键更方便的初始化方式
print(b['Tom'])             # 8
if 'Jerry' in a:            # 判断'Jerry'是否在keys里面
    print(a['Jerry'])        # 7
print(a.get('Spike'))       # None,通过get获得值,即使键不存在也不会报异常
a['Spike'] = 10
a['Tyke'] = 3
a.update({'Tuffy': 2, 'Mammy Two Shoes': 42})
print(a.values())   # dict_values([8, 2, 3, 7, 10, 42])
print(a.pop('Mammy Two Shoes'))     # 移除'Mammy Two Shoes'的键值对,并返回42
print(a.keys())     # dict_keys(['Tom', 'Tuffy', 'Tyke', 'Jerry', 'Spike']) 

b = a.items()
print(b)  # [('Tuffy', 2), ('Spike', 10), ('Tom', 8), ('Tyke', 3), ('Jerry', 7)]
from operator import itemgetter
c = sorted(a.items(), key=itemgetter(1))
print(c)  # [('Tuffy', 2), ('Tyke', 3), ('Jerry', 7), ('Tom', 8), ('Spike', 10)]
d = sorted(a.iteritems(), key=itemgetter(1))
print(d)  # [('Tuffy', 2), ('Tyke', 3), ('Jerry', 7), ('Tom', 8), ('Spike', 10)]
e = sorted(a)
print(e)  # 只对键排序,['Jerry', 'Spike', 'Tom', 'Tuffy', 'Tyke']

dict 有序输出

dic = {'t':2, 'r':1, 'e':3}
# 用sorted函数的key= 参数排序: 
# 按照key进行排序 
print(sorted(dic.items(), key=lambda d: d[0])) # [('e', 3), ('r', 1), ('t', 2)]
# 按照value进行排序 
print(sorted(dic.items(), key=lambda d: d[1])) # [('r', 1), ('t', 2), ('e', 3)]

经验:流式计算均值

【2023-7-27】读入元素过程中,同时计算均值

a = {'x':3, 'y':7, 'z':2}
score = 0
for i, k in enumerate(a):
    score = (score * i + a[k])/(i+1)
    print(f"第{i+1}轮: 当前元素: {k}, 当前均值: {score}")
# 第1轮: 当前元素: x, 当前均值: 3.0
# 第2轮: 当前元素: y, 当前均值: 5.0
# 第3轮: 当前元素: z, 当前均值: 4.0

set 集合

集合是一种很有用的数学操作,比如列表去重,或是理清两组数据之间的关系,集合的操作符和位操作符有交集

set是一个无序不重复的元素集合。

  • 集合对象是一组无序排列的可哈希的值,集合成员可以做字典中的键。
  • 集合支持用 in 和 not in 操作符检查成员,由 len() 内建函数得到集合的基数(大小), 用 for 循环迭代集合的成员。
  • 但是因为集合本身是无序的,不可以为集合创建索引或执行切片(slice)操作,也没有键(keys)可用来获取集合中元素的值。

set和dict一样,只是没有value,相当于dict的key集合,由于dict的key是不重复的,且key是不可变对象因此set也有如下特性:

  • 不重复
  • 元素为不可变对象
A = set([1, 2, 3, 4])
B = {3, 4, 5, 6}
C = set([1, 1, 2, 2, 2, 3, 3, 3, 3])
print(C)        # 集合的去重效果,set([1, 2, 3])
print(A | B)    # 求并集,set([1, 2, 3, 4, 5, 6])
print(A & B)    # 求交集,set([3, 4])
print(A - B)    # 求差集,属于A但不属于B的,set([1, 2])
print(B - A)    # 求差集,属于B但不属于A的,set([5, 6])
print(A ^ B)    # 求对称差集,相当于(A-B)|(B-A),set([1, 2, 5, 6])

# ===== 比较 ======
se = {11, 22, 33}
be = {22, 55}
temp1 = se.difference(be) # 找到se中存在,be中不存在的集合,返回新值
print(temp1)     # {33, 11}
print(se)        # {33, 11, 22}

temp2 = se.difference_update(be) # 找到se中存在,be中不存在的集合,覆盖掉se
print(temp2)        # None
print(se)           # {33, 11},
# ===== 删除 ======
discard()remove()pop()
se = {11, 22, 33}
se.discard(11)
se.discard(44)  # 移除不存的元素不会报错
print(se)
se = {11, 22, 33}
se.remove(11)
se.remove(44)  # 移除不存的元素会报错
print(se)
se = {11, 22, 33}  # 移除末尾元素并把移除的元素赋给新值
temp = se.pop()
print(temp)  # 33
print(se) # {11, 22}
# ===== 取交集 ======
se = {11, 22, 33}
be = {22, 55}
temp1 = se.intersection(be)             #取交集,赋给新值
print(temp1)  # 22
print(se)  # {11, 22, 33}
temp2 = se.intersection_update(be)      #取交集并更新自己
print(temp2)  # None
print(se)  # 22
# ===== 判断 ======
se = {11, 22, 33}
be = {22}
print(se.isdisjoint(be))        #False,判断是否不存在交集(有交集False,无交集True)
print(se.issubset(be))          #False,判断se是否是be的子集合
print(se.issuperset(be))        #True,判断se是否是be的父集合
# ===== 合并 ======
se = {11, 22, 33}
be = {22}
temp1 = se.symmetric_difference(be)  # 合并不同项,并赋新值
print(temp1)    #{33, 11}
print(se)       #{33, 11, 22}
temp2 = se.symmetric_difference_update(be)  # 合并不同项,并更新自己
print(temp2)    #None
print(se)             #{33, 11}
# ===== 取并集 ======
se = {11, 22, 33}
be = {22,44,55}
temp=se.union(be)   #取并集,并赋新值
print(se)       #{33, 11, 22}
print(temp)     #{33, 22, 55, 11, 44}
# ===== 更新 ======
se = {11, 22, 33}
be = {22,44,55}
se.update(be)  # 把se和be合并,得出的值覆盖se
print(se)
se.update([66, 77])  # 可增加迭代项
print(se)
# ===== 集合的转换 ======
se = set(range(4))
li = list(se)
tu = tuple(se)
st = str(se)
print(li,type(li)) # [0, 1, 2, 3] <class 'list'>
print(tu,type(tu)) # (0, 1, 2, 3) <class 'tuple'>
print(st,type(st)) # {0, 1, 2, 3} <class 'str'>

示例

mylist = ['Google', 'Yahoo', 'Baidu']
#变更索引位置1Yahoo的内容为Microsoft
mylist[1] = 'Microsoft'
#获取索引位置1到5的数据,注意这里只会取到索引位置4,这里叫做取头不取尾
mylist[1:5]   # 'Tencent', 'Microsoft', 'Baidu', 'Alibaba'
#获取从最头到索引位置5的数据
mylist[ :5]   #'Google', 'Tencent', 'Microsoft', 'Baidu', 'Alibaba'

#获取从索引位置2开到最后的数据
mylist[2:]    #'Microsoft', 'Baidu', 'Alibaba','Sina'
mylist.append('Alibaba')  #运行结果: ['Google', 'Microsoft', 'Baidu', 'Alibaba']
mylist.insert(1, 'Tencent')  # ['Google', 'Tencent', 'Microsoft', 'Baidu', 'Alibaba']
# 删除尾部元素
mylist.pop()      # 会返回被删除元素
# 删除指定位置的元素
mylist.pop(1)  # 删除索引为1的元素,并返回删除的元素
mylist.remove('Microsoft') #删除列表中的Microsoft
del mylist[1:3]       #删除列表中索引位置1到位置 3 的数据
mylist.sort()          # 排序 [1, 2 ,4, 5]
# 【2021-10-22】list元素排序,非原地
table_data = sorted(table_data,key=lambda x:x[1], reverse=True)
print(len(mylist)) # 长度

a = [1,2,3,4,5,6]
#在a的数据基础上每个数据乘以10,再生成一个列表b,
b = [i*10 for i in a]
#生成一个从1到20的列表
a = [x for x in range(1,20)]
#把a中所有偶数生成一个新的列表b
b = [m for m in a if m % 2 == 0]
print(b)

# tuple
a = (1,2,3,4)

# dict
d = {}
d = dict() # 创建空字典2
d = {"one":1,"two":2,"three":3,"four":4} #直接赋值方式
d = dict.fromkeys(d.keys(), "222")
d = {k:v for k,v in d.items()} #常规字典生成式
d = {k:v for k,v in d.items() if v % 2 ==0} #加限制条件的字典生成方式
print(d.get("one333"))
del d["one"] #删除一个数据,使用del
d.clear() # 清空字典
print(d)

if "two" in d:
    print("key")

#for k in d:
#for v in d.values():
for k in d.keys():
    print(k,d[k])
for k,v in d.items():
    print(k,'--->',v)
#for key, value in enumerate(words):

# 通用函数:len,max,min,dict
d = {"one":1,"two":2,"three":3,"four":4}
print(max(d), min(d), len(d))

#集合的定义
s = set()
s = frozenset() # 冰冻集合
s = set([1,2,3])
s.add(6)
s.remove(2)

s1 = {1,2,3,4,5,6,7}
s2 = {5,6,7,8,9}

#交集
s_1 = s1.intersection(s2)
#差集
s_2 = s1.difference(s2)
#并集
s_3 = s1.union(s2)
#检查一个集合是否为另一个子集
s_4 = s1.issubset(s2)
print("检查子集结果:",s_4)
#检查一个集合是否为另一个超集
s_5 = s1.issuperset(s2)
print("检查超集结果:",s_5)

高级数据结构

【2021-3-11】Python高级数据结构详解

  • Collection、Array、Heapq、Bisect、Weakref、Copy以及Pprint这些数据结构的用法 Collections
  • collections模块包含了内建类型之外的一些有用的工具,例如Counter、defaultdictOrderedDictdeque以及nametuple
  • 其中Counter、deque以及defaultdict最常用。
  • Counter:统计频次
  • Weakref
    • weakref模块能够帮助我们创建Python引用,却不会阻止对象的销毁操作。
    • strong reference是一个对对象的引用次数、生命周期以及销毁时机产生影响的指针。
    • Weak reference则是对对象的引用计数器不会产生影响。当一个对象存在weak reference时,并不会影响对象的撤销。这就说,如果一个对象仅剩下weak reference,那么它将会被销毁。
  • Copy()
    • 通过shallow或deep copy语法提供复制对象的函数操作。shallow和deep copying的不同之处在于对于混合型对象的操作(混合对象是包含了其他类型对象的对象,例如list或其他类实例)。
      1. 对于shallow copy而言,它创建一个新的混合对象,并且将原对象中其他对象的引用插入新对象。
      2. 对于deep copy而言,它创建一个新的对象,并且递归地复制源对象中的其他对象并插入新的对象中。
  • Pprint()
    • Pprint模块能够提供比较优雅的数据结构打印方式,如果你需要打印一个结构较为复杂,层次较深的字典或是JSON对象时,使用Pprint能够提供较好的打印结果。

Counter

词频统计

原生态

a = ['a','b','r','a','d','r']
b = {}
for i in a:    
    if i in b.keys():        
        b[i] += 1    
    else:        
        b[i] = 1
    # 改进, get 方法
    # b[i] = b.get(i, 0) + 1
print(b) # {'a': 2, 'b': 1, 'r': 2, 'd': 1}

每次都要判断key是否存在

优化:setdefault

a = ['a','b','r','a','d','r']
b = {}
for i in a:   
    b.setdefault(i,0) # setdefault 设置不存在时的默认值
    b[i] += 1
print(b) # {'a': 2, 'b': 1, 'r': 2, 'd': 1}

虽然去除了if语句,但对于每一个key,我们都需要进行判断是否需要给一个默认值。

优化:defaultdict

defaultdict字典相当于是普通字段的强化版,继承原始dict的功能,并赋予了它新的特性。

from collections import defaultdict

a = ['a','b','r','a','d','r']
# 初始化defaultdict的参数是一个可调用对象,即函数名, int
# 除了int,也可以用:list对应[],str对应的是空字符串,set对应set(),int对应0
b = defaultdict(int)
# b = defaultdict(lambda :0) # 等效表达:匿名函数
for i in a:   
    b[i] += 1
print(b)

int函数相当于

def fun():   return 0

也可以使用匿名函数

b = defaultdict(lambda :0)

优化:Counter

Counter类的目的是用来跟踪值出现的次数。

  • 它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。
  • 计数值可以是任意的Interger(包括0和负数)

创建的方式有三种

  • 第一种是空的Counter类
  • 第二种传入一个可迭代对象,可以是str、list、tuple、dict
  • 最后一种是以键值对的形式。
from collections import Counter 

a = Counter() # 空的Counter类
b = Counter('afdasfdasd') # 传入一个可迭代对象
c = Counter({'a':4,'b':7}) # 以键值对的形式
d = Counter(a=4,b=7) 
e = Counter('积极,中性,积极'.split(','))
# 计数结果是一个字典样式,故若查找的key不存在将会报错。
print(a,b,c,d,e)
# most_common([n]) 将计数结果以列表嵌套二元元组的形式返回。
# 参数n表示给最多n个元素计数,且计数量大的优先,默认返回所有计数。
print(b.most_common()) # Counter({'a': 3, 'd': 3, 'f': 2, 's': 2})
t = e.most_common(2) # [('积极', 2), ('中性', 1)]
print(e.most_common(2)[0]) # ('积极', 2)
print(b.most_common(2)) # [('a', 3), ('d', 3), ('f', 2), ('s', 2)][('a', 3), ('d', 3)]

namedtuple

namedtuple: 给 tuple 以及 tuple 中的每一个元素都取名字

如下的代码,account[0],account[1] 分别指的什么?不清楚

account = ('checking', 1850.90)
print(account[0])  # name
print(account[1])  # balance

使用namedtuple

from collections import namedtuple

account = ('checking', 1850.90) # 普通tuple定义方法
Account = namedtuple("Account", ['name', 'balance']) # namedtuple定义方法
# 第1个参数是 tuple 名称,和定义名称相同
# 第2个参数是 fields 名称
accountNamedTuple_1 = Account('checking', 1850.90) # 定义具体数值
print(accountNamedTuple_1.name, accountNamedTuple_1.balance)  # checking 1850.9

accountNamedTuple_2 = Account._make(account) # 传入普通tuple值,_make方法
account_name_2, account_balance_2 = accountNamedTuple_2
print(account_name_2, account_balance_2) # checking 1850.9

accountNamedTuple_3 = Account(*account)      # 传入普通tuple值,*方法
account_name_3, account_balance_3 = accountNamedTuple_3
print(account_name_3, account_balance_3) # checking 1850.9

print(accountNamedTuple_1._asdict()['balance']) # 1850.9
print(accountNamedTuple_2._asdict()['balance']) # 1850.9
print(accountNamedTuple_3._asdict()['balance']) # 1850.9

Deque 双端队列

Deque双端队列

  • Deque是一种由队列结构扩展而来的双端队列(double-ended queue),队列元素能够在队列两端添加或删除。因此它还被称为头尾连接列表(head-tail linked list)
  • Deque 线程安全,经过优化的append和pop操作,在队列两端的相关操作都能够达到近乎O(1)的时间复杂度。虽然list也支持类似的操作,但是它是对定长列表的操作表现很不错,而当遇到pop(0)和insert(0, v)这样既改变了列表的长度又改变其元素位置的操作时,其复杂度就变为O(n)了

deque:double ended queue 双端队列,使用deque而非 list 的原因首先是deque效率高,其次它保证线程安全 (thread safe),deque 所有的操作都是线程安全的 ,因此在使用 thread 时可使用 deque

from collections import deque

friends = deque(('Rolf', 'Charlie', 'Jen', 'Anna'))

friends.append('Jose')
friends.appendleft('Anthony') # 左侧追加
print(friends)  # deque(['Anthony', 'Rolf', 'Charlie', 'Jen', 'Anna', 'Jose'])

friends.pop()
print(friends)  # deque(['Anthony', 'Rolf', 'Charlie', 'Jen', 'Anna'])

friends.popleft()  # 左侧弹出
print(friends)  # deque(['Rolf', 'Charlie', 'Jen', 'Anna'])

Array

Array 时间换空间

  • array模块定义了一个很像list的新对象类型,不同之处在于只能装一种类型的元素
  • 省空间,但时间慢:如存储一千万个整数,用list至少需要160MB的存储空间,而使用array只需要40MB。但虽然说能够节省空间,array上基本操作比list慢。
  • 列表推导式(list comprehension)时,会将array整个转换为list,使得存储空间膨胀。一个可行的替代方案是使用生成器表达式创建新的array

Heapq

heapq

  • heapq模块使用一个用实现的优先级队列。
  • 堆是一种简单的有序列表,并且置入了堆的相关规则。
  • 堆是一种树形的数据结构,树上的子节点与父节点之间存在顺序关系。

函数:

heappush(heap, x) # 将x压入堆中
heappop(heap) # 从堆中弹出最小的元素
heapify(heap) # 让列表具备堆特征
heapreplace(heap, x) # 弹出最小的元素,并将x压入堆中
nlargest(n, iter) # 返回iter中n个最大的元素
nsmallest(n, iter)  # 返回iter中n个最小的元素

Bisect

  • Bisect
    • bisect模块能够提供保持list元素序列的支持,使用了二分法完成大部分的工作。它在向一个list插入元素的同时维持list是有序的。
from collections import Counter
 
li = ["Dog", "Cat", "Mouse", 42, "Dog", 42, "Cat", "Dog"]
a = Counter(li)
print(a) # Counter({'Dog': 3, 42: 2, 'Cat': 2, 'Mouse': 1})
print(len(set(li))) # 4
print(a.most_common(3)) # [('Dog', 3), ('Cat', 2), ('Mouse', 1)]

from collections import deque

q = deque(range(5))
q.append(5)
q.appendleft(6)
print q
print q.pop()
print q.popleft()
print q.rotate(3) # 队列的旋转操作,Right rotate(正参数)是将右端的元素移动到左端,而Left rotate(负参数)则相反

from collections import defaultdict

location = defaultdict(['a', 'b']) # 有序
location = defaultdict(('a', 'b')) # 无序

# 对于较大的array,这种in-place修改能够比用生成器创建一个新的array至少提升15%的速度
import array

a = array.array("i", [1,2,3,4,5])
b = array.array(a.typecode, (2*x for x in a)) # 节省空间
for i, x in enumerate(a): # 提效
    a[i] = 2*x

import heapq
 
heap = []
for value in [20, 10, 30, 50, 40]:
    heapq.heappush(heap, value) # 入堆

while heap:
    print heapq.heappop(heap) # 出堆

nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]
print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price']) # 针对字典的复杂操作

import bisect
 
a = [(0, 100), (150, 220), (500, 1000)]
bisect.insort_right(a, (250,400))
print bisect.bisect(a, (550, 1200)) # 5 寻找插入点
print a # [(0, 100), (150, 220), (250, 400), (500, 1000)]

import weakref

a = Foo() #created
b = a() # 强引用
b = weakref.ref(a) # 弱引用
b = weakref.proxy(a) # proxy更像是一个strong reference,但当对象不存在时会抛出异常
# 删除strong reference的时候,对象将立即被销毁
del a

import copy

a = [1,2,3]
b = [4,5]
c = [a,b]

d = c # 正常复制
print id(c) == id(d)          # True - d is the same object as c
print id(c[0]) == id(d[0])    # True - d[0] is the same object as c[0]

d = copy.copy(c) # Shallow Copy 浅拷贝,创建一个新的容器,其包含的引用指向原对象中的对象
print id(c) == id(d)          # False - d is now a new object
print id(c[0]) == id(d[0])    # True - d[0] is the same object as c[0]

d = copy.deepcopy(c) # Deep Copy 深拷贝,创建的对象包含的引用指向复制出来的新对象
print id(c) == id(d)          # False - d is now a new object
print id(c[0]) == id(d[0])    # False - d[0] is now a new object

import pprint

matrix = [ [1,2,3], [4,5,6], [7,8,9] ]
a = pprint.PrettyPrinter(width=20)
a.pprint(matrix)

# [[1, 2, 3],
#  [4, 5, 6],
#  [7, 8, 9]]

dict 扩展

Defaultdict

  • Defaultdict: 默认字典

使用dict时

  • 如果引用的Key不存在,就会抛出KeyError。
  • 如果希望key不存在时,返回一个默认值呢?这就是 defaultdict
from collections import defaultdict

dd = defaultdict(lambda: 'NULL')
dd['key1'] = 'abc'
dd['key1'] # key1存在, 'abc'
dd['key2'] # key2不存在,返回默认值 'NULL'

注意

  • 默认值是调用函数返回的,而函数在创建 defaultdict对象时传入。
  • 除了在Key不存在时返回默认值,defaultdict的其他行为跟dict是完全一样的

一键多值

(1) setdefault 实现

d={}
d.setdefault('a',[]).append(1)
d.setdefault('a',[]).append(2)
d.setdefault('b',[]).append(3)
print(d) # {'a': [1, 2], 'b': [3]}

(2) defaultdict 实现

from collections import defaultdict
# {'a':[1,2,3]}
d=defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['a'].append(3)
print(d) # defaultdict(list, {'a': [1, 2, 3]})

s=defaultdict(set)
s['a'].add(1)
s['a'].add(4)
s['b'].add(2)
s['b'].add(3)
s['c'].add(3)
s['c'].add(6)
s['c'].add(6)
print(s) # defaultdict(set, {'a': {1, 4}, 'b': {2, 3}, 'c': {3, 6}})

defaultdict 和 setdefault 的比较

  • 也可以用 setdefault 来实现以上操作,但是每次到用 setdefault 都会创建一个初始值的新实例

OrderedDict

dict里的Key无序,导致遍历dict时,无法确定Key的顺序。

  • 如果要保持Key的顺序呢?用OrderedDict
from collections import OrderedDict
# ----- dict 无序 ------
d = dict([('a', 1), ('b', 2), ('c', 3)])
d # dict的Key是无序的
# {'a': 1, 'c': 3, 'b': 2}
# ----- dict 有序 ------
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
od # OrderedDict的Key是有序的
# OrderedDict([('a', 1), ('b', 2), ('c', 3)])
od = OrderedDict()
od['z'] = 1
od['y'] = 2
od['x'] = 3
od.keys() # 按照插入的Key的顺序返回, ['z', 'y', 'x']

注意

  • OrderedDict 的 Key 会按照插入顺序排列,不是Key本身排序

OrderedDict 可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的Key

from collections import OrderedDict

class LastUpdatedOrderedDict(OrderedDict):
    def __init__(self, capacity):
        super(LastUpdatedOrderedDict, self).__init__()
        self._capacity = capacity

    def __setitem__(self, key, value):
        containsKey = 1 if key in self else 0
        if len(self) - containsKey >= self._capacity:
            last = self.popitem(last=False)
            print 'remove:', last
        if containsKey:
            del self[key]
            print 'set:', (key, value)
        else:
            print 'add:', (key, value)
        OrderedDict.__setitem__(self, key, value)

ChainMap

如何查找多个字典?

  • 逐个遍历:如果x没有,就遍历y…
x={'a':1,'c':2}
y={'b':3,'c':4}
x.update(y) # y 中的值覆盖 x
print(x) # {'a': 1, 'c': 4, 'b': 3}

有没有更好的办法?

  • ChainMap只是维护了一个记录底层映射关系的列表,如果有重复的值,会采用第一个映射中对应的值,修改映射的操作总是会作用在第一个映射结构上。

ChainMap 和 update 的比较

  • update方法,执行x.update(y), x会就地改变,如果y中有和x中一样的关键字,则y中关键字相应的值会覆盖x中相应关键字的值。
  • ChainMap的修改只体现在第一个字典中
from collections importChainMap

z=ChainMap(x,y)
print(z) # ChainMap({'a': 1, 'c': 2}, {'b': 3, 'c': 4})
print(z['a']) # 1
print(z['b']) # 3
print(z['c']) # 2, 返回第一个找到的值
# 修改值
z['c']=10 # 只修改了第一个映射里的c
z['d']=20 # 只在第一个映射里添加了键值对:'d':20
print(z)  # ChainMap({'a': 1, 'c': 10, 'd': 20}, {'b': 3, 'c': 4})
del z['a'] # 在z里删除关键字a,会在字典x中体现出来
print(x) # {'c': 10, 'd': 20}
del z['b'] # 通过z修改不了字典y, KeyError: "Key not found in the first mapping: 'b'"
# ChainMap后,对原始映射的修改会反映到合并后的结果中
x={'a':1,'c':2}
y={'b':3,'c':4}
z=ChainMap(x,y)
x['a']=10
print(z) # ChainMap({'a': 10, 'c': 2}, {'b': 3, 'c': 4})

aadict

  • aadict包:在dict的基础上封装了一层,使其能够像操作属性一样使用字典。使得后续操作更加简便且易读,还可以预防不必要的异常出现。
  • 代码示例
from addict import Dict

configs = Dict()

configs.platform.status = "on"
configs.platform.web.task.name = "测试-1204"
configs.platform.web.periods.start = "2020-10-01"
configs.platform.web.periods.end = "2020-10-31"
# 获取不存在的key时会返回一个空字典,不用担心报KeyError,对返回值做判空处理即可
app_configs = configs.platform.app
assert app_configs == {}

bidict

bidict,一个超酷的 Pythonicon 库,提供了一种双向字典的实现,即一种能够通过键或值来进行快速查找的数据结构icon。可以使用bidict库创建、访问和修改双向映射。bidict库提供了一些方法来处理安全的双向映射,可以在数据处理中使用双向映射来关联两个不同的数据集

dataclass

【2023-1-13】Dataclasses是一些适合于存储数据对象(data object)的Python类

对于那些熟悉对象关系映射(Object Relational Mapping,简称 ORM)的人来说,一个模型实例就是一个数据对象,表示了一种特定类型的实体,存储了用于定义或表示那种实体的属性。

Python3.7 提供了一个装饰器 dataclass,用以把一个类转化为 dataclass。

from dataclasses import dataclass
# 常规表示
class Number:
    def __init__(self, val):
        self.val = val
    def __eq__(self, other):
        return self.val == other.val
    def __lt__(self, other):
        return self.val < other.val

one = Number(1)
one.val
# dataclass表示
@dataclass
class Number:
    val:int 
    test:int = 0 # 定义默认值
one = Number(1)
# dataclass会自动添加一个__repr__函数,不用手动实现
one.val # 1

dataclass装饰器带来的变化:

  • 无需定义 __init__,然后将值赋给 self,dataclass 负责处理它(LCTT 译注:此处原文可能有误,提及一个不存在的d)
  • 无需定义 数据比较类方法,如 eq 和 lt
  • 更加易读, 预先定义了成员属性,以及类型提示。现在立即能知道 val是int类型, 这比一般定义类成员的方式更具可读性。

工具包导入

python导入相关,绝对导入可以在任何地方使用, 所有非 相对导入 都是绝对导入。

绝对导入根据 sys.path 来查找 包 或 模块. sys.path 是一个列表, 通常情况下会包含

  • 内建模块 的索引路径
  • 第三方库 的保存路径
  • 环境变量 PYTHONPATH 列出的目录
  • 脚本所在目录或当前目录 前三个路径一般不需要怎么管, 容易出现问题的地方是第四个 脚本所在目录或当前目录.

导入顺序

首先,这个路径会被添加到 sys.path 最前面, 所以如果该路径下存在和内建或第三方库同名的模块或包, 使用绝对导入会优先导入那个同名的模块。然后就是确定添加的这个目录到底是 脚本所在目录 还是 当前目录 了。

汇总:

  • 直接执行脚本 时添加的路径是 脚本所在目录.
  • 使用 Python -m 的方式执行脚本时, Python2 添加的是 脚本所在目录, 而 Python3 添加的是 当前目录.
  • 同一目录下, 同名的 包 和 模块, 包 的导入优先级比 模块 高。可以得出的一个优先级排序为:
    • 当前目录或脚本所在目录 ==> 内建模块 ==> 第三方库
    • 包 ==> 模块
  • import 会生成 .pyc 文件, .pyc 文件的执行速度不比 .py 快, 但是加载速度更快
  • 重复 import 只会执行第一次 import
  • 如果在 执行过程中 中 import 的模块发生改动,可以通过 reload 函数重新加载
  • from xxx import * 会导入除了以 _ 开头的所有变量,但是如果定义了 all, 那么会导入 all 中列出的东西
  • import xxx.object 的方式不能直接使用 object, 仍然需要通过 xxx.object 的方式使用
  • import xxx.object as obj 的方式可以直接使用 obj Python3.4 之后的版本中, 内置函数 reload 被移除了, 要使用可以通过 from importlib import reload 导入

模块、包与库

Python中如何正确使用import

多种类型:模块

  • (1) 模块 module
  • (2) 包 package
  • (3) 库

备注:

  • 模块、包、库这三个概念实际上都是模块,只不过是个体和集合的区别

使用 import 导入的东西只有三种: , 模块, 其他对象.

  • : __init__.py 文件所在目录
    • 该文件用于组织包(package),方便管理各个模块之间的引用、控制着包的导入行为。
    • 可以是空文件,表示导入所在目录的所有文件,也可以指定导入的包 __ALL__ = [‘a.py’,’b.py’],但不建议写模块,以保证该文件简单。
      • 模糊导入时,形如 from package import 是由 __all__定义的。
      • 精确导入,形如 from package import *、import package.class。
    • __path__也是一个常用变量,是个列表,默认情况下只有一个元素,即当前包(package)的路径。修改 path 可改变包(package)内的搜索路径。
  • 模块: 单个 .py 文件
  • 其他对象: 除了 包 和 模块 以外的 Python Object

(1)模块 module

模块指一个文件,如 .py文件,里面定义了一些函数和变量,需要的时候就可以导入这些模块(问题:class算模块吗?)

可以作为module的文件类型有:”.py”、”.pyo”、”.pyc”、”.pyd”、”.so”、”.dll”。

导入方法:

import a
import a as b # 定义别名

(2)包 package

包指一个目录,在模块之上的概念,为了方便管理而将文件进行打包。

  • 一个文件夹下必须要有 __init__.py 这个文件才会被识别为包。
  • 除了 __init__.py,就是一些模块文件和子目录。

导入方法:

# 直接整体导入
import a 
import a as b
from a import *
# 部分导入--from + import导入包中的部分模块
from a import a1
from a import a1 as b
from a import a1,a2

(3)库

库是具有相关功能模块的集合。

  • 这也是Python的一大特色之一,即具有强大的标准库第三方库以及自定义模块
  • 标准库:就是下载安装的python里那些自带的模块
    • 注意:里面有一些模块是看不到的, 比如像sys模块,这与linux下的cd命令看不到是一样的情况
  • 第三方库:就是由其他的第三方机构,发布的具有特定功能的模块
  • 自定义模块:用户自己可以自行编写模块,然后使用

导入方式

使用 import 的两种格式:

  • import package/module
  • from package/module import object

单独使用 import 的时候, 导入的只能是 模块.

  • 而 from … import … 的形式则可以从 模块 中导入任意对象

Python在导入import包的时候,有绝对导入和相对导入方式。

  • 绝对导入:import p1.m1 或者 from p1 import m1 等。
  • 相对导入:from . import m1 或者 from .. import m1 或者 from ..p1 import m1 或者 from .m1 import f1等。
    • 相对导入的方式只能用在 内部, 语法格式为:from . import object,
    • . 代表当前目录, .. 代表父级目录, 之后每多一个点代表目录上升一层。
    • 代表相对路径的符号 . 只能出现在 from 后面而不能出现在 import 后面, 即:

比较而言,绝对导入更为简便清晰,相对导入则可维护性强,但是容易出错。

史上最详解的 导入(import)

  • (1)标准导入:“标准”import,顶部导入
  • (2)顺序导入
    • 嵌套导入;各个模块的Local命名空间的独立的。即:
      • test模块 import moduleA后,只能访问moduleA模块,不能访问moduleB模块。虽然moduleB已加载到内存中,如需访问,还得明确地在test模块 import moduleB。实际上打印locals(),字典中只有moduleA,没有moduleB。
    • 循环导入/嵌套导入

示例

# -*- coding: utf-8 -*-
# 绝对导入
import sys
print(sys.path[0:1])
# 相对导入
from . import utils  # True
import .utils  # False

from xxx import * # 会导入除了以 _ 开头的所有变量,但是如果定义了 __all__, 那么会导入 __all__ 中列出的东西
import xxx.object # 不能直接使用 object, 仍然需要通过 xxx.object 的方式使用
import xxx.object as obj # 直接使用 obj

# 批量导入
from click import (
    argument,
    command,
    echo,
    edit,
    group,
    Group,
    option,
    pass_context,
    Option,
    version_option,
    BadParameter,
)

import sys, os

print(__file__)    #当前.py文件的位置
print(os.path.abspath(__file__))  #返回当前.py文件的绝对路径
print(os.path.dirname(os.path.abspath(__file__)))   #当前文件的绝对路径目录,不包括当前 *.py 部分,即只到该文件目录
print(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) #返回文件本身目录的上层目录    
print(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))  #每多一层,即再上一层目录

print(os.path.realpath(__file__))   #当前文件的真实地址
print(os.path.dirname(os.path.realpath(__file__))) # 当前文件夹的路径

path = os.path.dirname(os.path.abspath(__file__))
sys.path.append(path)   #将目录或路径加入搜索路径

print(__name__)

init 用法

【2023-2-21】python init 作用详解

__init__.py 文件的作用是将文件夹变为一个Python模块,Python 中的每个模块的包中,都有__init__.py 文件。

  • 通常 __init__.py 文件为空,实际上还可以增加其他功能。
  • 导入一个包时,实际上是导入了它的__init__.py文件。
  • 所以在__init__.py文件中批量导入所需要的模块,而不再需要一个一个的导入。

访问 _init_.py文件中的引用文件,需要加上包名。

_init__.py中还有一个重要的变量,__all_, 它用来将模块全部导入。

不用包时:

# package
# __init__.py
import re
import urllib
import sys
import os

# a.py
import package 
print(package.re, package.urllib, package.sys, package.os)

使用 __init__ 包时, 既可以导入系统包,也可以导入自定义包

# __init__.py
import subpackage1.a # 将模块subpackage.a导入全局命名空间,例如访问a中属性时用subpackage1.a.attr
from subpackage1 import a # 将模块a导入全局命名空间,例如访问a中属性时用a.attr_a
from subpackage.a import attr_a # 将模块a的属性直接导入到命名空间中,例如访问a中属性时直接用attr_a

__all__ = ['os', 'sys', 're', 'urllib']

# a.py
from package import *

Python库目录

python -c "import sys; print(sys.path)" # 终端直接执行python代码
python -m http.server 8081 # 运行模块,如简易文件服务
python -m test.py # 相当于import,叫做当做模块来启动
python -u test.py # 按照指定用户权限执行脚本

时间计算

time 和 datetime 两个模块,转换流程:时间格式转换图

  • img

ISO 8601

  • 一种标准化的时间表示方法,表示格式为 :YYYY-MM-DDThh:mm:ss ± timezone,表示不同时区的时间,时区部分用Z 表示为 UTC 标准时区。两个例子:
1997-07-16T08:20:30Z #  UTC 时间的 1997 年 7 月 16 号 8:20:30
1997-07-16T19:20:30+08:00 # 东八区时间的 1997 年 7 月 16 号 19:20:30

时间概念

  • GMT 时间:格林威治时间,基准时间
  • UTC 时间:Coordinated Universal Time,全球协调时间,更精准的基准时间,与 GMT 基本等同
  • CST 中国基准时间:为 UTC 时间 + 8 小时,即 UTC 时间的 0 点对应于中国基准时间的 8 点,即为一般称为东八区的时间

整个地球分为24个时区,每个时区都有自己的本地时间

  • 国际无线电通信中,为统一而普遍使用一个标准时间,称为通用协调时(UTC, Universal Time Coordinated)。UTC与格林尼治平均时(GMT, Greenwich Mean Time)一样,都与英国伦敦的本地时相同。UTC与GMT含义完全相同。
  • 北京时区是东八区,领先UTC 8个小时。所以将UTC装换成北京时间时,需要加上8小时。

时间戳

  • 1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0,当前的时间戳即为从 epoch time 到现在的秒数,一般叫做 timestamp,因此一个时间戳一定对应于一个特定的 UTC 时间,同时也对应于其他时区的一个确定的时间。因此时间戳可以认为是一个相对安全的时间表示方法。

datetime

datetime 分成两种类型:

  • naive本地类型时间,没有指定时区信息,此类型的时区是根据运行环境确定对应的时区。因此这种类型的时间会因为运行环境的不同而得到不同时间戳
  • aware带时区类型的时间,这种类型的时间对象由于时间和时区都是确定的,因此对应于确定的时间戳

举例如下:

from datetime import datetime, timezone

now = datetime.now() # native 类型
now.tzinfo      # None 
utc_now = datetime.now(timezone.utc) # aware类型, 指定时区
utc_now.tzinfo  # UTC

now 没有指定时区,为 naive 类型的时间,其时区与运行环境相关。而 utc_now 指定了 UTC 时区,为 aware 类型的时间。

获取当前时间

  • datetime.now() 获取当前时间,支持设置对应的时区,如果不设置时区默认获取的是本地的时间,根据是否指定时区可能穿件出 naive 类型的时间或者 aware 类型的时间,但是对应的时间戳都是符合预期的。
  • datetime.utcnow() 谨慎使用 获取是当前 UTC 对应的时间,但是生成的 datetime 对象是没有指定时区的,因此使用的是本地时区,创建的是 naive 类型的时间。因此如果运行环境为东八区,得到的时间是 UTC 对应的时间,但是时区是东八区,最终得到的时间会比预期早 8 个小时,转化得到时间戳也是不符合预期的。
from datetime import datetime
now = datetime.now()
now.timestamp()  # 1610035129.323702 
unow = datetime.utcnow()
unow.timestamp()  # 1610006329.323797

在 2021-01-07 23:58:49 在东八区环境下运行

  • now.timestamp() 得到时间戳转化为对应的时间为东八区的 2021-01-07 23:58:49
  • 但是 unow.timestamp() 得到的时间戳对应的时间为东八区的 2021-01-07 15:58:49,对应于 UTC 时间 2021-01-07 07:58:49,和 UTC 的当前时间完全对不上。

时间戳操作

  • datetime.timestamp() 当前时间对应的时间戳
  • datetime.fromtimestamp() 根据时间戳生成运行环境时区对应的时间
  • datetime.utcfromtimestamp() 谨慎使用, 根据时间戳生成对应的 UTC 时间,由于生成的 datetime 是没有指定时区的,因此获取时间戳看起来得到的是 8 个小时之前时间的时间戳
from datetime import datetime

timestamp = 1610035129
d1 = datetime.fromtimestamp(timestamp)  # 2021-01-07 23:58:49 
d2 = datetime.utcfromtimestamp(timestamp) # 2021-01-07 15:58:49

时区转换

时区设置

  • 默认 datetime 没有时区信息,通过 datetime.replace() 为时间设置上时区,但必须保证对应的时间与时区信息匹配,否则就会导致错误的时区的时间

一个简单例子就是:

from datetime import datetime, timedelta, timezone
tz_utc_8 = timezone(timedelta(hours=8))  # 创建时区UTC+8:00,即东八区对应的时区 now = datetime.now()  # 默认构建的时间无时区 dt = now.replace(tzinfo=tz_utc_8)  # 强制设置为UTC+8:00

设置上对应的时区后,对应的日期与时间是不变的,但是由于设置了全新的时区,如果与之前的时区不同,那么对应的时间戳就会改变,使用此方法时要谨慎

时区转换

  • 将带有时区信息的时间转换为另一个时区的时间,通过 datetime.astimezone() 可以实现

一个简单的例子是:

from datetime import datetime, timedelta, timezone
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)  # 构建了 UTC 的当前时间 
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))  # 将时区转化为东八区的时间

通过 astimezone() 进行转换后,虽然时间变化了,但是对应的是同样的基准时间,因此对应的时间戳是不变的,

import datetime
# ------方法①---------
t_str = '2023-08-16T08:20:30Z'
utc_now = datetime.datetime.strptime(t_str,'%Y-%m-%dT%H:%M:%SZ') # 从字符串里获取 UTC时间
#utc_now = datetime.datetime.utcnow() # 直接获取本地UTC时间
print('转换前:', utc_now, ', 时区:', utc_now.tzinfo)
tm_new = utc_now.replace(tzinfo=datetime.timezone.utc).astimezone(tz=None) # 转本地时间(即中国时间,东八区)
print('转换后:', tm_new.strftime('%Y-%m-%d %H:%M:%S'))
# -------方法②---------
import datetime

date_str = [ "2023-08-17T08:20:54Z", "2023-08-17T08:28:47.776Z" ]
for s in date_str:
    if s.find('.') != -1:
        UTC_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
    else:
        UTC_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
    utc_date = datetime.datetime.strptime(s, UTC_FORMAT)
    local_date = utc_date + datetime.timedelta(hours=8)
    local_date_str = datetime.datetime.strftime(local_date ,'%Y-%m-%d %H:%M:%S')
    print(local_date_str )    # 2019-07-26 16:20:54

pytz 工具

from pytz import timezone

def datetime_as_timezone(date_time, time_zone):
    tz = timezone(time_zone)
    utc = timezone('UTC')
    return date_time.replace(tzinfo=utc).astimezone(tz)


def datetime_to_str(date_time):
    date_time_tzone = datetime_as_timezone(date_time, 'Asia/Shanghai')
    return '{0:%Y-%m-%d %H:%M}'.format(date_time_tzone)

时间差函数

import time
import datetime

def diff_time(start, end, size='seconds'):
    #start = '2017-10-04 09:01:04'
    #end = '2017-10-05 10:02:50'
    start_tm = datetime.datetime.strptime(start, "%Y-%m-%d %H:%M:%S")
    end_tm = datetime.datetime.strptime(end, "%Y-%m-%d %H:%M:%S")
    diff = end_tm - start_tm
    print('{} -> {} = {}'.format(start, end, (end_tm - start_tm).seconds))
    if size == 'second':
        return diff.total_seconds()
    elif size == 'minute':
        return diff.total_seconds()//60
    elif size == 'hour':
        return diff.total_seconds()//3600
    elif size == 'day':
        return diff.days
    else: # 格式未知
        return diff.total_seconds()
    return diff.total_seconds()

start = '2017-10-04 09:01:04'
end = '2017-10-04 10:02:50'
res = diff_time(start, end, 'minute')
res

时间转换

from datetime import datetime

def str2timestamp(s):
    # 字符串 → 时间戳
    # s = '2021-10-25 15:29:08' -> 1635146948
    ts = datetime.strptime(str_p,'%Y-%m-%d %H:%M:%S').timestamp()
    return ts

def timestamp2str(ts):
    # 时间戳 → 字符串
    # 1695212244 -> 2023-09-20 20:17:24
    cur_date = datetime.fromtimestamp(ts)
    cur_date_str = datetime.strftime(cur_date ,'%Y-%m-%d %H:%M:%S')
    # cur_date_str = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
    return cur_date_str

for item in openai.File.list()['data']:
    #print(item)
    pass

import pandas as pd
df_file = pd.DataFrame(openai.File.list()['data'])
df_file['time'] = df_file['created_at'].map(timestamp2str)
df_file

用法详解:

import time
import datetime

# 【2023-2-11】一步到位, 时间戳→字符串输出
ts = 1695212244
tm = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')

print('time用法')
time_now = int(time.time())              # 获取当前时间的时间戳
print(int(datetime.datetime.now().timestamp()))     # 获取当前时间的时间戳
print(int(time.time()))              # 获取当前时间的时间戳
today = datetime.datetime.today().date() # 当前日期
weekday = today.weekday() # 当前星期几
# 时间增量
yestoday = today + datetime.timedelta(days=-1)
tomorrow = today + datetime.timedelta(days=1)
print(today) # 2019-01-30
print(yestoday)# 2019-01-29
print(tomorrow)# 2019-01-31
print('datetime用法')
dt_now = datetime.datetime.fromtimestamp(time_now)
print(type(dt_now), dt_now)   # <class 'datetime.datetime'> 2020-12-16 10:40:34
print(int(datetime.datetime.now().timestamp()))     # 获取当前时间的时间戳
print('字符串转datetime')
str_p = '2021-10-25 15:29:08'
d = datetime.datetime.strptime(str_p,'%Y-%m-%d %H:%M:%S')
print(str_p, d) # 2019-01-30 15:29:08
d = datetime.datetime.strptime(str_p,'%Y-%m-%d %H:%M:%S')
week = d.weekday() + 1
# 月、日、时、分、秒
d.month, d.day, d.hour, d.minute, d.second
d.weekday() # 星期
print('datetime转字符串')
print(d.strftime("%d/%b/%Y:%H:%M:%S"))    # 16/Dec/2020:11:01:37
print(d.strftime('%Y-%m-%d %H:%M:%S'))    # 2020-06-16 10:31:08
# 星期几
datetime.date(2022, 2, 22).isoweekday()
datetime.date(2022, 2, 22).weekday() # 1
datetime.date(2022, 2, 22).strftime("%A") # 'Tuesday'
datetime.date(2022, 2, 22).strftime("%a") # 'Tue'
import calendar
calendar.weekday(2022, 2, 22) # 1
print('时间差')
# 字符串时间差 【2022-9-17】亲测
print ((userEnd-userStart).days) # 0 天数
# print (end-start).total_seconds() # 30.029522 精确秒数
# print (end-start).seconds # 30 秒数
# print (end-start).microseconds # 29522 毫秒数
a1='2017-10-04 09:01:04'
a2='2017-10-05 10:02:50'
test1=datetime.datetime.strptime(a1, "%Y-%m-%d %H:%M:%S")
test2=datetime.datetime.strptime(a2, "%Y-%m-%d %H:%M:%S")
diff=test2-test1
print('{} -> {} = {}'.format(start, end, (userEnd-userStart).seconds))
print(diff.days)
print(diff.total_seconds())
print(diff.total_seconds()//60)

流程控制

switch 语句

switch

C++, Java里的switch语句,以C++为例:

#include 
using namespace std;

int main() {
  int day = 4;
  switch (day) {
  case 1:
    cout << "Monday";
    break;
  case 2:
    cout << "Tuesday";
    break;
  case 3:
    cout << "Wednesday";
    break;
  case 4:
    cout << "Thursday";
    break;
  case 5:
    cout << "Friday";
    break;
  case 6:
    cout << "Saturday";
    break;
  case 7:
    cout << "Sunday";
    break;
  }
  return 0;
}

Python解法

【2022-11-7】然而,Python 老版本没有 switch 语句,3.10版本后才有

def number_to_string(argument):
    match argument:
        case 0:
            return "zero"
        case 1:
            return "one"
        case 2:
            return "two"
        case default:
            return "something"
 
if __name__ = "__main__":
    argument = 0
    number_to_string(argument)

3.10以前如何实现switch功能?3种方法

  • dict
  • if then
  • class 面向对象
  • 匿名函数

dict方式

dict 结构:

# Function to convert number into string
def monday():
    return "monday"
# Switcher is dictionary data type here
def numbers_to_strings(argument):
    switcher = {
        0: "zero", 
        1: "one",
        2: "two",
        3: monday # 此处的value值也可以是函数名
    }
 
    # get() method of dictionary data type returns
    # value of passed argument if it is present
    # in dictionary otherwise second argument will
    # be assigned as default value of passed argument
    return switcher.get(argument, "nothing")
 
# Driver program
if __name__ == "__main__":
    argument=0
    print (numbers_to_strings(argument))

if-else方法

if-else 实现

bike = 'Yamaha'

if bike == 'Hero':
	print("bike is Hero")

elif bike == "Suzuki":
	print("bike is Suzuki")

elif bike == "Yamaha":
	print("bike is Yamaha")

else:
	print("Please choose correct answer")

类属性方式

类属性实现

class Python_Switch:

	def day(self, month):
		default = "Incorrect day"
        # 拼装、提取、返回属性方法
		return getattr(self, 'case_' + str(month), lambda: default)()

	def case_1(self):
		return "Jan"

	def case_2(self):
		return "Feb"

	def case_3(self):
		return "Mar"
my_switch = Python_Switch()
print(my_switch.day(1))
print(my_switch.day(3))

匿名函数

def zero():
        return 'zero'
def one():
        return 'one'
def indirect(i):
        switcher={
                0:zero,
                1:one,
                2:lambda:'two'
                }
        func=switcher.get(i,lambda :'Invalid')
        return func()
indirect(4)

itertools 高效迭代

itertools模块

代码:

from itertools import *
i = 0
for item in cycle(['a', 'b', 'c']):#无限循环
#for i in repeat('over', 5): #重复5次
#for i in izip(count(1), ['a', 'b', 'c']):
#for i in chain([1, 2, 3], ['a', 'b', 'c']): #多个迭代器串联成一个
    i += 1
    if i == 10:
        break
    print (i, item)

Python标准库13 循环器 (itertools)

无穷循环器

count(5, 2)     #从5开始的整数循环器,每次增加2,即5, 7, 9, 11, 13, 15 ...
cycle('abc')    #重复序列的元素,既a, b, c, a, b, c ...
repeat(1.2)     #重复1.2,构成无穷循环器,即1.2, 1.2, 1.2, ...
repeat也可以有一个次数限制:
repeat(10, 5)   #重复10,共重复5次

 

函数式工具

函数式编程是将函数本身作为处理对象的编程范式。在Python中,函数也是对象,因此可以轻松的进行一些函数式的处理,比如map(), filter(), reduce()函数。

itertools包含类似的工具。这些函数接收函数作为参数,并将结果返回为一个循环器

from itertools import *
rlt = imap(pow, [1, 2, 3], [1, 2, 3])
for num in rlt:
    print(num)

此外,还可以用下面的函数:

starmap(pow, [(1, 1), (2, 2), (3, 3)])
# pow将依次作用于表的每个tuple。
# ifilter函数与filter()函数类似,只是返回的是一个循环器。
ifilter(lambda x: x > 5, [2, 3, 5, 6, 7]
# 将lambda函数依次作用于每个元素,如果函数返回True,则收集原来的元素。6, 7
ifilterfalse(lambda x: x > 5, [2, 3, 5, 6, 7]) # 与上面类似,但收集返回False的元素。2, 3, 5
takewhile(lambda x: x < 5, [1, 3, 6, 7, 1]) # 当函数返回True时,收集元素到循环器。一旦函数返回False,则停止。1, 3
dropwhile(lambda x: x < 5, [1, 3, 6, 7, 1]) # 当函数返回False时,跳过元素。一旦函数返回True,则开始收集剩下的所有元素到循环器。6, 7, 1

组合工具

可以通过组合原有循环器,来获得新的循环器。

chain([1, 2, 3], [4, 5, 7])      # 连接两个循环器成为一个。1, 2, 3, 4, 5, 7
product('abc', [1, 2])   # 多个循环器集合的笛卡尔积。相当于嵌套循环        
for m, n in product('abc', [1, 2]):
    print m, n
permutations('abc', 2)   # 从'abcd'中挑选两个元素,比如ab, bc, ... 将所有结果排序,返回为新的循环器。
# 注意,上面的组合分顺序,即ab, ba都返回。
combinations('abc', 2)   # 从'abcd'中挑选两个元素,比如ab, bc, ... 将所有结果排序,返回为新的循环器。
# 注意,上面的组合不分顺序,即ab, ba的话,只返回一个ab。
combinations_with_replacement('abc', 2) # 与上面类似,但允许两次选出的元素重复。即多了aa, bb, cc

groupby()

将key函数作用于原循环器的各个元素。

  • 根据key函数结果,将拥有相同函数结果的元素分到一个新的循环器。
  • 每个新的循环器以函数返回结果为标签。这就好像一群人的身高作为循环器。

可以使用这样一个key函数: 如果身高大于180,返回”tall”;如果身高底于160,返回”short”;中间的返回”middle”。最终,所有身高将分为三个循环器,即”tall”, “short”, “middle”。

def height_class(h):
    if h > 180:
        return "tall"
    elif h < 160:
        return "short"
    else:
        return "middle"
friends = [191, 158, 159, 165, 170, 177, 181, 182, 190]
friends = sorted(friends, key = height_class)
for m, n in groupby(friends, key = height_class):
    print(m)
    print(list(n))

注意,groupby的功能类似于UNIX中的uniq命令。分组之前需要使用sorted()对原循环器的元素,根据key函数进行排序,让同组元素先在位置上靠拢。  

其它工具

compress('ABCD', [1, 1, 1, 0])  # 根据[1, 1, 1, 0]的真假值情况,选择第一个参数'ABCD'中的元素。A, B, C
islice()                        # 类似于slice()函数,只是返回的是一个循环器
izip()                          # 类似于zip()函数,只是返回的是一个循环器。
# (1)无限迭代器
迭代器         参数         结果            例子
count()     start, [step]   start, start+step, start+2*step, ...                count(10) --> 10 11 12 13 14 ...
cycle()     p               p0, p1, ... plast, p0, p1, ...                      cycle('ABCD') --> A B C D A B C D ...
repeat()    elem [,n]       elem, elem, elem, ... endlessly or up to n times    repeat(10, 3) --> 10 10 10

# (2)处理输入序列迭代器
迭代器          参数            结果            例子
chain()     p, q, ...           p0, p1, ... plast, q0, q1, ...              chain('ABC', 'DEF') --> A B C D E F
compress()  data, selectors     (d[0] if s[0]), (d[1] if s[1]), ...         compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F
dropwhile() pred, seq           seq[n], seq[n+1], starting when pred fails  dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1
groupby()   iterable[, keyfunc] sub-iterators grouped by value of keyfunc(v)
ifilter()   pred, seq           elements of seq where pred(elem) is True    ifilter(lambda x: x%2, range(10)) --> 1 3 5 7 9
ifilterfalse()  pred, seq       elements of seq where pred(elem) is False   ifilterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8
islice()    seq, [start,] stop [, step] elements from seq[start:stop:step]  islice('ABCDEFG', 2, None) --> C D E F G
imap()      func, p, q, ...     func(p0, q0), func(p1, q1), ...             imap(pow, (2,3,10), (5,2,3)) --> 32 9 1000
starmap()   func, seq           func(*seq[0]), func(*seq[1]), ...           starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000
tee()       it, n               it1, it2 , ... itn splits one iterator into n
takewhile() pred, seq           seq[0], seq[1], until pred fails            takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4
izip()      p, q, ...           (p[0], q[0]), (p[1], q[1]), ...             izip('ABCD', 'xy') --> Ax By
izip_longest()  p, q, ...       (p[0], q[0]), (p[1], q[1]), ...             izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-

# (3)组合生成器
迭代器          参数               结果
product()       p, q, ... [repeat=1]        cartesian product, equivalent to a nested for-loop
permutations()  p[, r]                      r-length tuples, all possible orderings, no repeated elements
combinations()  p, r                        r-length tuples, in sorted order, no repeated elements
combinations_with_replacement() p, r        r-length tuples, in sorted order, with repeated elements
product('ABCD', repeat=2)                   AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
permutations('ABCD', 2)                     AB AC AD BA BC BD CA CB CD DA DB DC
combinations('ABCD', 2)                     AB AC AD BC BD CD
combinations_with_replacement('ABCD', 2)    AA AB AC AD BB BC BD CC CD DD

第一部分

  • itertools.count(start=0, step=1)
  • itertools.cycle(iterable)
  • itertools.repeat(object[, times])

第二部分

  • itertools.chain(*iterables)
  • itertools.compress(data, selectors)
  • itertools.dropwhile(predicate, iterable)
  • itertools.groupby(iterable[, key])
  • itertools.ifilter(predicate, iterable)
  • itertools.ifilterfalse(predicate, iterable)
  • itertools.islice(iterable, stop)
  • itertools.imap(function, *iterables)
  • itertools.starmap(function, iterable)
  • itertools.tee(iterable[, n=2])
  • itertools.takewhile(predicate, iterable)
  • itertools.izip(*iterables)
  • itertools.izip_longest(*iterables[, fillvalue])

第三部分

  • itertools.product(*iterables[, repeat])
  • itertools.permutations(iterable[, r])
  • itertools.combinations(iterable, r)
  • itertools.combinations_with_replacement(iterable, r)

第四部分

  • 扩展
  • 自定义扩展
  • 补充

路径文件

除了os,还有Path,代码:

import os

os.path.abspath(path)	#返回绝对路径
os.path.basename(path)	#返回文件名
os.path.commonprefix(list)	#返回list(多个路径)中,所有path共有的最长的路径
os.path.dirname(path)	#返回文件路径
os.path.exists(path)	#路径存在则返回True,路径损坏返回False
# 判断是否已经存在该目录
if not os.path.exists(File_Path):
    # 目录不存在,进行创建操作
    os.mkdir(File_Path) # 创建一层目录
    os.makedirs(File_Path) #使用os.makedirs()方法创建多层目录
    print("目录新建成功:" + File_Path)
os.path.lexists	#路径存在则返回True,路径损坏也返回True
os.path.expanduser(path)	#把path中包含的"~"和"~user"转换成用户目录
os.path.expandvars(path)	#根据环境变量的值替换path中包含的"$name"和"${name}"
os.path.getatime(path)	#返回最近访问时间(浮点型秒数)
os.path.getmtime(path)	#返回最近文件修改时间
os.path.getctime(path)	#返回文件 path 创建时间
os.path.getsize(path)	#返回文件大小,如果文件不存在就返回错误
os.path.isabs(path)	#判断是否为绝对路径
os.path.isfile(path)	#判断路径是否为文件
os.path.isdir(path)	#判断路径是否为目录
os.path.islink(path)	#判断路径是否为链接
os.path.ismount(path)	#判断路径是否为挂载点
os.path.join(path1[, path2[, ...]])	#把目录和文件名合成一个路径
os.path.normcase(path)	#转换path的大小写和斜杠
os.path.normpath(path)	#规范path字符串形式
os.path.realpath(path)	#返回path的真实路径
os.path.relpath(path[, start])	#从start开始计算相对路径
os.path.samefile(path1, path2)	#判断目录或文件是否相同
os.path.sameopenfile(fp1, fp2)	#判断fp1和fp2是否指向同一文件
os.path.samestat(stat1, stat2)	#判断stat tuple stat1和stat2是否指向同一个文件
os.path.split(path)	#把路径分割成 dirname 和 basename,返回一个元组
os.path.splitdrive(path)	#一般用在 windows 下,返回驱动器名和路径组成的元组
os.path.splitext(path)	#分割路径中的文件名与拓展名
os.path.splitunc(path)	#把路径分割为加载点与文件
os.path.walk(path, visit, arg)	#遍历path,进入每个目录都调用visit函数,visit函数必须有3个参数(arg, dirname, names),dirname表示当前目录的目录名,names代表当前目录下的所有文件名,args则为walk的第三个参数
os.path.supports_unicode_filenames	#设置是否支持unicode路径名

from pathlib import Path
p.cwd() # 获取当前路径
p.stat()  # 获取当前文件的信息
p.exists()  # 判断当前路径是否是文件或者文件夹
p.glob(filename)  # 获取路径下的所有符合filename的文件,返回一个generator
p.rglob(filename)  # 与上面类似,只不过是返回路径中所有子文件夹的符合filename的文件
p.is_dir()  # 判断该路径是否是文件夹
p.is_file()  # 判断该路径是否是文件
p.iterdir()  #当path为文件夹时,通过yield产生path文件夹下的所有文件、文件夹路径的迭代器
P.mkdir(parents=Fasle)  # 根据路径创建文件夹,parents=True时,会依次创建路径中间缺少的文件夹
p_news = p/'new_dirs/new_dir'
p_news.mkdir(parents=True)
os.mkdir() # 创建路径中的最后一级目录,即:只创建path_03目录,而如果之前的目录不存在并且也需要创建的话,就会报错。os.makedirs() # 创建多层目录,即:Test,path_01,path_02,path_03如果都不存在的话,会自动创建,但是如果path_03也就是最后一级目录已存在的话就会抛出FileExistsError异常。
P.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)  #类似于open()函数
p.rename(target)  # 当target是string时,重命名文件或文件夹;当target是Path时,重命名并移动文件或文件夹
p.replace(target)  # 重命名当前文件或文件夹,如果target所指示的文件或文件夹已存在,则覆盖原文件
p.parent(),p.parents()  # parent获取path的上级路径,parents获取path的所有上级路径
p.is_absolute()  # 判断path是否是绝对路径
p.match(pattern)  # 判断path是否满足pattern
p.rmdir()  # 当path为空文件夹的时候,删除该文件夹
p.name  # 获取path文件名
p.suffix  # 获取path文件后缀

遍历文件

分情形

  • 只有文件时,用 os.listdir
  • 包含目录时,用 os.walk
import os
path = r'E:\test'
# os.listdir
filenames=os.listdir(path)
print(filenames)
# os.walk
for filepath,dirnames,filenames in os.walk(path):
    for filename in filenames:
        print(os.path.join(filepath,filename))

正则表达式

【2010-7-4】Python正则表达式指南

正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。

正则解析

正则表达式进行匹配的流程

  • 正则表达式文本 –引擎–> 正则表达式对象 –匹配–> 被匹配文本 –> 匹配结果

Python支持的正则表达式元字符和语法:

()         # 分组,保存,编号逐个+1
(?P<name>) # 分组别名 name
\<num>     # 引用编号num的分组匹配到字符串
(?:)    # 不分组
(?iLmsux) # 匹配模式,每个字母一个
(?#) # 注释
# 前后查找的操作符:
(?=)	# 正向前查找
(?!)	# 负向前查找
(?<=)	# 正向后查找
(?<!)	# 负向后查找
(?(id/name)yes|no) # 条件匹配,如果编号id/别名name的组匹配成功,则匹配yes,否则no

正则解析, 源自Python正则表达式

re模块

Python的re模块主要定义了9个常量、12个函数、1个异常 re.compile模式:

  • re.I(re.IGNORECASE): 忽略大小写(括号内是完整写法,下同)
  • M(MULTILINE): 多行模式,改变’^’和’$’的行为(参见上图)
  • S(DOTALL): 点任意匹配模式,改变’.’的行为
  • L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定
  • U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性
  • X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释

正则表达re模块共有12个函数,概览:

  • search、match、fullmatch:查找一个匹配项
    • search:查找任意位置的匹配项
    • match:必须从字符串开头匹配
    • fullmatch:整个字符串与正则完全匹配
    • 以上三种方法返回match对象,几个常用的方法如下:
      • m.start() 返回匹配到的字符串的起使字符在原始字符串的索引
      • m.end() 返回匹配到的字符串的结尾字符在原始字符串的索引
      • m.group() 返回指定组的匹配结果
      • m.groups() 返回所有组的匹配结果
      • m.span() 以元组形式给出对应组的开始和结束位置
  • findallfinditer:查找多个匹配项
    • 1)findall: 从字符串任意位置查找,返回一个列表
    • 2)finditer:从字符串任意位置查找,返回一个迭代器,适用大量匹配项
  • split: 分割
    • re.split(r”[;:]”,”var A;B;C:integer;”)
  • subsubn:替换
    • re.sub(‘\d+’,’*’,’aaa34bvsa56s’,count=1)
    • 与 re.sub函数 功能一致,只不过返回一个元组 (字符串, 替换次数)
    • re.subn(‘\d’,’*’,’aaa34bvsa56s’)#每个数字都替换一次
  • compile函数、template函数: 将正则表达式的样式编译为一个 正则表达式对象

正则语法总结:

代码示例

import re

p = re.compile(r'(\w+) (\w+)(?P<sign>.*)', re.DOTALL)

pattern = r'\d+' # 定义正则表达式
text = "Hello 123 World 456" # 定义目标字符串
match = re.match(pattern, text)
match = re.search(pattern, text) # 使用search()方法搜索匹配的子串
# 判断是否命中
if match:
    print("找到匹配的子串:", match.group())  # 输出:找到匹配的子串: 123
else:
    print("未找到匹配的子串")

print ("正则表达式:", p.pattern) # (\w+) (\w+)(?P<sign>.*)
print ("匹配模式:", p.flags) # 48
print ("分组数目:", p.groups) #  3
print ("别名分组:", p.groupindex) # {'sign': 3}

m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')

print ("原始字符串:", m.string) # 要匹配的原始字符串
print ("原始正则表达式:", m.re) # 要匹配的原始正则
print ("索引范围: [{}, {}]".format(m.pos, m.endpos)) # [0, 12]
print ("捕获的最后一个分组索引:", m.lastindex) # 3
print ("捕获的最后一个分组别名:", m.lastgroup) # sign
print ("获取多个组:", m.group(1, 2)) # ('hello', 'world')
print ("获取全部组(元组):", m.groups()) # ('hello', 'world', '!')
print ("获取全部别名组(字典):", m.groupdict()) # {'sign': '!'}
print ("第2个字符串范围: [{},{}]".format(m.start(2), m.end(2))) # (6, 11)
print ("第2个字符串范围:", m.span(2)) # (6, 11)
print ("局部重排:", m.expand(r'\2 \1\3')) # world hello!
# [2022-10-14] 正则替换,全部替换 等效于 repalce 函数
s="进入IM=>初始faq(te)=>点击faq(0:房源信息不真实,怎么举报?)"
out = re.sub(r'\(.*?\)', '', s, count=1) # 只替换一次
out = re.subn(r'\(.*?\)', '', s)  # 返回tuple,(替换后的内容, 替换次数) ('进入IM=>初始faq=>点击faq', 2)
re.split9(r'=>', s) # 正则分割
# 槽位字典
pattern_city = re.compile('(?P<city>西安|武汉|青岛|广州|重庆)市?(?P<district>城六|咸阳|高陵|长安)区?',re.X)
pattern_loan = re.compile('(?P<loan>全款|公积金|商业|组合)贷款?')

pattern_info = re.compile('(?P<city>西安|武汉|青岛|广州|重庆)市?(?P<district>城六|咸阳|高陵|长安)区?(?P<loan>全款|公积金|商业|组合)贷款?',re.X)

slot_dict = {'city': '-', 'loan':'-'}

query = '西安长安区商业贷款'
res1 = pattern_info.match(query)
print(res1) # match对象
print(res1.groupdict()) # 返回字典(含别名)
res2 = pattern_info.findall(query) # 返回列表
print(res2)

regex模块

【2021-8-9】Python的regex模块——更强大的正则表达式引擎

内置的re模块不支持一些高级特性,比如下面这几个:

  • 固化分组 Atomic grouping
  • 占有优先量词 Possessive quantifiers
  • 可变长度的逆序环视 Variable-length lookbehind
  • 递归匹配 Recursive patterns
  • (起始/继续)位置锚\G Search anchor 2009年,Matthew Barnett写了一个更强大正则表达式引擎——regex模块,这是一个Python的第三方模块。
  • 安装:pip install regex 除了上面这几个高级特性,还有很多有趣、有用的东西,本文大致介绍一下,很多内容取自regex的文档。

regex基本兼容re模块,现有的程序可以很容易切换到regex模块:import regex as re;

regex有Version 0和Version 1两个工作模式,其中的Version 0基本兼容现有的re模块,以下是区别:

  Version 0 (基本兼容re模块) Version 1
启用方法 默认模式 设置regex.DEFAULT_VERSION = regex.V1,或者在表达式里写上(?V1)
内联flag 内联flag只能作用于整个表达式,不可关闭。 内联flag可以作用于局部表达式,可以关闭。
字符组 只支持简单的字符组。 字符组里可以有嵌套的集合,也可以做集合运算(并集、交集、差集、对称差集)。
大小写匹配 默认支持普通的Unicode字符大小写,如Й可匹配й。这与Python3里re模块的默认行为相同。 默认支持完整的Unicode字符大小写,如ss可匹配ß。可惜不支持Unicode组合字符与单一字符的大小写匹配,所以感觉这个特性不太实用。可以在表达式里写上(?-f)关闭此特性。

V1模式默认开启.FULLCASE(完整的忽略大小写匹配)。通常用不上这个,所以在忽略大小写匹配时用(?-f)关闭.FULLCASE即可,这样速度更快一点,例如:(?i-f)tag

Version 0模式和re模块不兼容之处

  • \s的范围。
    • 在re中,\s在这一带的范围是0x09 ~ 0x0D,0x1C ~ 0x1E。
    • 在regex中,\s采用的是Unicode 6.3+标准的\p{Whitespace},在这一带的范围有所缩小,只有:0x09 ~ 0x0D。
  • regex有模糊匹配(fuzzy matching)功能,能针对字符进行模糊匹配,提供了3种模糊匹配:
    • i,模糊插入
    • d,模糊删除
    • s,模糊替换
    • 以及e,包括以上三种模糊
import regex as re

# 两次匹配都是把捕获到的内容放到编号为1捕获组中,在某些情况很方便
regex.match(r"(?|(first)|(second))", "first").groups() # ('first',)
regex.match(r"(?|(first)|(second))", "second").groups() # ('second',)

# 局部范围的flag控制。在re模块,flag只能作用于整个表达式,现在可以作用于局部范围;全局范围的flag控制,如 (?si-f)<B>good</B>
# (?i:)是打开忽略大小写,(?-i:)则是关闭忽略大小写。
# 如果有多个flag挨着写既可,如(?is-f:),减号左边的是打开,减号右边的是关闭。
regex.search(r"<B>(?i:good)</B>", "<B>GOOD</B>") # <regex.Match object; span=(0, 11), match='<B>GOOD</B>'>

# 可重复使用的子句
regex.search(r'(?(DEFINE)(?P<quant>\d+)(?P<item>\w+))(?&quant) (?&item)', '5 elephants') # <regex.Match object; span=(0, 11), match='5 elephants'> , 此例中,定义之后,(?&quant)表示\d+,(?&item)表示\w+。如果子句很复杂,能省不少事。

# 模糊匹配:匹配hello,其中最多容许有两个字符的错误。
regex.findall('(?:hello){s<=2}', 'hallo')
# ['hallo']

# 部分匹配。可用于验证用户输入,当输入不合法字符时,立刻给出提示。

# 编译好的正则式用pickle存储到文件,直接pickle.load()就不必再次编译,节省时间

参数处理

传参方式

【2018-6-28】Python的4种传值的方式:

  • 必选传参 func(param)
    • def func(a,b) 调用时要严格一致(数量和顺序)
  • 默认传参func(param=value)
    • def func(a,b=3)
    • 默认参数要跟在必选参数后面
    • 默认参数顺序不一致时需要指定参数名!
  • 可选传参func(*param)
    • def func(a,b=3,*c)
    • 参数个数不确定时使用可选参数,以tuple元组形式调用(列表也可以)
      • c=[ 2,5,1 ] #或(2,5,1)
    • func(2,b=2,*c)
    • *号作用:
      • 定义函数时,将传入的参数转成元组
      • 调用时将元组解开成单个元素
  • 关键字传参func(**param)
    • def func(a,b=3,*c,**d)
    • def func(*a,**b)接受任意类型参数
    • 多个参数,默认值,dict形式传递
    • d = {“a”:1,”b”:2,”c”:3}
    • func(1,10,11,**d)
    • **号作用:参考:Python 函数定义以及参数传递
      • 定义函数时,将传入的参数转成字典
      • 调用时将字典解开成单个元素

用户参数

Python获取用户参数的几种方式

  • input()函数
  • sys.argv模块
  • argparse模块
  • getopt模块

input函数

python2中input与python3的input不一样, python2中raw_input 与 python3 中的input 比较类似

  • raw_input(): python2独有
    • 小括号中放入的是提示信息,用来在获取数据之前给用户的一个简单提示
    • 在从键盘获取了数据以后,会存放到等号右边的变量中
    • 把用户输入的任何值都作为字符串来对待
  • input()函数与raw_input()类似,但其接受的输入必须是表达式

# python 2
data = input() # 输入23、"hello"(字符串必须有引号,否则报错)
data = input('plase: ')
data = raw_input("hello, please input") # 不限类型
# Python 3
data = input("please enter the data: ")
# 无论输入的是什么,最终data的数据类型都是“str”
print(data)

sys.argv

sys标准库最常用的是sys.argv,用来调用命令行参数,这些命令行参数以链表形式存储于 sys 模块的 argv 变量。一律是字符串形式

  • sys.argv[0]表示脚本本身
import sys
# python test.py one two three
print(sys.argv) # ['test.py', 'one', 'two', 'three']
print(sys.argv[0]) # 脚本名称
print(os.path.abspath(sys.argv[0])) # 脚本绝对路径

argparse

parser.add_argument 方法的type参数理论上可以是任何合法的类型

  • 但有些参数传入格式比较麻烦,例如 list,所以一般用 bool, int, str, float 这些基本类型
  • 更复杂的需求可以通过str传入,然后手动解析。
  • bool类型的解析比较特殊,传入任何值都会被解析成True,传入空值时才为False
  • 官方指南
import argparse

parser = argparse.ArgumentParser()
parser.description='please enter two parameters a and b ...'
# add_argument 指定命令行选项 help 设置提示语
parser.add_argument("-a", "--inputA", help="this is parameter a", dest="argA", type=int, default="0")
parser.add_argument("-b", "--inputB", help="this is parameter b",  type=int, default="1")
parser.add_argument("-c", "--inputC", help="this is parameter c",  action="store_true") # bull 变量,只要存在就是true
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2], help="increase output verbosity") # 多种候选取值
parser.remove_argument('--autotuning') # 删除参数
args = parser.parse_args()
print("parameter a is :",args.argA) # 使用自定义的别名, inputA --> argA
print("parameter b is :",args.inputB) # 使用系统默认变量名, inputB
if args.inputC:
    print("inputC turned on")
python test.py -h # 参数说明
python test.py -a 2 -b 3

tf.app.run

TensorFlow参数处理

tensorflow只提供以下几种方法:4种方法,分别对应str, int,bool,float类型的参数

  • tf.app.flags.DEFINE_string
  • tf.app.flags.DEFINE_integer
  • tf.app.flags.DEFINE_boolean
  • tf.app.flags.DEFINE_float 。 这里对bool的解析比较严格,传入1会被解析成True,其余任何值都会被解析成False。

脚本中需要定义一个接收一个参数的main方法:def main(_):,这个传入的参数是脚本名,一般用不到, 所以用下划线接收。

  • 以batch_size参数为例,传入这个参数时使用的名称为–batch_size,也就是说,中划线不会像在argparse 中一样被解析成下划线。

tf.app.run()会寻找并执行入口脚本的main方法。也只有在执行了tf.app.run()之后才能从FLAGS中取出参数。

  • 从它的签名来看,它也是可以自己指定需要执行的方法的,不一定非得叫main:
import tensorflow as tf

tf.app.flags.DEFINE_string('gpus', None, 'gpus to use')
tf.app.flags.DEFINE_integer('batch_size', 5, 'batch size')

FLAGS = tf.app.flags.FLAGS

def main(_):
    print(FLAGS.gpus)
    print(FLAGS.batch_size)

if __name__=="__main__":
    tf.app.run()

getopt

getopt.getopt(args, shortopts, longopts=[])

  • args指的是当前脚本接收的参数,它是一个列表,可以通过sys.argv获得
  • shortopts 是短参数,类似-v,-h这样的参数。短参数后面有冒号 “:” 表示该参数有输入值。
  • longopts 是长参数,类似–help,–version这样的参数。长参数后面带等号 “=” 表示参数有输入值

输入 -v 的时候打印脚本的版本信息,不执行脚本

通过getopt工具包实现

import sys
import socket
import getopt
import time
 
upFileName = ""
 
#服务端涵数
def server_handle(port):
    #创建服务端套接字
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #绑定IP和端口
    server.bind(('0.0.0.0', port))
    #监听
    server.listen(10)
    print("[*] Listening on 0.0.0.0:%d"%port)
    while True:
        client_socket, addr = server.accept()
        download_file(client_socket)
#客户端涵数
def client_handle(target, port):
    #创建socket连接
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((target, port))
    #将字符串文件名转成字节发送,socket套接字是要用byte传输
    client.send(upFileName.encode('utf-8'))
    time.sleep(1)
    upload_file(client)
    #传输完文件后关闭文件流
    client.close()
 
#文件下载涵数
def download_file(socket):
    #读取socket传过来的文件名数据
    fileName = socket.recv(1024)
 
    #因为socket传过来的是字节码,所以这里需要转成字符串
    fileName = fileName.decode()
    print("[*]Receive the file:%s"%fileName)
 
    #定义一个byte型的变量
    fileBuffer = "".encode('utf-8')
 
    #循环从socket中读取数据
    while True:
        #一次读取1024个字节
        data = socket.recv(1024)
        if data:
            #读取的数据累加保存在变量中
            fileBuffer += data
        else:
            #如果读到没有数据了就跳出结束
            break;
 
    #以写字节的形式打开一个文件
    f = open("_"+fileName, 'wb')
    #写文件
    f.write(fileBuffer)
    #关闭打开的文件
    f.close()
 
#文件上传涵数
def upload_file(socket):
    #以读byte字节的形式打开文件
    f = open(upFileName, 'rb')
 
    #读取文件
    data = f.read()
 
    #关闭打开文件
    f.close()
 
    #发送文件数据
    socket.send(data)
 
#定义usage涵数(命令行提示信息)
def usage():
    print("help info: python upload.py -h")
    print("client: python upload.py -t [target] -p [port] -u [uploadfile]")
    print("server: python filename.py -lp [port]")
    sys.exit()
 
def main():
    global upFileName   #文件名
    target = ""     #目标IP
    port = 0        #目标端口
    help = False    #显示帮助信息
    listen = False  #是否监听
 
    #利用getopt模块获取命令行参数
    opts, args = getopt.getopt(sys.argv[1:], "t:p:u:hl")
    for o, a in opts:
        if o == "-t":
            target = a;
        elif o == "-p":
            port = int(a)
        elif o == "-u":
            upFileName = a
        elif o == "-h":
            help = True
        elif o == "-l":
            listen = True
        else:
            #断言,传入的参数有误
            assert False, "Unhandled Option"
    #打印帮助信息
    if help:
        usage()
 
    #区分客户端和服务端
    if listen:
        server_handle(port)
    else:
        client_handle(target, port)
 
if __name__ == "__main__":
    main()

环境变量

环境变量已经存在系统中,python脚本中直接通过 os.getenv(‘ENV_NAME’) 就能拿到指定的环境变量。

# demo.py
import os

VAR1 = os.getenv('VAR1')
VAR2 = os.getenv('VAR2')

print(f"var1:{VAR1}")
print(f"var2:{VAR2}")

# os.environ 是环境变量的字典,但类型并不是 <class 'dict'>,不是所有字典的函数都能用
os.environ.keys() # 主目录下所有的 key
# 常见变量——win
os.environ['HOMEPATH']:当前用户主目录
os.environ['TEMP']:临时目录路径
os.environ["PATHEXT"]:可执行文件
os.environ['SYSTEMROOT']:系统主目录
os.environ['LOGONSERVER']:机器名
os.environ['PROMPT']:设置提示符
# 常见变量——linux
os.environ['USER']:当前使用用户
os.environ['LC_COLLATE']:路径扩展的结果排序时的字母顺序
os.environ['SHELL']:使用shell的类型
os.environ['LAN']:使用的语言
os.environ['SSH_AUTH_SOCK']:ssh的执行路径

# 设置环境变量
os.environ['环境变量名称']='环境变量值' #其中key和value均为string类型
os.putenv('环境变量名称', '环境变量值')
os.environ.setdefault('环境变量名称', '环境变量值')
# 修改
os.environ['环境变量名称']='新环境变量值'
# 获取
os.environ['环境变量名称']
os.getenv('环境变量名称')
os.environ.get('环境变量名称', '默认值')	#默认值可给可不给,环境变量不存在返回默认值
print(os.environ.get("HOME"))
print(os.environ.get("HOME", "default"))	#环境变量HOME不存在,返回	default
# 删除
del os.environ['环境变量名称']
del(os.environ['环境变量名称'])
# 判断是否存在
'环境变量值' in os.environ   # 存在返回 True,不存在返回 False

除此以外,还可以在 python 运行时指定环境变量。

# 临时环境变量
VAR1="test" VAR2=12 python demo.py

脚本可以拿到环境变量,但是在系统中用 env 命令是查不到的。export 是临时环境变量,只在当前 shell 有效。而上面这种设置环境变量的方式只对当前脚本有效。

Shell+Python 参数传递

【2024-3-16】变量传递

python -> shell

import os 

var=123 # or var='123' 
# (1) 环境变量
os.environ['var']=str(var)  # environ的键值必须是字符串  
os.system('echo $var')  
# (2) 字符串连接
os.system('echo ' + path)
# (3) 管道
os.popen('wc -c', 'w').write(var) 
# (4) 文件
output = open('/tmp/mytxt', 'w') 
output.write(S)      #把字符串S写入文件 
output.writelines(L) #将列表L中所有的行字符串写到文件中 
output.close()
# (5) 重定向
buf = open('/root/a.txt', 'w') 
print >> buf, '123\n', 'abc'

shell → Python

  • shell 中使用 export 关键词
  • Python 中使用 os.getenv 或 os.popen
#www='wangqiwen' # 无法传递
export www='wangqiwen'
python t.py
echo "python 调用完毕"

t.py 内容

# coding:utf8

import os
# (1) 环境变量
a = os.getenv('www', '空')
print('python内读取环境变量:'+a)
# (2) 管道
b = os.popen('echo $www').read()
print('python内读取环境变量:'+b)
# (3) commands
import commands 
var=commands.getoutput('echo $www')       #输出结果  
var=commands.getstatusoutput('echo $www') #退出状态和输出结果  
# (4) 文件方式
input = open('/tmp/mytxt', 'r') 
S = input.read( )      #把整个文件读到一个字符串中 
S = input.readline( )  #读下一行(越过行结束标志) 
L = input.readlines( ) #读取整个文件到一个行字符串的列表中  

密码输入

getpass模块提供平台无关的在命令行下输入密码的方法;

该模块主要提供:

  • 两个函数: getuser, getpass
  • 一个报警: GetPassWarning(当输入的密码可能会显示的时候抛出,该报警为UserWarning的一个子类)
import getpass

pwd = getpass.getpass() # 输入密码
pwd = getpass.getpass('请输入密码') # 输入密码
user = getpass.getuser() # 获取当前登录用户名

I/O 输入/输出

输出

【2018-6-13】不换行:

# Python2:加逗号
print x,
# Python3:加end
print(x,end="") 

彩色显示:

print('This is a \033[1;35m test \033[0m!')
print('This is a \033[1;32;43m test \033[0m!')
print('\033[1;33;44mThis is a test !\033[0m')

显示颜色格式:\033[显示方式;字体色;背景色m……[\033[0m]

-------------------------------------------
字体色     |       背景色     |      颜色描述
-------------------------------------------
30        |        40       |       黑色
31        |        41       |       红色
32        |        42       |       绿色
33        |        43       |       黃色
34        |        44       |       蓝色
35        |        45       |       紫红色
36        |        46       |       青蓝色
37        |        47       |       白色
-------------------------------------------

-------------------------------
显示方式     |      效果
-------------------------------
0           |     终端默认设置
1           |     高亮显示
4           |     使用下划线
5           |     闪烁
7           |     反白显示
8           |     不可见
-------------------------------

文件读取

测试文件 test.txt

hello,my friends!
This is python big data analysis,
let's study.

open方法

mode常用的模式:

  • r:表示文件只能读取
  • w:表示文件只能写入
  • a:表示打开文件,在原有内容的基础上追加内容,在末尾写入
  • w+:表示可以对文件进行读写双重操作

如果是字节(二进制)形式读写文件,在mode参数中追加’b’即可

data_file = 'test.txt'
f = open(data_file,'w')
f = open(data_file,'a')
f = open(data_file,'w+')
f = open(data_file,'bw+')
f = open(data_file,'bw+', encoding='utf-8') # 指定编码方式
f.close() # 关闭文件

# 安全方式,不用单独close
with open(data_file, 'w') as f:
    content = f.read() # 一次性读取所有内容
    pass
# (1)readline 从文件中读取整行,包括换行符'\n'。
# readline方法会记住上一个readline函数读取的位置,接着读取下一行。
f.readline() # hello,my friends!
f.readline(size=5) # 指定读取大小: This (第二行的前5个字节)
# (2)readlines方法读取所有行,返回列表。
print(f.readlines()) # ['hello,my friends!', 'This is python big data analysis,', 'let's study.']

# (3) 读写位置重置 seek
# (0,0) 开头位置
# (0,1) 当前位置
# (0,2) 末尾位置
f.readline()
f.seek(0,0)
f.readline() # 文件开头, 不再是下一行

write 方法

with open(data_file, 'w') as f:
    f.write('hello,my friends!\nthis is python big data analysis') # 不会换行
    f.close()
    # 清空文件
    # f.truncate(0) # 0表示从开始位置清除,即整体清空
# 删除文件
import os
os.remove(data_file)

小文件

【2017-11-28】Python读取大文件(GB)

  • read() 方法是一次性把文件的内容以字符串的方式读到内存, 放到一个字符串变量中
  • 调用readline()可以每次读取一行内容
    • readlines()的方法是一次性读取所有内容, 并按行生成一个list 

因为read()和readlines()是一次性把文件加载到内存, 如果文件较大, 甚至比内存的大小还大, 内存就会爆掉。 所以,这两种方法只适合读取小文件。 

f = open('/Users/michael/test.txt', 'r')
f = open('/Users/michael/test.jpg', 'rb') # 二进制文件
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk') # 非utf8编码需要指定编码方式
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore') # 忽略个别编码错误
f.read() # 'Hello, world!', 一次性读取所有内容
read(1000) # 如果文件有10G,内存就爆了,于是有了这种限额读取方法
for line in f.readlines(): # 一次读一行
    print(line.strip()) # 把末尾的'\n'删掉
# 循序遍历
with open("text.txt","r") as f:
    for line in f:
        print(line)

#文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:
f.close()

安全写法

try:
    f = open('/path/to/file', 'r')
    print(f.read())
finally:
    if f:
        f.close()
# 简化版
with open('/path/to/file', 'r') as f:
    print(f.read())

大文件

实际工作中,会碰到读取10几G的大文件的需求, 比如说日志文件。 这时候就要用新的读取文件的方法。 这里提供两种方法, 有简单,有复杂,但基本原理都是一样的。 就是利用到生成器generator。 

  • 方法一:将文件切分成小段,每次处理完小段内容后,释放内存
    • 这里会使用yield生成自定义可迭代对象,即generator, 每一个带有yield的函数就是一个generator
  • 方法二:利用open(“”, “”)系统自带方法生成的迭代对象
    •  for line in f 这种用法是把文件对象f当作迭代对象, 系统将自动处理IO缓冲和内存管理, 更加pythonic
  • 方法二:fileinput 处理, 用到了Python的fileinput模块
# 方法一
def read_in_block(file_path):  
    BLOCK_SIZE = 1024  
    with open(file_path, "r") as f:  
        while True:  
            block = f.read(BLOCK_SIZE)  # 每次读取固定长度到内存缓冲区  
            if block:  
                yield block  
            else:  
                return  # 如果读取到文件末尾,则退出  
def test3():  
    file_path = "/tmp/test.log"  
    for block in read_in_block(file_path):  
        print block 
# 方法二
def test4():  
    with open("/tmp/test.log") as f:  
        for line in f:  
            print line  
# 方法三
import fileinput
for line in fileinput.input(['sum.log']):
    print line

tqdm 包

【2024-3-6】tqdm 模块是python进度条库, 主要分为两种运行模式

  • (1) 基于迭代对象运行: tqdm(iterator)
  • (2) 手动进行更新
import time
from tqdm import tqdm, trange

#trange(i)是tqdm(range(i))的一种简单写法
for i in trange(100):
    time.sleep(0.05)

for i in tqdm(range(100), desc='Processing'):
    time.sleep(0.05)

dic = ['a', 'b', 'c', 'd', 'e']
pbar = tqdm(dic)
for i in pbar:
    pbar.set_description('Processing '+i)
    time.sleep(0.2)
# 100%|██████████| 100/100 [00:06<00:00, 16.04it/s]
# Processing: 100%|██████████| 100/100 [00:06<00:00, 16.05it/s]
# Processing e: 100%|██████████| 5/5 [00:01<00:00,  4.69it/s]

手动进行更新

import time
from tqdm import tqdm

with tqdm(total=200) as pbar:
    pbar.set_description('Processing:')
    # total表示总的项目, 循环的次数20*10(每次更新数目) = 200(total)
    for i in range(20):
        # 进行动作, 这里是过0.1s
        time.sleep(0.1)
        # 进行进度更新, 这里设置10个
        pbar.update(10)
# 输出
# Processing:: 100%|██████████| 200/200 [00:02<00:00, 91.94it/s]

tqdm模块参数说明

class tqdm(object):
  """
  Decorate an iterable object, returning an iterator which acts exactly
  like the original iterable, but prints a dynamically updating
  progressbar every time a value is requested.
  """

  def __init__(self, iterable=None, desc=None, total=None, leave=False,
               file=sys.stderr, ncols=None, mininterval=0.1,
               maxinterval=10.0, miniters=None, ascii=None,
               disable=False, unit='it', unit_scale=False,
               dynamic_ncols=False, smoothing=0.3, nested=False,
               bar_format=None, initial=0, gui=False):

参数说明

  • iterable: 可迭代的对象, 在手动更新时不需要进行设置
  • desc: 字符串, 左边进度条描述文字
  • total: 总的项目数
  • leave: bool值, 迭代完成后是否保留进度条
  • file: 输出指向位置, 默认是终端, 一般不需要设置
  • ncols: 调整进度条宽度, 默认是根据环境自动调节长度, 如果设置为0, 就没有进度条, 只有输出的信息
  • unit: 描述处理项目的文字, 默认是’it’, 例如: 100 it/s, 处理照片的话设置为’img’ ,则为 100 img/s
  • unit_scale: 自动根据国际标准进行项目处理速度单位的换算, 例如 100000 it/s » 100k it/s

示例

import time
from tqdm import tqdm

# 发呆0.5s
def action():
    time.sleep(0.5)

with tqdm(total=100000, desc='Example', leave=True, ncols=100, unit='B', unit_scale=True) as pbar:
    for i in range(10):
        action() # 发呆0.5秒
        pbar.update(10000) # 更新发呆进度
# Example: 100%|███████████████████████████████████████████████████| 100k/100k [00:05<00:00, 19.6kB/s]

写文件

shell

python -u test.py > a.log # 直接输出
python a_script.py 2>&1 | tee a.log # tee 重定向

python 写文件


f = open('/Users/michael/test.txt', 'w')
f.write('Hello, world!')
f.close()

# 安全便捷方式
with open('/Users/michael/test.txt', 'w') as f: # 覆盖模式
    f.write('Hello, world!')
    f.write("I am really enjoying it!\n") # 多行文本,注意加换行符

with open("text.txt","a") as file: # 追加模式
    f.write("What I want to add on goes here")
    # print >> f, 'print something' # python 2
    print("hello", file=f) # 用print方法输出
    #print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

文档处理

pdf 读取

【2022-11-25】python文档工具,参考

pdfplumber,可以方便地获取pdf的各种信息,包括文本、表格、图表、尺寸等。

  • 1、它是一个纯python第三方库,适合python 3.x版本
  • 2、它用来查看pdf各类信息,能有效提取文本、表格
  • 3、它不支持修改或生成pdf,也不支持对pdf扫描件的处理
#!pip install pdfplumber
# 导入pdfplumber
import pdfplumber
# 读取pdf文件,保存为pdf实例
pdf =  pdfplumber.open("E:\\nba.pdf") 
# 通过pdfplumber.PDF类的metadata属性获取pdf信息
pdf.metadata
# 通过pdfplumber.PDF类的metadata属性获取pdf页数
len(pdf.pages)
first_page = pdf.pages[0] # 第一页pdfplumber.Page实例
print('页码:',first_page.page_number) # 查看页码
print('页宽:'first_page.width) # 查看页宽
print('页高:'first_page.height) # 查看页高
# 访问第二页
first_page = pdf.pages[1]
# 读取文本
text = first_page.extract_text()
print(text)
# 自动读取表格信息,返回列表
table = first_page.extract_table()
table
# 将列表转为df
table_df = pd.DataFrame(table_2[1:],columns=table_2[0])
# 保存excel
table_df.to_excel('test.xlsx')

飞书表格

【2023-10-19】python 飞书API调用——电子表格操作

输入的数据类型可以参考飞书官方的开发文档

#飞书文档的储存地址结构:https://企业地址/sheets/shtcnjGdHzBm7Qa85UXQYk9OPxh?sheet=402cb1     #一般来说sh开头为文档地址,sheet=后跟工作簿地址,这两块是代码需要引用的参数
import json
url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/shtcnxuRDo9QJDBCeowEAQqUIvd/values"     #写入的sh开头的文档地址,其他不变
header = {"Content-Type": "application/json", "Authorization": "Bearer " + str(tat)} #请求头
post_data = {"valueRange": {"range": "402cb1!C3:N8" ,"values": [[ "Hello", 1],[ "World", 1]]}}     #在402cb1这个工作簿内的单元格C3到N8写入内容为helloworld等内容
r2 = requests.put(url, data=json.dumps(post_data), headers=header)  #请求写入
print( r2.json()["msg"])  #输出来判断写入是否成功

# 写入公式
post_data = {"valueRange": {"range": "PSGHlz!I2:I2" ,"values": [[{"type":"formula","text":"=IFERROR((E2-E9)/E9,0)"}]]}}

url = "https://open.feishu.cn/open- apis/sheets/v2/spreadsheets/shtcnl9XOiwOCVWEtjHuXKeT3zd/insert_dimension_range"     #写入的sh开头的文档地址,其他不变
header = {"Content-Type": "application/json", "Authorization": "Bearer " + str(tat)} #请求头
post_data ={
    "dimension":{
        "sheetId":"PSGHlz",
        "majorDimension":"ROWS",#可选COLUMNS
        "startIndex":1,
        "endIndex":2
    },
    "inheritStyle":"AFTER"#可选BEFORE
}
r2 = requests.post(url, data=json.dumps(post_data), headers=header)  #请求写入
print( r2.json()["msg"])  #输出来判断写入是否成功


内存管理

python不同于java和c,不用事先声明变量类型而直接对变量进行赋值

  • 对象的类型内存地址分配都是在运行时确定的——动态类型

Python的内存管理主要有三种机制:

  • 引用计数机制
    • Python采用了类似Windows内核对象一样的方式来对内存进行管理。每一个对象,都维护这一个对指向该对对象的引用的计数
  • 垃圾回收机制
  • 内存池机制

a. 引用计数

  • 当给一个对象分配一个新名称或者将一个对象放入一个容器(列表、元组或字典)时,该对象的引用计数都会增加
  • 当使用del对对象显示销毁或者引用超出作用于或者被重新赋值时,该对象的引用计数就会减少

可以使用sys.getrefcount()函数来获取对象的当前引用计数。

  • 多数情况下,引用计数要比猜测的大的多。
  • 对于不可变数据(数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。  

    b. 垃圾回收

当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。

  • 当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。
  • 然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。
  • 为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。  

    c. 内存池机制

Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统

  • 1)Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
  • 2)Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的 malloc
  • 3)对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。

浅拷贝与深拷贝

【2018-5-25】Python中的对象之间赋值时是按引用传递的,如果需要拷贝对象,需要使用标准库中的copy模块。

  • 1、赋值语句只是传引用,相当于贴一个标签
  • 2、copy.copy 浅拷贝 只拷贝父对象(仅顶层),不会拷贝对象的内部的子对象。
  • 3、copy.deepcopy 深拷贝 拷贝对象及其子对象
import copy
a = [1,2,3,4,['a','b']]  #原始对象
b = a  #赋值,传对象的引用,联动

c = copy.copy(a)#浅拷贝,顶层元素不变,深层的list变动

d = copy.deepcopy(a)#深拷贝,完全复制

a.append(5)
a[4].append('c')

print 'a=',a # a= [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print 'b=',b # b= [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print 'c=',c # c= [1, 2, 3, 4, ['a', 'b', 'c']]
print 'd=',d # d= [1, 2, 3, 4, ['a', 'b']]

python代码执行过程可视化,见pythontutor

拷贝特殊情况:

  • 对于非容器类型(如数字、字符串、和其他’原子’类型的对象)没有拷贝这一说
    • 对于这些类型,”obj is copy.copy(obj)” 、”obj is copy.deepcopy(obj)”
  • 如果元组变量包含原子类型对象,则不能深拷贝

网络请求

request工具包

get方法

# -*- coding:utf-8 -*-
import requests
import json

headers = {'user-agent': 'my-app/0.0.1'}
payload = {'key1': 'value1', 'key2': 'value2'}

r = requests.get("http://httpbin.org/get", params=payload)
r = requests.get(url, headers=headers) # header

print(r.url) # 请求网址
print(r.text) # 返回内容
print(r.encoding) # 编码
# http://httpbin.org/get?key2=value2&key1=value1

post方法

  • 1、带数据的post
  • 2、带header的post
  • 3、带json的post
  • 4、带参数的post
  • 5、普通文件上传
  • 6、定制化文件上传
  • 7、多文件上传 返回信息:
# -*- coding:utf-8 -*-
import requests
import json
 
host = "http://httpbin.org/"
endpoint = "post"
url = ''.join([host,endpoint])
data = {'key1':'value1','key2':'value2'}
# ① 带数据的post
r = requests.post(url, data=data)
# ② 带header的post
headers = {"User-Agent":"test request headers"}
r = requests.post(url, headers=headers)
#response = r.json()
print (r.text)
# ③ 带json的post
data = {
    "sites": [
                { "name":"test" , "url":"www.test.com" },
                { "name":"google" , "url":"www.google.com" },
                { "name":"weibo" , "url":"www.weibo.com" }
    ]
}
r = requests.post(url, json=data)
# ④ 带参数的post
params = {'key1':'params1','key2':'params2'}
# r = requests.post(url)
r = requests.post(url, params=params)
# ⑤ 文件上传
files = {
            'file':open('test.txt','rb')
        }
r = requests.post(url,files=files)
# ⑥ 自定义文件名,文件类型、请求头
files = {
        'file':('test.png',open('test.png','rb'),'image/png')
}
r = requests.post(url,files=files)
print (r.text)
# ⑦ 多文件上传
files = [
    ('file1',('test.txt',open('test.txt', 'rb'))),
    ('file2', ('test.png', open('test.png', 'rb')))
    ]
r = requests.post(url,files=files)
print (r.text)
# ⑧ 流式上传
with open( 'test.txt' ) as f:
    r = requests.post(url,data = f)

实际案例,调用情绪识别接口

import requests
import json

url = f'http://test.speech.analysis.ke.com/inspections/capability'
## headers中添加上content-type这个参数,指定为json格式
headers = {'Content-Type': 'application/json'}
text = "测!!"
data = {"biz_id":"utopia","app_id":"utopia","create_time":"2021-07-29 15:51:06",
        "sentence":[
            {"sentence_id":1,"text":"谢谢"},
            {"sentence_id":2,"text":"你好"},
            {"sentence_id":3,"text":"不好"},
            {"sentence_id":4,"text":"怎么搞的"},
            {"sentence_id":5,"text":"这也太慢了吧"},
            {"sentence_id":6,"text":"妈的,到底管不管"},
            #{"sentence_id"2,"text":f"{text}"}
        ],
        "capability_id":"new_zhuangxiu_emotion_capability",
        "audio_key":"","extend":{"context":[]}}
## post的时候,将data字典形式的参数用json包转换成json格式。 
response = requests.post(url=url,data=json.dumps(data),headers=headers)
print(data)
# 接口说明:http://weapons.ke.com/project/6424/interface/api/632026
# 表扬>交流>中性>抱怨>不满>愤怒
# pos_appr 应该是 positive_appraisal   
# pos_comm 应该是 positive_communication  
# neu_neut 应该是neutral_neutral    
# neg_comp 应该是 negative_complaint    
# neg_dis 应该是negative_disappointed      
# neg_angr 应该是negative_angry
type_info = {"pos_appr":"(正向)表扬型",
 "pos_comm":"(正向)交流型",
 "neu_neut":"中性型",
 "neg_comp":"(负向)抱怨型",
  "neg_dis":"(负向)不满型",
  "neg_angr":"(负向)愤怒型"
}
res = eval(response.text)
print('-'*20)
print(res.keys())
print(res)
print('-'*20)
print('\n'.join(['%s\t%s\t%s\t%s'%(i['result'][0]['text'],i['result'][0]['type'], type_info.get(i['result'][0]['type'],'未知'), i['result'][0]['confidence']) for i in res['sentence']]))

配置文件

json

JSON 代表JavaScript对象符号。它是一种轻量级的数据交换格式,用于存储和交换数据。

  • 一种独立于语言的格式,容易理解,因为本质上自描述。

python中有一个内置包 json ,支持JSON数据。 JSON中的数据表示为quoted-strings,由大括号{}之间的键值映射组成。json.load(s) & json.dump(s)

  • load/dump: 文件
  • loads/dumps: 字符串
  • img

json 用法

import json

io = open("in.json","r") # 文件只有一个json串
string = io.read()
# json.loads(str)
dictionary = json.loads(string)
# or one-liner 或者 一步到位
# dictionary = json.loads(open("in.json","r").read())
print(dictionary)
# ----------------
dictionary = json.load(open("in.json","r"))
# 更规范写法
with open("temp.json",'r', encoding='UTF-8') as f:
     load_dict = json.load(f)
# ------ dict -> str -------
d = {'alpha': 1, 'beta': 2}
s = json.dumps(d)
s = json.dumps(d, ensure_ascii=False, indent=2) # 中文显示,终端输出美化
print(s)
open("out.json","w").write(s) # 字典 → 字符串
json.dump(d, open("out.json","w")) # 字典 → 文件

特殊格式

【2023-9-19】采坑!

raw = '{"a":"this is a\" a."}' # 这种字符串, json解析时报错,转义符\"起作用
raw = r'{"a":"this is a\" a."}' # 字符串前面加 r,使用原生字符串,不转义
json.loads(raw) # {'a': 'this is a" a.'}

# 【2023-11-30】JSONDecodeError: Invalid control character at: line 1 column 175 (char 174)
import json

json_str = '{"name": "Bobby \nHadz"}'
# 👇️ set strict to False, 忽略特殊字符: \t, \n, \r, \0
result = json.loads(json_str) # 报错!JSONDecodeError: Invalid control character at: line 1 column 175 (char 174)

# ① raw字符串
json_str = r'{"name": "Bobby \nHadz"}'
result = json.loads(json_str)
# ② strict模式
result = json.loads(json_str, strict=False) # 字符串中允许使用控制字符

print(result)  # 👉️ {'name': 'Bobby Hadz'}

非 json 格式字符串

# 【2024-3-18】 字符串中
# eval()将字符串转化为字典对象
s = "{'id': 'cc695906217', 'name': '种冲'}"
json.loads(s) # 出错,非json格式
eval(s) # 恢复成字典: {'id': 'cc695906217', 'name': '种冲'}
s = json.loads(json.dumps(eval(s))

jsonl

jsonl 格式

jsonl 格式的文件,每行是一个单独的json字符串

读取方法

  • ① json.loads 逐个解析
  • ② pandas的read_json方法
json.loads

【2023-5-2】jsonl 格式, JSON Lines,json数据集,常见于大模型数据集

  • 每行都是json字符串,\n换行
  • UTF-8编码
  • 后缀是 .jsonl
{"prompt": "<提示文本>", "completion": "<理想的生成文本>"}
{"prompt": "<提示文本>", "completion": "<理想的生成文本>"}
{"prompt": "<提示文本>", "completion": "<理想的生成文本>"}
import json

test_file = 'in.json' # 一条json一行

def load_json(path):
    return [json.loads(x) for x in open(path, encoding='utf-8')]

test_data = load_json(test_file)
pandas read_json
import pandas as pd
import json

# 不同格式的 json 读取
# ① 单行,columns和data分离
s = '{"index":[1,2,3],"columns":["a","b"],"data":[[1,3],[2,5],[6,9]]}'
df = pd.read_json(s, orient='split')
# ② 单行,list形式
s = '[{"a":1,"b":2},{"a":3,"b":4}]'
df = pd.read_json(s, orient='records')

【2024-2-29】jsonl 文件读取

  • Python2 执行报错,找不到文件
  • Python3 执行正常
# ③ jsonl 文件格式
data_file = '/Users/bytedance/work/data/dataset.jsonl'
df = pd.read_json(open(data_file, 'r'), lines=True)

# [2024-1-31] 
data_file = 'data/test_v1.json'
#df = pd.read_json(path_or_buf=data_file, lines=True)
df = pd.read_json(data_file, lines=True)
# 嵌套json结构经过pd解析后,可以直接按dict方式读取
# df['session'][0] 内容: cut_label, object_id, prompt(list)
df['object_id'] = df['session'].apply(lambda x: x['object_id'])
df['num'] = df['session'].apply(lambda x: len(x['prompt']))
df['prompt'] = df['session'].apply(lambda x: x['prompt'])
df['cut_label'] = df['session'].apply(lambda x: x['cut_label'])

json 修复

【2024-1-26】json_repair

  • 修复损坏的JSON文件,尤其是LLM输出的病态JSON文件】
  • A python module to repair broken JSON, very useful with LLMs
from json_repair import repair_json
try:
    good_json_string = repair_json(bad_json_string)
except Exception:
    # Not even this library could fix this JSON

You can use this library to completely replace json.loads():

import json_repair
try:
    decoded_object = json_repair.loads(json_string)
except Exception:
    # Not even this library could fix this JSON

or just

import json_repair
try:
    decoded_object = json_repair.repair_json(json_string, return_objects=True)
except Exception:
    # Not even this library could fix this JSON

【2024-1-26】实测代码

# coding:utf8
# git clone https://github.com/mangiucugna/json_repair.git
import os
os.sys.path.append('json_repair/src')
import json_repair

json_string = "{a:10, 'b':4, c:['x', y, 100]}"
print('校正前:', json_string)
try:
    decoded_object = json_repair.loads(json_string)
    print('校正后:', decoded_object)
except Exception as e:
    # Not even this library could fix this JSON
    print('撰写失败', e)

llama.cpp的grammars也提供json修复功能,只能在推理流程中控制

hjson

【2024-4-24】json 缺点: 不支持注释

HJSON 是 JSON 格式的扩展,增加了注释,并去掉多余的标识符。

  • 注释支持 单行 和 多行,也支持Python脚本的#注释。

安装

pip install hjson

示例

{
  // 注释
  // 可以使用 #, // 或者 /**/,
  // 数字
  key: 1
  // 字符串
  contains: everything on this line
  // 集合
  cool: {
    foo: 1
    bar: 2
  }
  // 数组
  list: [
    1,
    2,
  ]
  // 多行字符串
  realist:
    '''
    My half empty glass,
    I will fill your empty half.
    Now you are half full.
    '''
}

使用

import hjson

text = """{
  foo: a
  bar: 1
}"""

# 解析文件
r = hjson.loads(text)
print(r)
# 结果返回一个字典
# OrderedDict([('foo', 'a'), ('bar', 1)])

# 解析文件
CONFIG_FILE = 'config.hjson'
with open(CONFIG_FILE) as f:
    json_dic = hjson.load(f)
    print json_dic
# dict -> hjson
hjson.dumps({'foo': 'text', 'bar': (1, 2)})
# dict -> json
hjson.dumpsJSON(['foo', {'bar': ('baz', None, 1.0, 2)}])

yaml

YAML是JSON(源)的超集。每个有效的JSON文件也是一个有效的YAML文件。这意味着您拥有所有期望的类型:整数,浮点数,字符串,布尔值,空值。以及序列和图。

yaml介绍

  • YAML是一种直观的能够被电脑识别的的数据序列化格式,容易被人类阅读,并且容易和脚本语言交互。YAML类似于XML,但是语法比XML简单得多,对于转化成数组或可以hash的数据时是很简单有效的。
    1. 大小写敏感
    2. 使用缩进表示层级关系
    3. 缩进时不允许使用Tab,只允许使用空格
    4. 缩进的空格数目不重要,只要相同层级的元素左对齐即可
    5. # 表示注释,从它开始到行尾都被忽略
    6. >- 表示换行
  • 支持的数据类型
    • 字符串,整型,浮点型,布尔型,null,时间,日期
  • 主要特性,更多:Python YAML用法详解
    • & 锚点 和 * 引用
    • 强制转换,用!!实现
    • 同一个yaml文件中,可以用 — 来分段
    • 构造器(constructors)、表示器(representers)、解析器(resolvers )
    • 包含子文件:!include
  • 安装
    • pip install pyyaml

yaml示例

name: Tom Smith
name: 'Tom Smith' # str等效表达
# 长字符串(换行符非必须,仅为了好看) , > 
disclaimer: >
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    In nec urna pellentesque, imperdiet urna vitae, hendrerit
    odio. Donec porta aliquet laoreet. Sed viverra tempus fringilla.
# 多行字符串, | 相当于行间换行符\n
mail_signature: |
      Martin Thoma
      Tel. +49 123 4567

age: 37
list_by_square_bracets_0: 
  - foo
  - bar
list_by_square_bracets_1: [foo, bar] # list的等效表达

spouse:
    name: Jane Smith
    age: 25
map_by_curly_braces: {foo: bar, bar: baz} # dict的等效表达
children:
 - name: Jimmy Smith
   age: 15
 - name1: Jenny Smith
   age1: 12
# YAML支持11种写布尔值的方法
bool_a : yes # true/True
bool_b : no # false/False
# 三个-表示多文档,返回list,如 [{'foo': 'bar'}, {'fizz': 'buzz'}]
---
# 这个例子输出一个字典,其中value包括所有基本类型
str: "Hello World!"
int: 110
float: 3.141
boolean: true  # or false
None: null  # 也可以用 ~ 号来表示 null
time: 2016-09-22t11:43:30.20+08:00  # ISO8601,写法百度
date: 2016-09-22  # 同样ISO8601
name: &name 灰蓝 # 设置被引用字段别名
tester: *name # * 取引用内容(仅含值)
# 成对引用(锚)
localhost: &localhost1
    host: 127.0.0.1
user:
    <<: *localhost1 # <<表示按照K/V形式成对展开,合并键
    db: 8
# [2023-7-20] 换行表达
test_str: >-
   换行测试
   新的一行

a: !!str 3.14 # 转字符串
b: !!int "123" # 转int
# 复杂类型
tuple_example: !!python/tuple
  - 1337
  - 42
set_example: !!set {1337, 42}
date_example: !!timestamp 2020-12-31
  • 操作方法
import yaml

# 文件
f = open(r'config.yml')
# 字符串
f = '''
---
name: James
age: 20
---
name: Lily
age: 19
'''
y = yaml.load(f) 
y = yaml.load_all(f) # 多个yaml区域
for data in y:
    print(data)
# 转成yaml文档
obj1 = {'name': 'Silenthand Olleander',
            'race': 'Human',
            'traits': ['ONE_HAND', 'ONE_EYE']
}
obj2 = {"name": "James", "age": 20}
print(yaml.dump(obj1))
# 中文输出
import json
print(json.dumps(obj1, ensure_ascii=False, indent=2))
print(yaml.dump(d,default_flow_style=False, indent=2, allow_unicode=True))
f = open(r'out_config.yml','w')
print(yaml.dump(obj2,f))
yaml.dump_all([obj1, obj2], f) # 一次输出多个片区

【2023-7-24】错误提示

load() missing 1 required positional argument: ‘Loader’

原因: 5.4 版本后, yaml 读文件方法升级

  • Starting from pyyaml>=5.4, it doesn’t have any discovered critical vulnerabilities
  • if your YAML file contains just simple YAML (str, int, lists), try to use yaml.safe_load() instead of yaml.load(). And If you need FullLoader, you can use yaml.full_load().
import yaml

f = open('test.yml')
# ----- old -----
info = yaml.load(f) # 报错
y = yaml.load_all(f) # 多个yaml区域
# 解法1
info = yaml.load(ymlfile, Loader=yaml.Loader)
# 解法2
# ---- new -----
info = yaml.safe_load(f)
info = yaml.full_load(f)

高级功能

# === 文件 bar.yaml ====
- 3.6
- [1, 2, 3]

# === 文件 foo.yaml ====
a: 1
b:
    - 1.43
    - 543.55
# 包含别的文件
c: !include bar.yaml
  • python调用代码
import os
import yaml

class Loader(yaml.Loader):

    def __init__(self, stream):
        self._root = os.path.split(stream.name)[0]
        super(Loader, self).__init__(stream)

    def include(self, node):
        filename = os.path.join(self._root, self.construct_scalar(node))
        with open(filename, 'r') as f:
            return yaml.load(f, Loader)
# 添加新的语法标记
Loader.add_constructor('!include', Loader.include)

def load_yaml(yaml_file):
    """
    :param yaml_file:
    :return:
    """
    with open(yaml_file, 'r') as f:
        config = yaml.load(f, Loader)
    return config

y = load_yaml('foo.yaml')
print(yaml.dump(y))
  • 简洁版
import yaml
import os

def yaml_include(loader, node):
# Get the path out of the yaml file
	file_name = os.path.join(os.path.dirname(loader.name), node.value)
	with open(file_name) as inputfile:
		return yaml.load(inputfile)

yaml.add_constructor("!include", yaml_include)
stream = open('foo.yaml', 'r')
print(yaml.dump(yaml.load(stream)))
  • 或者

#!/usr/bin/python
import os.path
import yaml
 
class IncludeLoader(yaml.Loader):
    def __init__(self, *args, **kwargs):
        super(IncludeLoader, self).__init__(*args, **kwargs)
        self.add_constructor('!include', self._include)
        if 'root' in kwargs:
            self.root = kwargs['root']
        elif isinstance(self.stream, file):
            self.root = os.path.dirname(self.stream.name)
        else:
            self.root = os.path.curdir
 
    def _include(self, loader, node):
        oldRoot = self.root
        filename = os.path.join(self.root, loader.construct_scalar(node))
        self.root = os.path.dirname(filename)
        data = yaml.load(open(filename, 'r'))
        self.root = oldRoot
        return data
 
class Config(object):
    def __init__(self, path):
        config_file = open(path, 'r')
        self.config_content = yaml.load(config_file, IncludeLoader)
        config_file.closed
 
    def getConfigContent(self):
        return self.config_content

dotenv

Dotenv 是一个零依赖模块,从 .env 文件中读取环境变量

  • 以 k,v 方式读取作为环境变量
  • 像访问系统环境变量一样使用.env文件中的变量

安装

pip install -U python-dotenv

代码

from dotenv import load_dotenv, find_dotenv
from pathlib import Path  # Python 3.6+ only

# 一、自动搜索 .env 文件
load_dotenv(verbose=True)
# 二、与上面方式等价
load_dotenv(find_dotenv(), verbose=True)
# 三、或者指定 .env 文件位置
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path, verbose=True)
# 从流对象中加载
from io import StringIO     # Python2: from StringIO import StringIO
from dotenv import dotenv_values
filelike = StringIO('SPAM=EGGS\n')
filelike.seek(0)
parsed = dotenv_values(stream=filelike)
parsed['SPAM'] # 'EGGS'
# --- 使用 -----
import os
SECRET_KEY = os.getenv("EMAIL")
DATABASE_PASSWORD = os.getenv("DATABASE_PASSWORD")
# -----或--------
import dotenv
# from dotenv import load_dotenv, find_dotenv
from getpass import getpass

# 从当前目录或其父目录中的.env文件或指定的路径加载环境变量
dotenv.load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

ipython 加载

%load_ext dotenv # 加载扩展
%dotenv # 加载 env文件
%dotenv relative/or/absolute/path/to/.env # 制定文件
%dotenv -o # 覆盖原有变量
%dotenv -v # 输出提示信息

xml

【2023-8-11】XML 指可扩展标记语言(eXtensible Markup Language)

Python 有三种方法解析 XML,SAX,DOM,以及 ElementTree:

  1. SAX (simple API for XML )
    • Python 标准库包含 SAX 解析器,SAX 用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件。
  2. DOM(Document Object Model)
    • 将 XML 数据在内存中解析成一个树,通过对树的操作来操作XML。
  3. ElementTree(元素树)
    • ElementTree就像一个轻量级的DOM,具有方便友好的API。代码可用性好,速度快,消耗内存少。

注:

  • 因DOM需要将XML数据映射到内存中的树,一是比较慢,二是比较耗内存,而SAX流式读取XML文件,比较快,占用内存少,但需要用户实现回调函数(handler)。

python中用xml.dom.minidom来解析xml文件 refer

测试数据

  • data 里 包含多个 country
  • country 属性: name
  • country 子节点: rank, year, gdppc, neighbor
<?xml version="1.0"?> 
<data> 
  <country name="Singapore"> 
    <rank>4</rank> 
    <year>2011</year> 
    <gdppc>59900</gdppc> 
    <neighbor name="Malaysia" direction="N"/> 
  </country> 
  <country name="Panama"> 
    <rank>68</rank> 
    <year>2011</year> 
    <gdppc>13600</gdppc> 
    <neighbor name="Costa Rica" direction="W"/> 
    <neighbor name="Colombia" direction="E"/> 
  </country> 
</data> 

解析代码

#!/usr/bin/python 
#coding=utf-8 
  
from xml.dom.minidom import parse 
import xml.dom.minidom
  
# 使用minidom解析器打开XML文档 
DOMTree = xml.dom.minidom.parse("country.xml") 
Data = DOMTree.documentElement
# 判断是否包属性 name
if Data.hasAttribute("name"): 
  print "name element : %s" % Data.getAttribute("name") 

# 在集合中获取所有国家 
Countrys = Data.getElementsByTagName("country") 
  
# 打印每个国家的详细信息 
for Country in Countrys: 
  print "*****Country*****"
  # 获取属性
  if Country.hasAttribute("name"): 
    print "name: %s" % Country.getAttribute("name") 
  # 获取 子节点, 及其元素取值
  rank = Country.getElementsByTagName('rank')[0] 
  print "rank: %s" % rank.childNodes[0].data # 元素取值
  year = Country.getElementsByTagName('year')[0] 
  print "year: %s" % year.childNodes[0].data 
  gdppc = Country.getElementsByTagName('gdppc')[0] 
  print "gdppc: %s" % gdppc.childNodes[0].data 
  # 获取 子节点属性
  for neighbor in Country.getElementsByTagName("neighbor"):  
    print neighbor.tagName, ":", neighbor.getAttribute("name"), neighbor.getAttribute("direction") 

打日志

日志级别等级

  • CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET

logging

logging模块

#!coding:utf-8
import logging
import logging.handlers
import datetime, time

#logging    初始化工作
logger = logging.getLogger("zjlogger")
logger.setLevel(logging.DEBUG)

# 添加TimedRotatingFileHandler
# (1) 定义一个1秒换一次log文件的handler, 保留3个旧log文件
rf_handler = logging.handlers.TimedRotatingFileHandler(filename="all.log",when='S',interval=1, backupCount=3)
#handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')  # 实例化handler

# (2) 写入文件myapp.log,如果文件超过100个Bytes,仅保留5个文件。
handler = logging.handlers.RotatingFileHandler('myapp.log', maxBytes=100, backupCount=5)

rf_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))

#在控制台打印日志
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))

logger.addHandler(rf_handler)
logger.addHandler(handler)

# logging格式化输出
d={"a":1, "c":[3,1,2]}
logger.info("this is %s, %s", d['a'], d)

while True:
    # 不同日志等级
    logger.debug('debug message')
    logger.info('info message')
    logger.warning('warning message')
    logger.error('error message')
    logger.critical('critical message')
    time.sleep(1)

使用logging模块,参考:python logging模块详解

  • 日志级别等级: CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
# coding:utf8
import logging  
if __name__ == '__main__':
    #[2018-1-17]http://blog.csdn.net/zyz511919766/article/details/25136485/
    # 设置日志格式,日志文件(默认打到标准输出),模式(追加还是覆盖)
    # 日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
    #logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',datefmt='%a, %d %b %Y %H:%M:%S',filename='test.log',filemode='w')  
    logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',datefmt='%Y-%M-%d %H:%M:%S',filemode='w')  
    logging.debug('debug message')  
    logging.info('info message')  
    logging.critical('critical message')
    # 创建一个logger,不用调logging包
    log = logging.getLogger()
    log.debug('debug')
    # 日志等级大全
    log.debug('logger debug message')
    log.info('logger info message')
    log.warning('logger warning message')
    log.error('logger error message')
    log.critical('logger critical message')

【2024-3-20】设置北京时间

import logging
import datetime

def beijing(sec, what):
    beijing_time = datetime.datetime.now() + datetime.timedelta(hours=8)
    return beijing_time.timetuple()

logging.Formatter.converter = beijing

logging.basicConfig(
    format="%(asctime)s %(levelname)s: %(message)s",
    level=logging.INFO,
    datefmt="%Y-%m-%d %H:%M:%S",
)

logging.info('完成')

loguru模块

  • 【2021-3-30】不想手动再配置logging?那可以试试loguru
  • logging库采用模块化设计,虽然可以设置不同的 handler 来进行组合,但是在配置上通常较为繁琐;而且如果不是特别处理,在一些多线程或多进程的场景下使用 logging 还会导致日志记录会出现错乱或是丢失的情况。
  • loguru库不仅能够减少繁琐的配置过程还能实现和logging类似的功能,同时还能保证日志记录的线程进程安全,又能够和 logging 相兼容,并进一步追踪异常也能进行代码回溯。一个专为像我这样懒人而生日志记录库
  • logger 本身就是一个已经实例化好的对象,如果没有特殊的配置需求,那么自身就已经带有通用的配置参数;同时它的用法和 logging 库输出日志时的用法一致, 可配置部分相比于 logging 每次要导入特定的handler再设定一些formatter来说是更为「傻瓜化」了
  • 日志文件留存、压缩,甚至自动清理。可以通过对 rotation 、compression 和 retention 三个参数进行设定来满足: rotation 参数能够帮助我们将日志记录以大小、时间等方式进行分割或划分
#!pip install loguru
from loguru import logger

logger.debug("debug message"    ) 
logger.info("info level message 中文信息输出 ") 
logger.warning("warning level message") 
logger.critical("critical level message")

# 可配置
import os
 
logger.add(os.path.expanduser("~/Desktop/testlog.log"))
# 序列化配置:通过 serialize 参数将其转化成序列化的 json 格式,最后将导入类似于 MongoDB、ElasticSearch 这类数 NoSQL 数据库中用作后续的日志分析。
logger.add(os.path.expanduser("~/Desktop/testlog.log"), serialize=True)
# loguru 集成了一个名为 better_exceptions 的库,不仅能够将异常和错误记录,并且还能对异常进行追溯
logger.add(os.path.expanduser("~/Desktop/exception_log.log"), backtrace=True, diagnose=True)
# 与logging兼容
import logging.handlers
file_handler = logging.handlers.RotatingFileHandler(LOG_FILE, encoding="utf-8")
logger.add(file_handler)
# -------------
logger.info("hello, world!")

# 日志自动清理
LOG_DIR = os.path.expanduser("~/Desktop/logs")
LOG_FILE = os.path.join(LOG_DIR, "file_{time}.log")
if os.path.exits(LOG_DIR):
    os.mkdir(LOG_DIR)
# ①自动切分日志,大小、时间等方式进行分割或划分
logger.add(LOG_FILE, rotation = "200KB")
# ②分割文件的数量越来越多,可以进行压缩对日志进行压缩、留存
logger.add(LOG_FILE, rotation = "200KB", compression="zip")
# ③只想保留一段时间内的日志并对超期的日志进行删除;
#   当然对 retention 传入整数时,该参数表示的是所有文件的索引,而非要保留的文件数;只有两个时间最近的日志文件会被保留下来,其他都被直接清理掉了
logger.add(LOG_FILE, rotation="200KB",retention=1)
for n in range(10000):
    logger.info(f"test - {n}")
                                                                                                                       
  • loguru 还为输出的日志信息带上了不同的颜色样式(schema),使得结果更加美观

彩色日志

Python Colorama模块可跨多终端显示不同的颜色字体和背景,只需要导入colorama模块即可,不用再每次都像linux一样指定颜色。

Fore是针对字体颜色,Back是针对字体背景颜色,Style是针对字体格式

  • Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
  • Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
  • Style: DIM, NORMAL, BRIGHT, RESET_ALL

注意

  • 颜色RED,GREEN都需要大写,先指定是颜色和样式是针对字体还是字体背景,然后再添加颜色,颜色就是英文单词指定的颜色 

测试代码

!pip install colorama
from colorama import Fore, Back, Style

init(autoreset=True)    #  初始化,并且设置颜色设置自动恢复, 不用单独reset
print(Fore.RED + 'some red text')
print(Back.GREEN + 'and with a green background')
print(Style.DIM + 'and in dim text')
print(Style.RESET_ALL)
print('back to normal now')

# 记得要及时关闭colorma的作用范围
# 如果不关闭的话,后面所有的输出都会是你指定的颜色
print(Style.RESET_ALL)

转可执行包

给别人写了个python文件,功能实现了,但怎么交付呢?

两种方案:

  • 直接给python文件,然后让对方自行安装python。
  • 把python文件和python环境一起给对方,直接点点点即可。

【2021-11-23】pyinstaller 可以把python文件直接打包成可执行文件,符合需求。

# pip安装
pip install pyinstaller
pip install pyinstaller --trusted-host https://pypi.org --trusted-host https://files.pythonhosted.org

# 或源码安装
git clone https://github.com/dkw72n/pyinstaller.git
python setup.py install

pyinstaller用法:

  • -F 指定打包后只生成一个exe格式的文件
  • -D –onedir 创建一个目录,包含exe文件,但会依赖很多文件(默认选项)
  • -c –console, –nowindowed 使用控制台,无界面(默认)
  • -w –windowed, –noconsole 使用窗口,无控制台
  • -p 添加搜索路径,让其找到对应的库。
  • -i 改变生成程序的icon图标
# 打包
pyinstaller -F demo.py 
pyinstaller.py -F -p C:\python27; ..\demo.py  # 注意当前目录是我在下一级目录里
pyinstaller.py -F -p C:\python27; -i ..\a.ico ..\demo.py # icon图标

高级语法

Python三器:装饰器迭代器生成器

特殊函数

函数属性

default

【2023-3-9】python 将默认参数存储在函数对象之中,__defaults__ 属性中,此后每次调用函数默认参数都是最开始计算的那一个,而由于这个默认参数是可变对象,内容变了,下一次调用当然就是最新的值。

def foo(x=[]):
    x.append(1)
    return x
# foo.__defaults__ 属性中
print(foo()) # [1]
print(foo()) # [1, 1]

map, reduce和filter

map 可以用于对可遍历结构的每个元素执行同样的操作,批量操作:

map(lambda x: x**2, [1, 2, 3, 4])                 # [1, 4, 9, 16]
map(lambda x, y: x + y, [1, 2, 3], [5, 6, 7])   # [6, 8, 10]

reduce 则是对可遍历结构的元素按顺序进行两个输入参数的操作,并且每次的结果保存作为下次操作的第一个输入参数,还没有遍历的元素作为第二个输入参数。这样的结果就是把一串可遍历的值,减少(reduce)成一个对象:

reduce(lambda x, y: x + y, [1, 2, 3, 4])    # ((1+2)+3)+4=10

filter顾名思义,根据条件对可遍历结构进行筛选:

filter(lambda x: x % 2, [1, 2, 3, 4, 5])    # 筛选奇数,[1, 3, 5]

注意

  • 对于 filter 和 map ,在Python2中返回结果是列表,Python3中是生成器

列表生成(list comprehension)

列表生成是Python2.0中加入的一种语法,可以非常方便地用来生成列表和迭代器,比如上节中map的两个例子和filter的一个例子可以用列表生成重写为:

[x**2 for x in [1, 2, 3, 4]]                      # [1, 4, 9 16]
[sum(x) for x in zip([1, 2, 3], [5, 6, 7])] # [6, 8, 10]
[x for x in [1, 2, 3, 4, 5] if x % 2]       # [1, 3, 5]

callable

【2022-4-9】callable() 函数用于检查一个对象是否是可调用的。

  • 如果返回 True,object 仍然可能调用失败;
  • 但如果返回 False,调用对象 object 绝对不会成功。

对于函数方法lambda 函式以及实现了 __call__ 方法的类实例, 它都返回 True。

  • 简版:只要可以在一个对象的后面使用小括号来执行代码,那么这个对象就是callable对象
callable(0) # False
callable("runoob") # False
# ------ 函数 -------
def add(a, b):
    return a + b
callable(add)      # 函数返回 True
# ------- 类 --------
class A: # 类
    def method(self):
        return 0
callable(A)        # 类返回 True,因为定义过方法
# ------- 实例 -------
a = A()
callable(a)        # 没有实现 __call__, 返回 False

class B:
    def __call__(self):
        return 0
callable(B)    # True
b = B()
callable(b)    # 实现 __call__, 返回 True

absl库

absl 库全称是 Abseil Python Common Libraries。它原本是个C++库,后来被迁移到了Python上。

Abseil Python命令库

  • 这个absl库是建立Python应用的代码集合。
  • 从Google自己的Python代码基础上收集来的,被用于测试和工业中。

特点:

  • 简单的应用创建
  • 分布式命令行标志系统
  • 用户自定义模块和附加特征
  • 测试工具

安装方法:

  • pip install absl-py

示例:输入参数,name 代表名字,num_times 代表语句重复次数。name是必填参数,num_times是可选参数,默认值为1.

from absl import app
from absl import flags
 
FLAGS = flags.FLAGS # 用法和TensorFlow的FLAGS类似,具有谷歌独特的风格。
flags.DEFINE_string("name", None, "Your name.")
flags.DEFINE_integer("num_times", 1,
                     "Number of times to print greeting.")
 
# 指定必须输入的参数
flags.mark_flag_as_required("name")
 
def main(argv):
  del argv  # 无用
  for i in range(0, FLAGS.num_times):
    print('Hello, %s!' % FLAGS.name)

if __name__ == '__main__':
  app.run(main)  # 和tf.app.run()类似

代码:test.py

"""Test helper fot smoke_test.sh"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import sys

from absl import app
from absl import flags
from absl import logging

FlAGS = flags.FLAGS

flags.DEFINE_string('echo', None, 'Test to echo')
flags.DEFINE_integer('times', 10, 'how many times to print the string')


def main(argv):
    del argv # Unused
    print("Running under Python {0[0]}.{0[1]}.{0[2]}".format(sys.version_info),
          file = sys.stderr)
    logging.info('echo is %s', FlAGS.echo)
    for _ in range(FlAGS.times):
        print('Hello Python!')

if __name__ == '__main__':
    app.run(main)

终端命令:

  • python test.py –name = ‘David’ –times = 6

高阶函数

高阶函数的定义

  • 高阶函数其实就是参数为函数的函数
  • 或把函数作为结果返回的函数是高阶函数(higher-order function)。

回调函数

【2022-10-8】自从搞懂了回调函数,我对Python的理解上了一个台阶

把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,这就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

定义

  • 回调函数就是被作为参数传递的函数
# 高阶函数
def calculator(v1,v2,fn):
    result = fn(v1,v2)
    return result
# calculator的回调函数
def add(v1,v2):
    return v1 + v2
# 调用calculator,计算1+1
print(calculator(1,1,add))
  • calculator是高阶函数,而add是回调函数
  • add函数就被称为calculator的回调函数

回调函数和递归函数的区别

  • 回调函数是在一个函数中“回调函数”以参数的形式传入,并在该函数内部被调用。
  • 而递归函数是在一个函数中,调用了自己。

属性

【2022-3-22】python类属性介绍

使用类或实例直接操作类属性

  • 缺点:对类的属性没有操作控制规则,容易被人修改。

属性的定义:python中的属性其实是普通方法的衍生

操作类属性有三种方法:

  1. 使用 @property 装饰器操作类属性。
  2. 使用 类 或 实例 直接操作类属性(例如:obj.name,obj.age=18,del obj.age)
  3. 使用python内置函数操作属性。 属性存在的意义:
    • 1、访问属性时可以制造出和访问字段完全相同的假象,属性由方法衍生而来,如果Python中没有属性,方法完全可以代替其功能。
    • 2、定义属性可以动态获取某个属性值,属性值由属性对应的方式实现,应用更灵活。
    • 3、可以制定自己的属性规则,用于防止他人随意修改属性值。
#coding=utf-8

class Employee(object):
    #所有员工基类
    empCount = 0
    def __init__(self, name, salary) :
         #类的构造函数
         self.name = name
         self.salary = salary
         Employee.empCount += 1
    def displayCount(self) :
         #类方法
         print ("total employee ",Employee.empCount)
    def displayEmployee(self) :
         print ("name :",self.name , ", salary :", self.salary)

#创建Employee类的实例对象
emp1 = Employee("丽丽", 10000)
emp1.displayCount()
emp1.displayEmployee()
emp1.salary = 20000 #修改属性 salary
print (emp1.salary)
emp1.age = 25 #添加属性 age
print (emp1.age)
del emp1.age #删除 age属性
emp1.empCount=100
Employee.empCount=1000
# 结果:total employee  1
# name : 丽丽 , salary : 10000
# 20000
# 25

python内置函数操作属性。

  • 1)getattr(obj, name[, default]):访问对象的属性,如果存在返回对象属性的值,否则抛出AttributeError异常。
  • 2)hasattr(obj,name):检查是否存在某个属性,存在返回True,否则返回False。
  • 3)setattr(obj,name,value):设置一个属性。如果属性不存在,会创建一个新属性,该函数无返回值。若存在则更新这个值。
  • 4)delattr(obj, name):删除属性,如果属性不存在则抛出AttributeError异常,该函数也无返回值。
#encoding=utf-8

class Employee(object):
    #所有员工基类
    empCount=0
    def __init__(self,name,age,salary):
        #类的构造函数
        self.name=name
        self.salary=salary
        self.age=age
        Employee.empCount+=1
        
    def displayCount(self):
        #类方法
        print("total employee",Employee.empCount)
    
    def displayEmployee(self):
        print("name:",self.name,"age:",self.age,",salary:",self.salary)
          
#创建Employee类的实例对象
emp1=Employee("Rose",27,20000)

#判断实例对象是否存在某个属性,存在返回True,否则返回False
if hasattr(emp1,'name'):
    name_value=getattr(emp1,'name') #获取name属性值
    print( "name的属性值为:",name_value)
else:
    print ("员工属性不存在")

#给实例添加一个属性  
if hasattr(emp1,'tel'):   
    print ("员工属性已存在")
else:              
    setattr(emp1,'tel','17718533234') 
    t1=getattr(emp1,'tel')
    print("tel的属性值为:",t1) 
    setattr(emp1,'tel','15042622134')  
    t2=getattr(emp1,'tel')
    print("tel修改后的属性值为:",t2) 

#给实例删除一个属性 
if hasattr(emp1,'age'):     
    delattr(emp1,'age')    
else:              
  print ("员工tel属性不存在")

#验证属性是否删除成功  
if hasattr(emp1,'age'):
    print( "属性age存在!")
else:
    print ("属性age不存在!")
# 结果:
# name的属性值为: Rose
# tel的属性值为: 17718533234
# tel修改后的属性值为: 15042622134
# 属性age不存在!

Python内置类属性,这里做简单介绍:

  • __dict__ : 类的属性(获取类所有信息):结果返回一个字典包含类属性及属性值,类方法等所有类信息
  • __doc__ : 类的文档字符串,也就是类的帮助信息。
  • __name__: 类名
  • __module__: 类定义所在的模块
    • 如果在当前模块返回’__main__’;
    • 如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)
  • __bases__ : 类的所有父类(包含了所有父类组成的元组)
#coding=utf-8                                       
class Employee (object):                            
    """所有员工的基类"""                                                
    empCount = 0                                         
    def __init__(self, name, salary) :                  
            #类的构造函数                                             
            self.name = name                                    
            self.salary = salary                                
            Employee.empCount += 1                              
    def displayCount(self) :                            
            #类方法                                                
            print ("total employee ",Employee.empCount )          
    def displayEmployee(self) :                         
            print ("name :",self.name , ", salary :", self.salary)
                
print ("Employee.__doc__:", Employee.__doc__       )  
print ("Employee.__name__:", Employee.__name__     )  
print ("Employee.__module__:", Employee.__module__ )  
print ("Employee.__bases__:", Employee.__bases__   )  
print ("Employee.__dict__:", Employee.__dict__     )

装饰器

操作类属性方法之一:

  1. 使用 @property 装饰器操作类属性。
class Goods(object):
    def __init__(self):
        self.value=50
    # 经典类只有property
    @property
    def price(self):  # 查看属性
        return self.value
    # 新式类才有如下功能:setter、deleter
    @price.setter  # 添加/设置属性(属性名.setter)
    def price(self, value):
        if value >=50 and value<=100:  #对属性的取值和赋值加以控制
            self.value=value
            print (self.value)
        else:
            print ("请输入一个50到100之间的数!")

    @price.deleter  # 删除属性(属性名.deleter) 注意:属性一旦删除,就无法设置和获取
    def price(self):
        del self.value
        print ("price is deleted!")

obj = Goods()
print (obj.price)   # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price=106     # 自动执行 @price.setter 修饰的 price 方法,并将106 赋值给方法
del obj.price     # 自动执行 @price.deleter 修饰的 price 方法

结果输出:

  • 50
  • 请输入一个50到100之间的数!
  • price is deleted!

定义

把原来的函数给包了起来,在不改变原函数代码的情况下,在外面起到了装饰作用,这就是传说中的装饰器。它其实就是个普通的函数。

示例

import time

# (1)不带参数装饰器
def timer(func):
 '''统计函数运行时间的装饰器'''
 def wrapper():
  start = time.time()
  func()
  end = time.time()
  used = end - start
  print(f'{func.__name__} used {used}')
 return wrapper

# 先调用timer函数,生成一个包装了step1的新的函数timed_step1.
# 剩下的就是调用这个新的函数time_step1(),它会帮我们记录时间。
timed_step1 = timer(step1)
timed_step1()
# 简洁版
timer(step1)()

# (2)带参数的装饰器
#   wrapper使用了通配符,*args代表所有的位置参数,**kwargs代表所有的关键词参数。这样就可以应对任何参数情况。
#   wrapper调用被装饰的函数的时候,只要原封不动的把参数再传递进去就可以
def timer(func):
 '''统计函数运行时间的装饰器'''
 def wrapper(*args, **kwargs):
  start = time.time()
  func(*args, **kwargs)
  end = time.time()
  used = end - start
  print(f'{func.__name__} used {used}')
 return wrapper

# @符号,语法糖衣
@timer
def step1(num):
 print(f'我走了#{num}步')

step1(5)

# (3)带返回值的装饰器
def timer(func):
 '''统计函数运行时间的装饰器'''
 def wrapper(*args, **kwargs):
  start = time.time()
  ret_value = func(*args, **kwargs)
  end = time.time()
  used = end - start
  print(f'{func.__name__} used {used}')
  return ret_value
 return wrapper

@timer
def add(num1, num2):
 return num1 + num2

sum = add(5, 8)
print(sum)

python技能

调试

code 比 print、pdb 要简单。

  • 常用pycharm的debugger,感觉比code要好用一些。pycharm可以深入到method里去debug。code好像不能。只能在最上层看变量,执行方法。

pdb 调试

pdb 2种用法:

  • 非侵入式方法(不用额外修改源代码,在命令行下直接运行就能调试)
    • python3 -m pdb filename.py
  • 侵入式方法(需要在被调试的代码中添加一行代码然后再正常运行代码)
    • import pdb;pdb.set_trace()

命令行看到下面这个提示符时,说明正确打开pdb

(Pdb)

 import pdb

 pdb.set_trace()

命令:参考

  • 查看
    • l: 查看当前位置前后11行源代码(多次会翻页)
    • ll: 查看当前函数或框架的所有源代码
  • p 打印变量值
    • p a+b 执行表达式
    • a: 打印函数参数及值
    • whatis var: 打印变量(var)类型
    • w: 打印堆栈信息, 箭头表示当前栈
  • 逐行调试
    • n: 执行下一行,不进函数体
    • s: 执行下一行,可进入函数内部
    • r: 执行下一行,函数中时,直接执行到函数返回处
  • 连续执行
    • c: 连续执行, 直至下一个断点
    • unt lineno: 执行到指定行
    • j lineno: 直接跳到指定行
  • 栈操作: 跳到堆栈上一层 u;跳到堆栈下一层 d
    • u: 返回到上一个栈帧,初步尝试发现是可以回退执行
    • d: 进入下一个栈帧
  • b: 条件断点,对for循环比较有用
    • b 查看断点设置
    • b lineno 添加到行(lineno)
    • b filename:lineno 添加到文件(filename)的某行(lineno)
    • b functionname 函数第一行设置断点
    • tbreak 临时断点(执行一次后自动删除), 用法同上
  • cl 清除断点
    • cl 清除所有断点
    • cl filename:lineno
    • cl bpnumber 清除指定序号的断点
  • 交互
    • interact: 启动python交互解释器, ctrl+d 返回pdb
  • 退出
    • q: 退出pdb

code 模块

【2023-1-10】系统自带的code模块支持以交互方式调试代码, test.py

import code # python3 内置

bal_a = 2324
bal_b = 0
bal_c = 409
bal_d = -2

account_balances = [bal_a, bal_b, bal_c, bal_d]

def display_bal():
    for balance in account_balances:
        if balance < 0:
            print("Account balance of {} is below 0; add funds now.".format(balance))
        elif balance == 0:
            print("Account balance of {} is equal to 0; add funds soon.".format(balance))
        else:
            print("Account balance of {} is above 0.".format(balance))

# Use interact() function to start the interpreter with local namespace
code.interact(local=locals()) # 启动交互调试,局部变量
code.interact(local=globals()) # 启动交互调试,全局变量
code.interact(local=dict(globals(), **locals())) # 局部变量+全局变量
# Use interact() function to start the interpreter
code.interact(banner="Start", local=locals(), exitmsg="End") # 启动交互调试,设置提示语(开始 Start、结束 End)
display_bal()

执行 python3 test.py

python3 test.py # 执行交互调试
# Python 3.5.2 (default, Nov 17 2016, 17:05:23) 
# [GCC 5.4.0 20160609] on linux
# Type "help", "copyright", "credits" or "license" for more information.
# (InteractiveConsole)
print(bal_c) # 输出变量
print(account_balances)
print(display_bal()) # 调用函数

唯一id

uuid生成

  • python生成并处理uuid的方法

  • uuid是128位的全局唯一标识符(univeral unique identifier),通常用32位的一个字符串的形式来表现。有时也称guid(global unique identifier)。python中自带了uuid模块来进行uuid的生成和管理工作。(具体从哪个版本开始有的不清楚。。)
  • python中的uuid模块基于信息如MAC地址、时间戳、命名空间、随机数、伪随机数来uuid。具体方法有如下几个:  
    • uuid.uuid1()  基于MAC地址,时间戳,随机数来生成唯一的uuid,可以保证全球范围内的唯一性。
    • uuid.uuid2()  算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。不过需要注意的是python中没有基于DCE的算法,所以python的uuid模块中没有uuid2这个方法。
    • uuid.uuid3(namespace,name)  通过计算一个命名空间和名字的md5散列值来给出一个uuid,所以可以保证命名空间中的不同名字具有不同的uuid,但是相同的名字就是相同的uuid了。【感谢评论区大佬指出】namespace并不是一个自己手动指定的字符串或其他量,而是在uuid模块中本身给出的一些值。比如uuid.NAMESPACE_DNS,uuid.NAMESPACE_OID,uuid.NAMESPACE_OID这些值。这些值本身也是UUID对象,根据一定的规则计算得出。
    • uuid.uuid4()  通过伪随机数得到uuid,是有一定概率重复的
    • uuid.uuid5(namespace,name)  和uuid3基本相同,只不过采用的散列算法是sha1

一般而言,在对uuid的需求不是很复杂的时候,uuid1方法就已经够用了

import uuid

name = 'test_name'
# namespace = 'test_namespace'
namespace = uuid.NAMESPACE_URL

print uuid.uuid1()
print uuid.uuid3(namespace,name)
print uuid.uuid4()
print uuid.uuid5(namespace,name)

md5

代码

import hashlib

def calculate_md5(data):
    md5_hash = hashlib.md5()
    md5_hash.update(data.encode('utf-8'))
    return md5_hash.hexdigest()

prompt_md5 = calculate_md5(prompt)

性能优化

  • 【2020-9-10】Python语言优化
  • 原生的python通常都是由cpython实现,而cpython的运行效率,确实让人不敢恭维,比较好的解决方案有cython、numba、pypy等等

cython

numba

pypy

异步

  • Python语言没有真正的进程,而是通过协程来实现,多线程也受制于GIL
    • Python因为有GIL(全局解释锁),不可能有真正多线程的存在,因此很多情况下都会用 multiprocessing 实现并发,而且在Python中应用多线程还要注意关键地方的同步,不太方便,用协程代替多线程多进程是一个很好的选择,因为它吸引人的特性:主动调用/退出,状态保存,避免cpu上下文切换等…
  • 参考

效率进阶之路

爬虫效率进阶之路:

  • (1)urllib和urllib2包抓5个页面总共耗时7.6秒
  • (2)requests包6.5秒!虽然比用urllib快了1秒多,但是总体来说,他们基本还是处于同一水平线的,程序并没有快很多,这一点的差距或许是requests对请求做了优化导致的。
  • (3)lxml库→正则,继续提升1s
  • (4)多线程:threading库有效的解决了阻塞等待的问题,足足比之前的程序快了80%!只需要1.4秒就可完成电影列表的抓取。
    • 多线程受限于GIL,能否用多进程提速?
  • (5)多进程:ProcessPoolExecutor库实现多进程
    • ThreadPoolExecutor和ProcessPoolExecutor是Python3.2之后引入的分别对线程池和进程池的一个封装,如果使用Python2.x,需要安装futures这个库才能使用
    • 多进程带来的优点(cpu处理)并没有得到体现,反而创建和调度进程带来的开销要远超出它的正面效应,拖了一把后腿。即便如此,多进程带来的效益相比于之前单进程单线程的模型要好得多。
  • (6)基于协程的网络库,叫做gevent,异步功能,只有1.2秒,果然很快
    • gevent给予了我们一种以同步逻辑来书写异步程序的能力,看monkey.patch_all()这段代码,它是整个程序实现异步的黑科技
    • 但gevent的魔术给他带来了一定的困惑,并非Pythonic的清晰优雅
  • (7)async/await:同步的requests库改成了支持asyncio的aiohttp库,使用3.5的async/await语法(3.5之前用@asyncio.coroutine和yield from代替)写出了协程版本,1.7s

  • 清晰优雅的协程可以说实现异步的最优方案之一
  • 协程的机制使得我们可以用同步的方式写出异步运行的代码。

思考:

  • 更换零件带来的优化小,而且扩展性有限
  • 网络应用通常瓶颈都在IO层面,解决等待读写的问题比提高文本解析速度来的更有性价比!

GIL 锁

【2023-8-7】Python考虑去除GIL,真正的多线程要来了

GIL 的全称是 Global Interpreter Lock(全局解释器锁),不是 Python 独有,而是在实现 CPython(Python 解释器)时引入的一个概念。

  • 将 GIL 理解为一个互斥锁,用来保护 Python 里的对象,防止同一时刻多个线程执行 Python 的字节码,从而确保线程安全。
  • GIL 存在一个弊端,即在同一时刻只能有一个线程在一个 CPU 上执行,无法将多个线程映射到多个 CPU 上,使得 Python 并不能实现真正的多线程并发,从而降低了执行效率。

Python 团队已经正式接受了删除 GIL 的这个提议,并将其设置为可选模式

  • Meta 的 软件工程师 Sam Gross 花费了四年多的时间才完成这一工程。

将 no-GIL 构建作为一种实验性构建模式,大概会在 3.13 版本(也有可能推迟到 3.14 版本)可用。希望 no-GIL 成为默认方式,并删除 GIL 的所有痕迹

多线程

参考

进程+线程

进程间通讯

进程、线程及其之间的关系与特点:

  • 进程是资源分配的最小单元,一个程序至少包含一个进程
  • 线程是程序执行的最小单元,一个进程至少包含一个线程
  • 每个进程都有自己独占的地址空间、内存、数据栈等;由于进程间的资源独立,所以进程间通信(IPC)是多进程的关键问题
  • 同一进程下的所有线程都共享该进程的独占资源,由于线程间的资源共享,所有数据同步与互斥是多线程的难点

此外,多进程多线程也必须考虑硬件资源的架构:

  • 单核CPU中每个进程中同时刻只能运行一个线程,只在多核CPU中才能够实现线程并发
  • 当线程需运行但运行空间不足时,先运行高优先级线程,后运行低优先级线程
  • Windows系统新开进行会增加很大的开销,因此Windows系统下使用多线程更具优势,这时更应该考虑的问题是资源同步和互斥
  • Linux系统新开进程的开销增加很小,因此Linux系统下使用多进程更具优势,这时更应该考虑的问题是进程间通信

总结

  • IO密集型(不用CPU)→ 多线程
  • 计算密集型(用CPU)→ 多进程

使用线程和进程的目的都是为了提升效率

  • (1)单进程单线程,主进程/主线程
  • (2)自定义线程:主进程 → 主线程/子线程

Python 有两种形式可以开启线程

  • threading.Thread()方式: 函数调用
  • 继承 thread.Thread类: 面向对象

GIL 全局解释器锁

Python 的设计上有 Global Interpreter Lock (GIL), 导致 Python 一次只能做一件事情.

  • 尽管 Python 完全支持多线程编程, 但解释器的C语言实现部分在完全并行时并不是线程安全的。 实际上,解释器被一个全局解释器锁保护着,确保任何时候都只有一个Python线程执行。
  • GIL最大的问题:Python 多线程程序并不能利用多核CPU 优势 (比如一个使用了多个线程的计算密集型程序只会在一个单CPU上面运行)。

Python 并非真正意义上的多线程

GIL只会影响到那些严重依赖CPU的程序(比如计算型的)。

  • 如果程序大部分只会涉及到I/O,比如网络交互,那么使用多线程就很合适, 因为大部分时间都在等待。
  • 实际上,完全可以放心的创建几千个Python线程, 现代操作系统运行这么多线程没有任何压力,没啥可担心的。

  • python在运行计算密集型的多线程程序时,更倾向于让线程在整个时间片内始终占据GIL
  • 而I/O密集型的多线程程序在I/O被调用前会释放GIL,允许其他线程在I/O执行时获得GIL

因此python 多线程更适用于I/O密集型的程序(网络爬虫)

threading.Thread()

threading.Thread()方式开启线程

  • 创建 threading.Thread() 对象
  • 通过 target 指定运行函数
  • 通过 args 指定需要的参数
  • 通过 start 运行线程

非必须:

  • 通过 threading.Lock() 保证线程同步
  • 通过 join 阻塞运行
  • 通过 name 指定线程名称
  • 通过 daemon 设置守护线程
threading 用法

主要方法

import threading

# 查看已激活线程数
threading.active_count() # 2
# 查看所有线程
threading.enumerate() # [<_MainThread(MainThread, started 140736011932608)>, <Thread(SockThread, started daemon 123145376751616)>]
# 查看正在运行的线程
threading.current_thread() # <_MainThread(MainThread, started 140736011932608)>

def T1_job():
    print("T1 start\n")
    for i in range(10):
        time.sleep(0.1)
    print("T1 finish\n")

def T2_job():
    print("T2 start\n")
    print("T2 finish\n")

if __name__ == '__main__':
    thread = threading.Thread(target=thread_job,)   
    thread_1 = threading.Thread(target=T1_job, name='T1') # 定义线程 
    thread_2 = threading.Thread(target=T2_job, name='T2') # 定义线程
    # 启动、阻塞顺序很重要,直接关系到 各个任务之间的依赖关系
    thread_1.start() # 开启线程T1
    thread_2.start() # 开启线程T2
    thread_2.join() # 阻塞T2
    thread_1.join() # 阻塞T1
    print("all done\n")
并发
import datetime
import os
import threading
import time

def log(msg):
    pid = os.getpid() # 获取进程编号
    tid = threading.current_thread().ident # 获取当前线程编号
    print(f"进程[{pid}]-线程[{tid}]: {msg}")


def add(x, y):
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now}")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(3)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    return x + y

if __name__ == '__main__':
    log("=== 主线程 ===")
    t1 = threading.Thread(target=add, args=(1, 2))
    t2 = threading.Thread(target=add, args=(3, 4))
    t1.start()
    t2.start()
    log("=== 主线程完事 ===")

执行结果

  • 异常: 各个代码块并没有一起执行,执行顺序混乱
  • 原因: 不同步
进程[18365]-线程[139826158360128]: === 主线程 ===
进程[18365]-线程[139826148665088]: 现在的时间是:2024-02-08 17:46:15
进程[18365]-线程[139826140272384]: 现在的时间是:2024-02-08 17:46:15
进程[18365]-线程[139826158360128]: === 主线程完事 ===
进程[18365]-线程[139826140272384]: 执行加法:3 + 4 = 7
进程[18365]-线程[139826148665088]: 执行加法:1 + 2 = 3
进程[18365]-线程[139826140272384]: 现在的时间是:2024-02-08 17:46:18,结束加法运算
进程[18365]-线程[139826148665088]: 现在的时间是:2024-02-08 17:46:18,结束加法运算
加锁实现同步

不同线程使用同一共享内存时,lock确保线程之间互不影响

锁被用来实现对共享资源的同步访问

两种锁

  • 锁对象:threading.Lock
  • 递归锁对象:threading.RLock
    • 解决死锁问题;RLock对象内部维护着一个Lock和counter变量,counter记录着acquire的次数,从而使得资源可以被多次acquire,直到一个线程的所有acquire都被release时,其他线程才能够acquire资源。
    • 死锁是指两个或两个以上的线程在执行过程中,因争夺资源访问权而造成的互相等待的现象,从而一直僵持下去。
  • 条件变量对象:threading.Condition
  • 信息量对象:threading.Semaphore
    • 一个信号量管理一个内部计数器,acquire( )方法会减少计数器,release( )方法则增加计算器,计数器的值永远不会小于零,当调用acquire( )时,如果发现该计数器为零,则阻塞线程,直到调用release( ) 方法使计数器增加。
  • 事件对象:threading.Event
    • 如果一个或多个线程需要知道另一个线程的某个状态才能进入下一步的操作,就可以使用线程的event事件对象来处理。
  • 定时器对象:threading.Timer
    • 一个操作需要在等待一段时间之后执行,相当于一个定时器。
  • 栅栏对象:threading.Barrier

在应用锁对象时,会发生死锁;死锁是指两个或两个以上的线程在执行过程中,因争夺资源访问权而造成的互相等待的现象,从而一直僵持下去。

递归锁对象就是用来解决死锁问题的,RLock对象内部维护着一个Lock和一个counter变量,counter记录着acquire的次数,从而使得资源可以被多次acquire,直到一个线程的所有acquire都被release时,其他线程才能够acquire资源。

使用lock的方法

  • 每个线程执行运算修改共享内存之前,执行 lock.acquire() 将共享内存上锁,确保当前线程执行时,内存不会被其他线程访问
  • 执行运算完毕后,使用 lock.release() 将锁打开,保证其他的线程可以使用该共享内存。
  • 通过 threading.Lock() 保证线程同步 (所有任务都执行完才会进行下一步)
mutex = threading.Lock() # 创建锁
mutex.acquire([timeout]) # 锁定
# 锁定方法acquire可以有一个超时时间的可选参数timeout。如果设定了timeout,则在超时后通过返回值可以判断是否得到了锁,从而可以进行一些其他的处理。
mutex.release() #释放

示例

import datetime
import os
import threading
import time

def log(msg):
    pid = os.getpid() # 获取进程编号
    tid = threading.current_thread().ident # 获取当前线程编号
    print(f"进程[{pid}]-线程[{tid}]: {msg}")

def add(lock, x, y):
    # 同一时间内,只有lock区域内代码在执行
    lock.acquire() # 锁定
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(3)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release() # 解锁
    return x + y

if __name__ == '__main__':
    log("=== 主线程 ===")
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2))
    t2 = threading.Thread(target=add, args=(thread_lock, 3, 4))
    t1.start()
    t2.start()
    log("=== 主线程完事 ===")
    

执行结果

  • 相同代码块一起执行, 如2个子线程
  • 问题: 主线程并没有等任意子线程执行完
  • 原因: 没有阻塞等待机制
进程[18446]-线程[140361755809344]: === 主线程 ===
进程[18446]-线程[140361746114304]: 现在的时间是:2024-02-08 17:50:16,开始加法运算
进程[18446]-线程[140361746114304]: 执行加法:1 + 2 = 3
进程[18446]-线程[140361755809344]: === 主线程完事 ===
进程[18446]-线程[140361746114304]: 现在的时间是:2024-02-08 17:50:19,结束加法运算
进程[18446]-线程[140361737721600]: 现在的时间是:2024-02-08 17:50:19,开始加法运算
进程[18446]-线程[140361737721600]: 执行加法:3 + 4 = 7
进程[18446]-线程[140361737721600]: 现在的时间是:2024-02-08 17:50:22,结束加法运算
守护线程

守护线程指程序运行时在后台提供一种通用服务的线程,比如垃圾回收线程;

  • 这种线程在程序中并非不可或缺。
  • 默认创建的线程为非守护线程,等所有的非守护线程结束后,程序也就终止了,同时杀死进程中的所有守护线程。
  • 只要非守护线程还在运行,程序就不会终止。
t1 = threading.Thread(target=add, args=(thread_lock, 1, 2), name="", daemon=False)  # 默认, 非守护进程
t2 = threading.Thread(target=sub, args=(thread_lock, 8, 4), name='', daemon=False)  # 守护进程
join 阻塞

没有阻塞时,主进程直接按顺序执行,不管子进程是否执行完毕

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    tid = threading.current_thread().ident
    print(f"进程[{pid}]-线程[{tid}]: {msg}")

def add(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(3)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release()
    return x + y

if __name__ == '__main__':
    log(f'=== 主线程开始 === {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2), name='t1')
    t2 = threading.Thread(target=add, args=(thread_lock, 3, 4), name='t2')
    # 子线程启停顺序直接影响任务依赖关系
    t1.start() # 启动t1
    t2.start() # 启动t2
    t1.join() # 主线程要等待子线程1结束
    t2.join() # 主线程要等待子线程2结束
    log(f'=== 主线程结束 === {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')

执行结果

进程[18612]-线程[140486813877824]: === 主线程开始 === 2024-02-08 17:55:02
进程[18612]-线程[140486804182784]: 现在的时间是:2024-02-08 17:55:02,开始加法运算
进程[18612]-线程[140486804182784]: 执行加法:1 + 2 = 3
进程[18612]-线程[140486804182784]: 现在的时间是:2024-02-08 17:55:05,结束加法运算
进程[18612]-线程[140486795790080]: 现在的时间是:2024-02-08 17:55:05,开始加法运算
进程[18612]-线程[140486795790080]: 执行加法:3 + 4 = 7
进程[18612]-线程[140486795790080]: 现在的时间是:2024-02-08 17:55:08,结束加法运算
进程[18612]-线程[140486813877824]: === 主线程结束 === 2024-02-08 17:55:08
存储进程结果

如何记录各个进程执行结果?

  • 多线程调用的函数不能用return返回值!
  • 解法: 用 queue
import threading
import time

from queue import Queue

def job(l,q):
    for i in range (len(l)):
        l[i] = l[i]**2
    # 多线程调用的函数不能用return返回值
    q.put(l)

def multithreading():
    # 定义一个Queue,用来保存返回值,代替return
    q =Queue()
    threads = []
    data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
    # 定义并启动4个线程
    for i in range(4):
        t = threading.Thread(target=job,args=(data[i],q)) #Thread首字母要大写,被调用的job函数没有括号,只是一个索引,参数在后面
        t.start()#开始线程
        threads.append(t) #把每个线程append到线程列表中
    # 阻塞在主进程前面
    for thread in threads:
        thread.join()
    # 定义空列表results,将4个线运行结果保存到列表results
    results = []
    for _ in range(4):
        results.append(q.get())
    print(results) # [[1, 4, 9], [9, 16, 25], [16, 16, 16], [25, 25, 25]]

if __name__=='__main__':
    multithreading()

继承 thread.Thread类

完整示例

#!/usr/bin/env python
#coding=utf-8

import threading
import time
 
class MyThread(threading.Thread):
    def run(self):
        global num
        time.sleep(1)
 
        if mutex.acquire(1): 
            num = num+1
            msg = self.name+' set num to '+str(num)
            print(msg)
            mutex.release()
num = 0
mutex = threading.Lock()
def test():
    for i in range(5):
        t = MyThread()
        t.start()
if __name__ == '__main__':
    test()

输出:

Thread-1 set num to 1
Thread-3 set num to 2
Thread-4 set num to 3
Thread-5 set num to 4
Thread-2 set num to 5

示例

import threading
import time

lock = threading.Lock() #创建锁

def fun(data):
    try:
        lock.acquire(True) #锁定
        print("------> fun 1:",time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())),data)
        time.sleep(5)
        print("------> fun 2:", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())),data)
    finally:
        lock.release()#释放

threading.Thread(target = fun, name='socket_tcp_server', kwargs={'data':100}).start()
threading.Thread(target = fun, name='socket_tcp_server', kwargs={'data':200}).start()
threading.Thread(target = fun, name='socket_tcp_server', kwargs={'data':300}).start()
threading.Thread(target = fun, name='socket_tcp_server', kwargs={'data':400}).start()

协程

  • 协程,又称作Coroutine。从字面上来理解,即协同运行的例程,它是比是线程(thread)更细量级的用户态线程
    • 特点是允许用户的主动调用和主动退出,挂起当前的例程然后返回值或去执行其他任务,接着返回原来停下的点继续执行。
  • 问题
    • 函数都是线性执行的,怎么会说执行到一半返回,等会儿又跑到原来的地方继续执行
    • 答案是用yield语句
      • yield能把一个函数变成一个generator,与return不同,yield在函数中返回值时会保存函数的状态,使下一次调用函数时会从上一次的状态继续执行,即从yield的下一条语句开始执行
      • 好处:数列较大时,可以一边循环一边计算的机制,节省了存储空间,提高了运行效率
    • 操作系统具有getcontext和swapcontext这些特性,通过系统调用,我们可以把上下文和状态保存起来,切换到其他的上下文,这些特性为coroutine的实现提供了底层的基础。操作系统的Interrupts和Traps机制则为这种实现提供了可能性

代码

def fib(max):
    n, a, b = 0, 0, 1
    while n  max:
        # 每次调用函数时,都要耗费大量时间循环做重复的事情
        # print b
        # 用yield,则会生成一个generator,需要时,调用它的next方法获得下一个值
        yield b
        a, b = b, a + b
        n = n + 1

用协程实现生产者-消费者模式

#-*- coding:utf-8

def consumer():
    status = True
    while True:
        n = yield status
        print("我拿到了{}!".format(n))
        if n == 3:
            status = False

def producer(consumer):
    n = 5
    while n > 0:
    # yield给主程序返回消费者的状态
        yield consumer.send(n)
        n -= 1

if __name__ == '__main__':
    # 由于yield,不同于一般函数执行,Python会当做一个generator对象
    c = consumer() 
    # 将consumer(即变量c,它是一个generator)中的语句推进到第一个yield语句出现的位置
    #    函数里的status = True和while True:都已经被执行了,程序停留在n = yield status的位置(未执行)
    c.send(None) # 如果漏写这一句,那程序直接报错
    # 定义了producer的生成器,注意的是这里我们传入了消费者的生成器,来让producer跟consumer通信。
    p = producer(c)
    # 循环地运行producer和获取它yield回来的状态
    for status in p:
        if status == False:
            print("我只要3,4,5就行啦")
            break
    print("程序结束")
  • 让生产者发送1,2,3,4,5给消费者,消费者接受数字,返回状态给生产者,而我们的消费者只需要3,4,5就行了,当数字等于3时,会返回一个错误的状态。最终我们需要由主程序来监控生产者-消费者的过程状态,调度结束程序。
  • 生产者p调用了消费者c的send()方法,把n发送给consumer(即c),在consumer中的n = yield status,n拿到的是消费者发送的数字,同时,consumer用yield的方式把状态(status)返回给消费者,注意:这时producer(即消费者)的consumer.send()调用返回的就是consumer中yield的status!消费者马上将status返回给调度它的主程序,主程序获取状态,判断是否错误,若错误,则终止循环,结束程序。上面看起来有点绕,其实这里面generator.send(n)的作用是:把n发送generator(生成器)中yield的赋值语句中,同时返回generator中yield的变量(结果)。
  • 结果
我拿到了5!
我拿到了4!
我拿到了3!
我只要3,4,5就行啦
程序结束
  • Coroutine与Generator
  • 相似:
    • 都是不用return来实现重复调用的函数/对象,都用到了yield(中断/恢复)的方式来实现。
  • 区别
    • generator总是生成值,一般是迭代的序列
    • coroutine关注的是消耗值,是数据(data)的消费者
    • coroutine不会与迭代操作关联,而generator会
    • coroutine强调协同控制程序流,generator强调保存状态和产生数据
  • 协程相比于多进程多线程的优点
    • ①多进程和多线程创建开销大
    • ②一个难以根治的缺陷,处理进程之间或线程之间的协作问题,因为是依赖多进程和多线程的程序在不加锁的情况下通常是不可控的,而协程则可以完美地解决协作问题,由用户来决定协程之间的调度。

asyncio

asyncio是python 3.4中新增的模块,它提供了一种机制,使得你可以用协程(coroutines)、IO复用(multiplexing I/O)在单线程环境中编写并发模型。

  • 根据官方说明,asyncio模块主要包括了:
    • 具有特定系统实现的事件循环(event loop);
    • 数据通讯和协议抽象(类似Twisted中的部分);
    • TCP,UDP,SSL,子进程管道,延迟调用和其他;
    • Future类;
    • yield from的支持;
    • 同步的支持;
    • 提供向线程池转移作业的接口;

下面来看下asyncio的一个例子:

import asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)
    return x + y

async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

Python在3.5版本中引入了关于协程的语法糖asyncawait

  • async修饰将普通函数生成器函数包装成异步函数异步生成器
  • 通过await调用async修饰的异步函数
  • async 用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件(假设挂起条件是sleep(5))消失后,也就是5秒到了再回来执行。
  • await 用来用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。

参考

# 异步函数(协程)
async def async_function():
    return 1
# 异步生成器
async def async_generator():
    yield 1
# 通过类型判断可以验证函数的类型
import types
print(type(function) is types.FunctionType)
print(type(generator()) is types.GeneratorType)
print(type(async_function()) is types.CoroutineType)
print(type(async_generator()) is types.AsyncGeneratorType)

# 直接调用异步函数不会返回结果,而是返回一个coroutine对象:
print(async_function())
## <coroutine object async_function at 0x102ff67d8>

# 协程需要通过其他方式来驱动,因此可以使用这个协程对象的send方法给协程发送一个值:
print(async_function().send(None))
# 改进:捕获协程的真正返回值,新建run函数驱动协程
def run(coroutine):
    try:
        coroutine.send(None)
    except StopIteration as e:
        return e.value
# 在协程函数中,可以通过await语法来挂起自身的协程,并等待另一个协程完成直到返回结果:
async def async_function():
    return 1

async def await_coroutine():
    result = await async_function()
    print(result)
    
run(await_coroutine())
# 1

任务调度

定期调度 —— Schedule

简单强大的Python库!Schedule—实用的周期任务调度工具

在Linux服务器上周期性地执行某个 Python 脚本

(1)最出名的选择应该是 Crontab 脚本,但是 Crontab 具有以下缺点:

  1. 不方便执行秒级的任务。
  2. 当需要执行的定时任务有上百个的时候,Crontab的管理就会特别不方便。 (2)Celery,但是 Celery 的配置比较麻烦,如果只是需要一个轻量级的调度工具,Celery 不会是一个好选择。

在你想要使用一个轻量级的任务调度工具,而且希望它尽量简单、容易使用、不需要外部依赖,最好能够容纳 Crontab 的所有基本功能,那么 Schedule 模块是你的不二之选。

# pip install schedule
import schedule
import time

def job():
  print("I'm working...")

# scedule.every(时间数).时间类型.do(job)
# 执行一次
schedule.every.day.at('22:30').do(job)
# 每10分钟执行一次 job 函数
schedule.every(10).minutes.do(job)
# 每十分钟执行任务
schedule.every(10).minutes.do(job)
# 每个小时执行任务
schedule.every.hour.do(job)
# 每天的10:30执行任务
schedule.every.day.at("10:30").do(job)
# 每个月执行任务
schedule.every.monday.do(job)
# 每个星期三的13:15分执行任务
schedule.every.wednesday.at("13:15").do(job)
# 每分钟的第17秒执行任务
schedule.every.minute.at(":17").do(job)
# do 将额外的参数传递给job函数
schedule.every(2).seconds.do(greet, name='Alice')

while True:
  schedule.run_pending()
  time.sleep(1)

多进程任务调度框架

  • 【2021-1-21】多年前写的多任务调度框架,每天处理20T上网数据
# ***************************************************************************
# *
# * Copyright (c) 2012 Baidu.com, Inc. All Rights Reserved
# *
# **************************************************************************/
  
#/**
# * @file start.py
# * @author wangqiwen@baidu.com(zhanglun@baidu.com)
# * @date 2013/04/26 16:32
# * detect if data is ready
# * process data use codes defined in conf
# * output data to path in conf
# * multi thread , multi job commit
# * log is added
# **/
 
#import packages:
import ConfigParser
import string
import os
import sys
import threading
import time
import datetime
import logging
import urllib, re
import commands
import json
 
sys.path.append('.')
 
from optparse import OptionParser
 
#define class:
#job spec conf
class JobSpecConf:
    def __init__(self):
        self.job_name = None
        self.input_ready = []
        self.inputs = []
        self.must_input = []
        self.code_path = None
        self.detect_start_time = 0
        self.detect_end_time = 0
        self.delay_before_start = 0
        self.output_ready = None
        self.map_con_num = 0
        self.reduce_num = 0
 
#load jon conf
def loadJobConf(conf_file,date):
    config = ConfigParser.ConfigParser()
    config.read(conf_file)
    global_conf.map_dict['$date'] = date
    global_conf.map_dict['$date_pre'] = getPreDate(date=date)
    # tranform job conf info
    job_conf_list = []
    try:
        for section in config.sections():
            # all job session name starts with 'job' in configre file
            if not section.startswith('job'):
                continue
            # new job conf object
            job_spec_conf = JobSpecConf()
            job_spec_conf.job_name = config.get(section,"job_name")
            # update job_name in map_dict
            global_conf.map_dict['$job_name'] = job_spec_conf.job_name
            # get input path & ready
            item_list = config.options(section)
            path_list = [multiReplace(config.get(section,i),global_conf.map_dict) for i in item_list if i.startswith('input_path_')]
            ready_list = [multiReplace(config.get(section,i),global_conf.map_dict) for i in item_list if i.startswith('input_ready_')]
            job_spec_conf.input_number = len(path_list)
            if len(path_list) != len(ready_list):
                logger.error("configure file(%s) error: input path & ready in section %s doesn't match !" %(conf_file,section))
                return job_conf_list
            job_spec_conf.inputs = path_list
            job_spec_conf.input_ready = ready_list
            if 'must_input' in item_list:
                job_spec_conf.must_input = [multiReplace(config.get(section,'input_ready_%s'%(i)),global_conf.map_dict) for i in config.get(section,"must_input").split('&')]
                logger.info('[%s] must_input = %s'%(job_spec_conf.job_name,repr(job_spec_conf.must_input)))
            # get other info
            job_spec_conf.output = multiReplace(config.get(section,"output"),global_conf.map_dict)
            job_spec_conf.output_ready = multiReplace(config.get(section,"output_ready"),global_conf.map_dict)
            job_spec_conf.code_path = multiReplace(config.get(section,"code_path"),global_conf.map_dict)
            job_spec_conf.detect_start_time = config.get(section,"detect_start_time")
            job_spec_conf.detect_end_time = config.get(section,"detect_end_time")
            job_spec_conf.delay_before_start = config.getint(section,"delay_before_start")
            job_spec_conf.map_con_num = config.get(section,"map_con_num")
            job_spec_conf.reduce_num = config.get(section,"reduce_num")
            job_conf_list.append(job_spec_conf)
        logger.info("job conf info: %s" %(repr(job_conf_list)))
    except Exception,err:
        logger.error("error:(%s) when loading configure file(file:%s,section:%s)" %(err,conf_file,section))
    return job_conf_list
 
def multiReplace(str,dict):
    rx = re.compile('|'.join(map(re.escape,dict)))
    def one_xlat(match):
        return dict[match.group(0)]
    return rx.sub(one_xlat,str)
  
 
#init logger
def initlog(date,job_type):
    logger = logging.getLogger('%s.py' % job_type)
    hdlr = logging.FileHandler('../log/%s/%s_%s.txt' % (now,job_type,date),mode="wb")
    formatter = logging.Formatter('[%(levelname)s] [%(asctime)s]: %(message)s')
    hdlr.setFormatter(formatter)
    logger.addHandler(hdlr)
    logger.setLevel(logging.DEBUG)
    return logger
 
def send_mail(mail_alarm='on',title='mail title',content='mail content',receiver=None):
    """
        usage: send_email(title,content,receiver)
        shell: echo -e "hello" | mail -s "title" receivers
    """
    if mail_alarm != 'on':
        logger.info('alarm mail is off ...')
        return 0
    if receiver == None:
        logger.info('Mail receiver address (%s) invalid !' %(receiver))
        exit(-1)
    code = os.system("echo -e \"%s\" | mail -s \"%s\" \"%s\"" %(content,title,receiver))
    if 0 == code:
        logger.info('Success to send mail (%s:%s) to %s' %(title,content,receiver))
        return 0
    else:  
        logger.error('Fail to send mail (%s:%s) to %s, error code (%s) ,program exit ...' %(title,content,receiver,code))
        exit(-1)
 
def get_jobtracker_running_job_name_ids(url):
    while True:
        try:
            lines = urllib.urlopen(url).readlines()
        except:
            raise
            return False
        if lines:
            break
        else:
            logger.error("%s not reachable" % url)
     
    found = False
    jobs = {}
    for line in lines:
        line = line.strip()
        if found == True:
            if line.startswith('<h2'):
                break
            o = re.search('(job_[0-9]+_[0-9]+).+id="name_[0-9]+">([^<]+)<', line)
            if o:
                name, jid = o.groups()
                jobs[name] = jid
        if line == """<h2 id="running_jobs">Running Jobs</h2>""":
            found = True
    return jobs
 
#define class:
#global conf
class GlobalConf:
    def __init__(self):
        self.retry_interval = 0
        self.output_path = ''
        self.root_code_path = ''
        self.hadoop_client = ''
        self.re_run_number = 0
        self.jobtracker = ''
        self.map_dict = {'$output_path':'-','$date_pre':'-','$date':'-','$job_name':'-'}
        self.mail_receiver_in = None
        self.mail_alarm = 'on'
        self.mail_receiver_out = None
 
#define functions:
#load global config
def globalConfLoad(config_file):
    #load conf
    config = ConfigParser.ConfigParser()
    config.read(config_file)
    #init conf
    global_conf = GlobalConf()
    global_conf.retry_interval = config.getint("global","retry_interval")
    global_conf.output_path = config.get("global","output_path")
    global_conf.map_dict['$output_path'] = global_conf.output_path
    global_conf.root_code_path = config.get("global","root_code_path")
    global_conf.hadoop_client = config.get("global","hadoop_client")
    global_conf.re_run_number = config.getint("global","re_run_number")
    global_conf.jobtracker = config.get("global", "jobtracker")   
    global_conf.mail_receiver_in = config.get("global","mail_receiver_in")
    global_conf.mail_receiver_out = config.get("global","mail_receiver_out")
    global_conf.mail_alarm = config.get("global","mail_alarm")
    return global_conf
 
#test path exist
def checkHadoopFile(path,date,global_conf):
    path_after_parse = multiReplace(path,global_conf.map_dict)
    cmd = '%s fs -test -e %s' % (global_conf.hadoop_client,path_after_parse)
    code, msg = commands.getstatusoutput(cmd)
    if code == 0:
        return True
    elif msg:
        logger.warning('hadoop file (%s) not found , error_id : %s' %(path_after_parse,msg))
        return False
    return False
 
def getPreDate(n = 1 , date = 'today'):
    '''
        get string of date before n days
    '''
    #date_pre = (datetime.datetime.strptime(date,'%Y%m%d')+datetime.timedelta(days=1)).strftime('%Y%m%d')
    if date == 'today':
        date_object = datetime.date.today()
    else:
        date_object = datetime.datetime.strptime(date,'%Y%m%d')
    date_pre = (date_object + datetime.timedelta(days=-n)).strftime('%Y%m%d')
    return date_pre
 
def stopTask(job_spec_conf, date):
    task_prefix = "%s_%s_%s" % (options.pipeline, date, job_spec_conf.job_name)
    found = True
    while found:
        found = False
        running_jobs = get_jobtracker_running_job_name_ids(global_conf.jobtracker)
        for job in running_jobs:
            if job.startswith(task_prefix):
                logger.info("%s: killing job %s " % (job_spec_conf, running_jobs[job]))
                os.system('%s job -kill %s' % (global_conf.hadoop_client, running_jobs[job]))
                found = True
 
def cleanData(job_spec_conf, date, stop = False):
    if stop:
        stopTask(job_spec_conf, date)
    data_path = job_spec_conf.output
    ready_path = job_spec_conf.output_ready
    if 0 == os.system('%s fs -test -e %s' %(global_conf.hadoop_client,ready_path)):
        os.system('%s fs -rmr %s && %s fs -rmr %s' %(global_conf.hadoop_client,ready_path,global_conf.hadoop_client,data_path))
        logger.info("[%s] clean data (%s,%s)" %(job_spec_conf.job_name,data_path,ready_path))
 
def startMultiThead(date, tasks, options):
    logger.info('Begin to run the whole job of day: %s' % date)
    all_job_list = []
    all_job_list = loadJobConf(options.config_file,date)
    all_job_dict = {}
    for job in all_job_list:
        all_job_dict[job.job_name] = job
    logger.info('all jobs in conf : %s' % json.dumps([i.job_name for i in all_job_list]))
    # select some jobs
    if tasks:
        job_conf_list = []
        for jobname in tasks:
            if jobname in all_job_dict:
                job_conf_list.append(all_job_dict[jobname])
            else:
                logger.error('job name (%s) error ! please check with configure file(%s),all job names:%s'%(jobname,options.config_file,repr(all_job_dict.keys())))
                return False
    else:
        job_conf_list = all_job_list
    # check whether each job need to run or not
    job_conf_list = [c for c in job_conf_list if checkTask(c, date, options)]
    logger.info('jobs need to run : %s' % repr([i.job_name for i in job_conf_list]))
    if len(job_conf_list) <= 0:
        logger.info('no job in list , exit ...')
        return False
    # clean old data
    for c in job_conf_list:
        cleanData(c, date, True)
    if options.kill_task:
        return True
    # start multithread
    if options.parallel:
        # run all jobs at the same time
        threads = [threading.Thread(target = tryRunTask, args = (c, date, options)) for c in job_conf_list]
        for t in threads:
            t.start()
        for t in threads:
            t.join()
    else:
        # run one by one
        for c in job_conf_list:
            tryRunTask(c, date, options)
    logger.info('End of day: %s' % date)
    
def runOneTask(job_spec_conf, date, options):
    start_time = datetime.datetime.strptime(getPreDate(n=0)+job_spec_conf.detect_start_time,'%Y%m%d%H:%M')
    end_time = datetime.datetime.strptime(getPreDate(n=0)+job_spec_conf.detect_end_time,'%Y%m%d%H:%M')
    # wait some time until start
    if job_spec_conf.delay_before_start > 0:
        logger.info('[%s] sleep %s mins before start to detect ...'%(job_spec_conf.job_name,job_spec_conf.delay_before_start))
        time.sleep(job_spec_conf.delay_before_start*60)
    # Initialize input path
    input_ready_number = 0
    detect_input_dict = {}
    for i,v in enumerate(job_spec_conf.input_ready):
        detect_input_dict[v] = job_spec_conf.inputs[i]
    input_ready_list = []
    input_path_list = []
    # waiting
    while True:
        now_time = datetime.datetime.now()
        time_info = '[now %s,start %s,end %s]' %(str(now_time),str(start_time),str(end_time))
        #count ready input file
        tmp_key_list = detect_input_dict.keys()
        for tmp_input_ready in tmp_key_list:
            if checkHadoopFile(tmp_input_ready,date,global_conf):
                logger.info('[%s] detect input file (%s): ready ...' % (job_spec_conf.job_name,tmp_input_ready))
                input_ready_list.append(tmp_input_ready)
                input_path_list.append(detect_input_dict[tmp_input_ready])
                del detect_input_dict[tmp_input_ready]
            else:
                logger.warning('[%s] detect input file (%s): not ready... %s' % (job_spec_conf.job_name,tmp_input_ready,time_info))
        input_ready_number = len(input_ready_list)
        logger.info('[%s] detect result : ready num => %d, must input num => %d, all input num => %d...' % (job_spec_conf.job_name,input_ready_number,len(job_spec_conf.must_input),job_spec_conf.input_number))
        # check whether match min input
        match = 0
        if input_ready_number > 0:
            match = 1
        for i in job_spec_conf.must_input:
            if i not in input_ready_list:
                match = 0
                break
        if options.run_force and match :
            logger.warning("[%s] run forcely... input data ready info : %s/%s " %(job_spec_conf.job_name,input_ready_number,job_spec_conf.input_number))
            break
        #too early
        if now_time < start_time:
            logger.info('[%s] time enough , wait until start time ... %s' %(job_spec_conf.job_name,time_info))
            logger.info('[%s] sleep %d minutes (retry_interval)' % (job_spec_conf.job_name,global_conf.retry_interval))
            time.sleep(global_conf.retry_interval*60)
            continue
        #all ready
        if input_ready_number == job_spec_conf.input_number :
            logger.info('[%s] detect_start_time %s , detect_end_time %s , all inputs are ready ...' %(job_spec_conf.job_name,job_spec_conf.detect_start_time,job_spec_conf.detect_end_time))
            break
        # too late
        if options.enable_timeout and now_time >= end_time:
            if match:
                logger.warning('[%s] input ready before time over, ... input data ready info : %s/%s ' %(job_spec_conf.job_name,input_ready_number,job_spec_conf.input_number))
                break
            else:
                logger.warning('[%s] time over , but input files not enough , exit now ... input data ready info : %s/%s ' %(job_spec_conf.job_name,input_ready_number,job_spec_conf.input_number))
                exit(-1)
        logger.info('[%s] sleep %d minutes (retry_interval)' % (job_spec_conf.job_name,global_conf.retry_interval))
        time.sleep(global_conf.retry_interval*60)
             
    # run
    hadoop_job_cmd = 'sh %s%s %s %s %s %s %s %s %s %s %s &>../log/%s/%s_%s.txt' % (
                global_conf.root_code_path,
                job_spec_conf.code_path,
                global_conf.hadoop_client,
                '"%s"' % (';'.join(input_path_list)),
                job_spec_conf.output,
                job_spec_conf.job_name,
                global_conf.root_code_path,
                date,
                "%s_%s_%s" % (options.pipeline, date, job_spec_conf.job_name),
                job_spec_conf.map_con_num,
                job_spec_conf.reduce_num,
                now,
                job_spec_conf.job_name,
                date)
    logger.info('[%s] hadoop job command: %s' % (job_spec_conf.job_name, hadoop_job_cmd))
    code, msg = commands.getstatusoutput(hadoop_job_cmd)
    if code != 0:
        logger.error('[%s]: job Failled when running, error_code = %s(%s)' %(job_spec_conf.job_name,code,msg))
        title = '[merge-wise][ERROR] [%s] Job failed when running !'%(job_spec_conf.job_name)
        content = '[ERROR] [%s] Job failed when running , error_code = %s(%s)'%(job_spec_conf.job_name,code,msg)
        logger.error('mail_receiver_in=%s'%(global_conf.mail_receiver_in))
        send_mail(global_conf.mail_alarm,title,content,global_conf.mail_receiver_in)
        return False
    # result info : all input,ready input,miss input
    result_dict = {'input_list':[],'input_num':0,'ready_list':[],'ready_num':0,'miss_list':[],'miss_num':0}
    for i in job_spec_conf.input_ready:
        if i in input_ready_list:
            result_dict['ready_list'].append(i)
        else:
            result_dict['miss_list'].append(i)
        result_dict['input_list'].append(i)
    result_dict['ready_num'] = len(result_dict['ready_list'])
    result_dict['miss_num'] = len(result_dict['miss_list'])
    result_dict['input_num'] = len(result_dict['input_list'])
    logger.info('[%s] result_dict : %s' %(job_spec_conf.job_name,repr(result_dict)))
     
    # create ready file
    output_ready = job_spec_conf.output_ready
    hadoop_ready_cmd = 'echo %s | %s fs -put - %s' %(json.dumps(result_dict),global_conf.hadoop_client,output_ready)
    logger.info('create ready file : %s' %(hadoop_ready_cmd))
    code, msg = commands.getstatusoutput(hadoop_ready_cmd)
    if code != 0:
        logger.error('[%s] fail to create ready file (%s) , error_code = %s(%s)'%(job_spec_conf.job_name,output_ready,code,msg))
        title = '[merge-wise][ERROR][%s] Failed to create ready file !'%(job_spec_conf.job_name)
        content = '[ERROR] [%s] Failed to create ready file (%s),error_code = %s(%s)'%(job_spec_conf.job_name,output_ready,code,msg)
        send_mail(global_conf.mail_alarm,title,content,global_conf.mail_receiver_in)
        return False
    if result_dict['miss_num'] > 0:
        miss_source_info = ','.join([i.strip().split('/')[-3] for i in result_dict['miss_list']])
        logger.info('[%s] job finished at the end without sources(%s)' %(job_spec_conf.job_name,miss_source_info))
        title = '[merge-wise][WARNING][%s] Job finished at the end without sources(%s)' %(job_spec_conf.job_name,miss_source_info)
        content = '[WARNING][%s] Job finished at the end without sources(%s)\n\nmiss_info = %s\n\nready_info = %s' %(job_spec_conf.job_name,miss_source_info,repr(result_dict['miss_list']),repr(result_dict['ready_list']))
        send_mail(global_conf.mail_alarm,title,content,global_conf.mail_receiver_out)
    if options.signal_zk:
        cmd_out = os.system("%s cr -register  %s -cronID %s" % (global_conf.hadoop_client, job_spec_conf.output, job_spec_conf.job_name))
        if cmd_out != 0:
            title = '[merge-wise][ERROR][%s] Failed to reigster zk data (%s)!'%(job_spec_conf.job_name,job_spec_conf.output)
            content = '[ERROR][%s] Failed to reigster zk data (%s) ! error_code = %s '%(job_spec_conf.job_name, job_spec_conf.output ,str(cmd_out))
            send_mail(global_conf.mail_alarm,title,content,global_conf.mail_receiver_in)
            logger.error('[%s] job Failled, reigster zk data (%s) error, error_code = %s' %(job_spec_conf.job_name, job_spec_conf.output,str(cmd_out)))
            return False
    logger.info('[%s] job finish.' % (job_spec_conf.job_name))
    return True
 
def checkTask(job_spec_conf, date, options):
    # check whether job needs to run or not
    output_ready = job_spec_conf.output_ready
    if options.rerun or options.run_force:
        return True
    #if options.fix_data and checkMissTask(job_spec_conf, date):
    #    return True
    if checkHadoopFile(output_ready, date, global_conf):
        logger.info('check task %s : exist, %s' %(output_ready,job_spec_conf.job_name))
        return False
    else:
        logger.info('check task %s : not exist, %s' %(output_ready,job_spec_conf.job_name))
        return True
 
def tryRunTask(job_spec_conf, date, options):
    logger.info('try to run task %s of %s' % (job_spec_conf.job_name, date))
    for i in range(options.retry):
        if runOneTask(job_spec_conf, date, options):
            return
        if i == (options.retry - 1):
            break
        logger.error('Task %s on %s failed, try again ...' % (job_spec_conf.job_name, date))
        time.sleep(options.retry_sleep)
        cleanData(job_spec_conf, date, False)
    logger.error('Task %s on %s failed ...' % (job_spec_conf.job_name, date))
     
#main define here:
def main():
    global options
    global logger
    global global_conf
 
    usage = "usage: %start.py [options] [tasks]"
    parser = OptionParser(usage=usage)
    parser.add_option("-f", "--disable_timeout",
                      action = "store_false", dest = "enable_timeout", default = True,
                      help = "disable job timeout")
    parser.add_option("-d", "--date", dest = "date", default = getPreDate(), help = "last day for job")
    parser.add_option("-n", "--total_day", type = "int", dest = "total_day", default = 1, help = "total day for job")
    parser.add_option("-s", "--sequence_run", action = "store_false", dest = "parallel", default = True, help = "run task one by one")
    parser.add_option("-x", "--fix_data", action = "store_true", dest = "fix_data", default = False, help = "fix data")
    parser.add_option("-p", "--pipeline", dest = "pipeline", default = "start", help = "pipeline name")
    parser.add_option("-r", "--retry", type = "int", dest = "retry", default = 1, help = "job retry times")
    parser.add_option("-R", "--rerun", action = "store_true", dest = "rerun", default = False, help = "rerun tasks")
    parser.add_option("-w", "--retry_sleep", type = "int", dest = "retry_sleep", default = 300, help = "job retry sleep time, in seconds")
    parser.add_option("-a", "--run_force", action = "store_true", dest = "run_force", default = False, help = "run job no matter the job has run already or data are not all ready")
    parser.add_option("-c", "--config_file", dest = "config_file", default = "conf/job_conf.ini", help = "configure file path")
    parser.add_option("-K", "--kill", action = "store_true", dest = "kill_task", default = False, help = "Kill tasks")
    parser.add_option("-S", "--signal", action = "store_true", dest = "signal_zk", default = False, help = "Signal data reay in zk")
 
    (options, tasks) = parser.parse_args()
    logger = initlog(options.date, options.pipeline)
    global_conf = globalConfLoad(options.config_file)
    logger.info('=================================================')
    logger.info('options=%s tasks=%s' %(repr(options),repr(tasks)))
    #logger.info('options=%s tasks=%s' %(repr(options),repr(tasks)))
    # get previous n days list before date, then start it
    for i in xrange(options.total_day):
        date_i = getPreDate(n=i,date=options.date)
        startMultiThead(date_i, tasks, options)
    logger.info('Finish. Time to quit soon ...')
#main start:
if __name__ == '__main__':
    # call build.sh to update local directory
    os.system('sh build.sh')
    now = getPreDate( n = 0 )
    main()
 
# */* vim: set expandtab ts=4 sw=4 sts=4 tw=400: */
  • 【2021-1-26】其它调度框架,Python 实现并发Pipeline
  • 对于Pipeline 根据侧重点的不同,有两种实现方式
    1. 用于加速多线程任务的pipeline
    • 用于加速多线程任务的 Pipeline 主要强调 任务的顺序执行, 转移之间不涉及过于复杂的逻辑。所以 每个pipe 通过自身调用 next pipe。整体上强调 后向连续性。
      1. 用于控制流程的pipeline
    • 用于流程控制的piepline, 强调任务的 逻辑性, 由外部 manager 来控制 pipeline 的执行方向。整体上强调前向的依赖性, 使用拓扑排序确定执行顺序。

终端文本可视化

rich

python之Rich库使用入门

  • 打印彩色字体,表单,进度条与状态动画,高级数据类型)

Rich是一个Python库,用于将丰富的文本(带有颜色和样式)写入终端,并用于显示高级内容,例如表格。 使用Rich使您的命令行应用程序更具视觉吸引力,并以更具可读性的方式呈现数据。Rich还可以通过漂亮的打印和语法突出显示数据结构来作为有用的调试辅助工具。

安装:pip install Rich

进度条

from rich.progress import track
from time import sleep
for step in track(range(100), description="Writing Medium Story..."):
    sleep(0.1)

一款杀手级的Python工具

桌面提示

【2022-1-25】5个方便好用的Python自动化脚本

自动桌面提示

  • 这个脚本会自动触发windows桌面通知,提示重要事项,比如说:您已工作两小时,该休息了, 可以设定固定时间提示,比如隔10分钟、1小时等

用到的第三方库:

  • win10toast - 用于发送桌面通知的工具
from win10toast import ToastNotifier
import time
toaster = ToastNotifier()

header = input("What You Want Me To Remember\n")
text = input("Releated Message\n")
time_min=float(input("In how many minutes?\n"))

time_min = time_min * 60
print("Setting up reminder..")
time.sleep(2)
print("all set!")
time.sleep(time_min)
toaster.show_toast(f"{header}", f"{text}", duration=10, threaded=True)
while toaster.notification_active(): time.sleep(0.005) 

ip地址

获取ip地址

【2023-7-31】Python获取本机IP地址的几种方式

Mac下获取ip地址

# 直接输出到终端  curl 命令
import requests
res = requests.get('http://myip.ipip.net', timeout=5).text
print(res)

# 使用 socket库
import socket

#client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#print(client_socket)
# res = socket.gethostbyname(socket.gethostname())
# print(res)

# (1) 获取ip —— 成功, 但获取的是局域网地址
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
print(s.getsockname()[0])

# 适配ipv4、ipv6
addr = ("", 8080)  # all interfaces, port 8080
if socket.has_dualstack_ipv6():
    s = socket.create_server(addr, family=socket.AF_INET6, dualstack_ipv6=True)
else:
    s = socket.create_server(addr)
print(s)
# (2) 直接使用socket --- Mac下失败!
#  socket.gaierror: [Errno 8] nodename nor servname provided, or not known
hostname = socket.gethostname()
print('hostname: ', hostname)
IPAddr = socket.gethostbyname(hostname)
print("Your Computer Name is:"+hostname)
print("Your Computer IP Address is:"+IPAddr)

# (3) 适用于所有接口。它还适用于所有公共、私有、外部 IP。这种方法在 Linux、Windows 和 OSX 上很有效

def extract_ip():
    st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:       
        st.connect(('10.255.255.255', 1))
        IP = st.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        st.close()
    return IP
print(extract_ip())


# (4) netifaces 模块用于提供有关网络接口及其状态的信息, 局域网IP。
from netifaces import interfaces, ifaddresses, AF_INET
for ifaceName in interfaces():
    addresses = [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr':'No IP addr'}] )]
    print(' '.join(addresses))

面向对象

编程范式

编程范式对比

  • 面向过程:根据业务逻辑从上到下写垒代码
  • 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
  • 面向对象:对函数进行分类和封装,让开发“更快更好更强…”

OOP特点

面向对象三大特点:封装继承多态

  • 封装:将内容封装到某个地方,以后再去调用被封装在某处的内容
  • 继承:面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容
  • 多态:

资料

OOP 概念

  • :用户定义的对象原型(prototype),该原型定义了一组可描述该类任何对象的属性,属性是数据成员(类变量 和 实例变量)和方法,可以通过 ‘.’ 来访问。说简单一点,类是一个模板,我们可以使用该模板生成不同的具体的对象,来完成我们想要的操作
  • 实例:某一个类的单个对象,例如定义了一个 Person 类,而具体的人,比如小明,小黄就是 Person 类的实例
  • 属性:描述该类具有的特征,比如人类具备的属性,身份证,姓名,性别,身高,体重等等都是属性
  • 方法:是该类对象的行为,例如这个男孩会打篮球,那个女孩会唱歌等等都是属于方法,常常通过方法改变一些类中的属性值

类的成员可以分为三大类:字段、方法和属性

  • 字段:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
    • 普通字段属于对象
    • 静态字段属于类
  • 方法
  • 属性

类的所有成员在上一步骤中已经做了详细的介绍,对于每一个类的成员而言都有两种形式:

  • 公有成员,在任何地方都能访问
  • 私有成员,只有在类的内部才能方法
    • 私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)

普通字段

  • 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
  • 私有普通字段:仅类内部可以访问;
    • 如果想要强制访问私有字段,可以通过 【对象._类名__私有字段明 】访问(如:obj._C__foo),不建议强制访问私有成员。
#class C(object): # 新式类定义
class C: # 经典类定义
    # 所有静态成员在内存中只保留一份!
    name = "公有静态字段" # 任何地方都能访问
    __name = "私有静态字段" # 类内才能访问

    def __init__(self):
        # 普通字段每个实例独有一份
        self.name = '公有普通字段' # 对象、类本身、派生类可访问
        self.foo = "公有普通字段"
        self.__foo = "私有普通字段" # 仅类内可访问

    def func(self):
        print(self.foo) # 访问对象公有普通字段
        print(C.name) # 访问类公有静态成员
        print(C.__name) # 访问类私有静态字段
        print(C.__name)

class D(C):
    # 子类继承
    def show(self):
        print(C.__name) # 派生类访问类私有静态成员

if __name__ == "__main__":
    C.__name       # 类访问            ==> 错误
    obj = C()      # 类实例化 → 对象
    obj.func()     # 类内部可以访问     ==> 正确
    obj_son = D()
    obj_son.show() # 派生类中可以访问   ==> 错误
    obj._C__foo # 强制访问对象私有成员(不推荐)

字段(属性)

分成两类:属性、实例属性

  • 类属性:静态字段在内存中只保存一份
  • 实例属性:普通字段在每个对象中都要保存一份
class Province:
    # 静态字段(所有实例公用)
    country  '中国'

    def __init__(self, name):
        # 普通字段(每个实例独有)
        self.name = name

# 直接访问普通字段
obj = Province('河北省')
print(obj.name)
# 直接访问静态字段
Province.country

应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段

方法

方法包括:普通方法、静态方法和方法,三种方法在内存中都归属于类,区别在于调用方式不同。

  • 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
  • 方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
  • 静态方法:由类调用;无默认参数;
class Foo:
    # 各种方法说明
    def __init__(self, name):
        # 构造函数
        self.name = name

    def ord_func(self):
        """ 定义普通方法,至少有一个self参数 """
        # print self.name
        print '普通方法'

    @classmethod
    def class_func(cls):
        """ 定义类方法,至少有一个cls参数 """
        print '类方法'

    @staticmethod
    def static_func():
        """ 定义静态方法 ,无默认参数"""
        print '静态方法'

# 调用普通方法
f = Foo() # 类实例化
f.ord_func() # 普通方法
# 调用类方法
Foo.class_func() # 类方法
# 调用静态方法
Foo.static_func() # 类静态方法
  • 相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
  • 不同点:方法调用者不同、调用方法时自动传入的参数不同。

特殊方法

特殊方法也成为魔术方法,它的使用也很简单,用 __ 开始 以及结尾 就可以使用了

class Test():
    # __开头结尾的函数
    def __init__(self):
        print("我是初始化方法")

    def __len__(self):
        return 55

    def __str__(self):
        return "Hello World"

t = Test()

print(t)
print(len(t))
print(str(t))

# 常见的还有很多,大家可以自行尝试
'''
    特殊方法,也成为魔术方法
    特殊方法以 __ 开头和结尾,比如
    __init__ 初始化方法
    __str__() str() 这个特殊方法会在尝试将对象转换为字符串的时候调用
    __rpr__() rpr()
    __len__() 获取长度 len()
    __bool__() 返回布尔值 bool()
    __pow__
    __lshift__()
    __lt__()
    __add__()
    __and__
    __or__
    __eq__
    __sub__
'''

属性

Python中的属性其实是普通方法的变种。向调用属性一样调用函数

对于属性,有以下三个知识点:

  • 属性的基本使用
  • 属性的两种定义方式
class Foo:
    # 类属性使用方法
    def func(self):
        pass
    # 定义属性 —— 经典类只有这个方法,新式类就有多种功能
    @property
    def prop(self):
        pass
# ############### 调用 ###############
foo_obj = Foo()
foo_obj.func()
foo_obj.prop   # 调用属性,没有括号

由属性的定义和调用要注意一下几点:

  • 定义时,在普通方法的基础上添加 @property 装饰器;
  • 定义时,属性仅有一个self参数
  • 调用时,无需括号
    • 方法:foo_obj.func()
    • 属性:foo_obj.prop 注意:
  • 属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
  • 属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。

Python属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回。

属性的定义有两种方式:

  • 装饰器:在方法上应用装饰器,在类的普通方法上应用@property装饰器
  • 静态字段:在类中定义值为property对象的静态字段

新式类的属性比经典类的属性丰富

  • 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
  • 新式类中的属性有三种访问方式,并分别对应了三个被 @property、 @方法名.setter、 @方法名.deleter修饰的方法,定义为对同一个属性:获取、修改、删除

封装

封装是将内容封装到某个地方,以后再去调用被封装在某处的内容。

所以,在使用面向对象的封装特性时,需要:

  • 将内容封装到某处
  • 从某处调用被封装的内容
    • 通过对象直接调用
    • 通过self间接调用

封装的核心:封装是隐藏对象中一些不希望被外部访问到的属性或方法

class Foo:
 
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def detail(self):
        print self.name
        print self.age
 
obj1 = Foo('wupeiqi', 18)
print obj1.name    # 直接调用obj1对象的name属性
print obj1.age     # 直接调用obj1对象的age属性
obj1.detail()  # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
 
obj2 = Foo('alex', 73)
print obj2.name    # 直接调用obj2对象的name属性
print obj2.age     # 直接调用obj2对象的age属性
obj2.detail()  # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78

用装饰器把 getter 和 setter 更好的封装起来

class Person():
    '''
        __xxxx 成为隐藏属性
        __name -> _Person__name

        使用 _xxx 作为私有属性,没有特殊需求,不要修改私有属性
        类一般使用属性或方法不可见可以使用单下划线
    '''
    # 使用一个
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name
    
    '''
        setter
        getter 方法更好的使用
        @property,将一个 get 方法,转换为对象属性
        @属性名.setter 讲一个 set 方法,转换为对象属性
        两者缺一不可
    '''
    # setter 方法的的装饰器: @属性名.setter
    @name.setter
    def name(self, name):
        self.__name = name

p = Person('猴赛雷')
p.name = 'aaa' # 使用属性的方式 调用 setter 和 getter
print(p.name)

继承

经典类新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。

# 经典类
class C1():
    pass
# 新式类
class C1(object):
    pass

# 子类继承
class C2(C1):
    pass

super

super获取父类信息,用法

  • super() 直接获取当前类的父类
  • super(a, self) 获取指定类(a)的父类
import os

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length) # 默认获取当前类的父类
        super(Square, self).__init__(length, length) # 获取自定义类Square,等效于上一条语句

class Cube(Square):
    def surface_area(self):
        face_area = super(Square, self).area()# 获取自定义类 Square
        return face_area * 6

    def volume(self):
        face_area = super(Square, self).area()
        return face_area * self.length

多继承

Python的类如果继承了多个类,搜索方法分别是:深度优先和广度优先

  • 经典类时,多继承情况下,会按照深度优先方式查找
  • 新式类时,多继承情况下,会按照广度优先方式查找

多态

Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,其Python崇尚“鸭子类型”。

  • 多态:它指的是一个声明为 A类型的变量,可能是指 A类型的对象,也可以是 A类型的任何子类对象
class Dog():
    # 父类:狗
    def __init__(self,name,age):
        self.name = name

        if age > 100:
            raise ValueError("狗狗的年龄不可能这么大")
        self.__age = age

    def getAge(self):
        return self.__age

    def setAge(self,age):
        if age > 100:
            raise ValueError ("狗狗的年龄不可能这么大")
        self.__age = age

    # 定义公共方法
    def bark(self):
        print(f"{self.name} can bark")

    def eat(self):
        print(f"{self.name} 喜欢吃鸡肉")

class husky(Dog):
    # 子类:哈士奇
    def play(self):
        print(f"{self.name} 会打滚")
    # 方法覆盖
    def eat(self):
        print(f"{self.name} 喜欢吃火腿")
    # 方法覆盖
    def bark(self):
        print(f"{self.name} 在叫,嘻嘻....")

class teddy(Dog):
    # 子类:泰迪
    def other(self):
        print(f"{self.name} 会杂技")

# 公用函数:狗叫 —— 多态
def dog_bark(dog):
    # dog可以是父类的对象,也可以是子类的对象
    if isinstance(dog, Dog):
        dog.bark()

d = Dog("阿拉斯加",3)
dog_bark(d)

d1 = husky("哈士奇:二哈",2)
dog_bark(d1)

d2 = teddy("泰迪:皮皮",1)
dog_bark(d2)

使用多态时,并不需要给每一个 子类定义一个调用 bark() 的方法,pipi_bark(), dandan_bark(),只需要定义一个 公用的dog_bark(), 在调用的时候给它传递对应的子类对象即可。

接口实现

接口的特点如下:

  • 1、类通过继承接口的方式,来继承接口的抽象方法;
  • 2、接口并不是类(虽然编写类和方法的方式很相似);
  • 3、类描述对象的属性和方法(实现接口的类,必须实现接口内所描述的所有方法,否则必须声明为抽象类);
  • 4、接口包含类要实现的方法(接口无法被实例化,但可以被实现);

总结:接口只定义规范,不负责具体实现(具体实现由具体的实现者完成)

python中也有interface的概念,但是python其本身不提供interface的实现,需要通过第三方扩展库来使用类似interface的功能,一般都是Zope.interface

  • Python面向对象编程之Zope.interface安装使用( @implementer)implements
# coding=utf-8
from zope.interface import Interface
from zope.interface.declarations import implementer
 
# 定义接口
class MyMiss(Interface):
    def imissyouatlost(self,miss):
        """Say i miss you at lost to miss"""
 
@implementer(MyMiss) # 继承接口
class Miss:
    def imissyouatlost(self,somebody):
        """Say i miss you at lost to somebody"""
        return "i miss you at lost, %s!" % somebody
 
if __name__ == '__main__':
    z = Miss()
    hi = z.imissyouatlost('Zy')
    print(hi)

Python生态系统

python模块:

  • Numpy:多维数组容器,以矩阵为基础的数学计算模块,纯数学。
  • Scipy:科学计算函数库,基于Numpy,科学计算库,有一些高阶抽象和物理模型。比方说做个傅立叶变换,这是纯数学的,用Numpy;做个滤波器,这属于信号处理模型了,在Scipy里找。
  • Pandas:表格容器,提供了一套名为DataFrame的数据结构,比较契合统计分析中的表结构,并且提供了计算接口,可用Numpy或其它方式进行计算。金融行业
  • Matplotlib:仿造MATLAB的画图工具
  • Seaborn:画图工具

空值判断

python中的nan,即 Not A Number

计算结果为 nan 的情况

a = -float('inf')
b = -float('inf')
c = float('inf')
d = float('inf')
# 取值为nan的情形
a-b
c-d
0*a
0*c

numpy中nan的表示为np.nan

  • 不能使用 num==np.nan来判断
import numpy as np

x = np.nan
np.isnan(x)

方法

  • numpy 判断
  • Math 判断
  • Pandas 判断
  • 是否等于自身
import numpy as np
res = np.isnan(float('nan'))

import math
res = math.isnan(float('nan'))

import pandas as pd
res = pd.isna(float('nan'))


def is_nan(n):
    return n != n
res = is_nan(float('nan'))

def is_nan(n):
    return not float('-inf') < n < float('inf')
res = is_nan(nan)

数学函数

(1) 内置的数学函数

  • min()和max()函数可用于查找可迭代的最小值或最大值

(2) Math模块

  • Python还有一个名为math的内置模块,该模块扩展了数学函数的列表

常数

  • math.e 欧拉数(2.7182 …)
  • math.inf 浮点正无穷大
  • math.nan 浮点NaN(非数字)值
  • math.pi PI (3.1415…)
  • math.tau tau(6.2831 …)
x = min(5, 10, 25)
y = max(5, 10, 25)
x = abs(-7.25) # 绝对值
# x的y次幂(x^y), pow(x,y)
x = pow(4, 3)
# ---------------
import math

x = math.sqrt(64) # 8
x = math.ceil(1.4) # 2
y = math.floor(1.4) # 1
x = math.pi # 常数 pi

函数

  • math.acos() 数字的反余弦
  • math.acosh() 反双曲余弦
  • math.asin() 反正弦
  • math.asinh() 反双曲正弦值
  • math.atan() 弧度数的反正切
  • math.atan2() 弧度的y / x的反正切
  • math.atanh() 反双曲正切值
  • math.ceil() 数字四舍五入到最接近的整数
  • math.comb() 从n个项中选择k个项而无需重复和排序的方式数
  • math.copysign() 由第一个参数的值和第二个参数的符号组成的浮点数
  • math.cos() 数字的余弦值
  • math.cosh() 数字的双曲余弦值
  • math.degrees() 角度从弧度转换为
  • math.dist() 两个点(p和q)之间的欧几里得距离,其中p和q是该点的坐标
  • math.erf() 数字的误差函数
  • math.erfc() 数字的互补误差函数
  • math.exp() E的x次方
  • math.expm1() httpswwwcjavapycom
  • math.fabs() 数字的绝对值
  • math.factorial() 数字的阶乘
  • math.floor() 数字四舍五入到最接近的整数
  • math.fmod() x / y的余数
  • math.frexp() 指定数字的尾数和指数
  • math.fsum() 所有可迭代项(元组,数组,列表等)的总和。
  • math.gamma() x处的伽玛函数
  • math.gcd() 两个整数的最大公约数
  • math.hypot() 欧几里得范数
  • math.isclose() 检查两个值是否彼此接近
  • math.isfinite() 检查一个数是否是有限
  • math.isinf() 检查数字是否为无穷大
  • math.isnan() 检查值是否为NaN(不是数字)
  • math.isqrt() 将平方根向下取整至最接近的整数
  • math.ldexp() math.frexp()的逆,它是给定数字x和i的x * (2**i)
  • math.lgamma() x的对数伽玛值
  • math.log() 数字的自然对数,或数字以底为底的对数
  • math.log10() x的以10为底的对数
  • math.log1p() 1 + x的自然对数
  • math.log2() x的以2为底的对数
  • math.perm() 有序,无重复地从n个项中选择k个项的方式数
  • math.pow() x的y次方的值
  • math.prod() 可迭代的所有元素的乘积
  • math.radians() 度值转换为弧度
  • math.remainder() 可以使分子被分母完全整除的最接近的值
  • math.sin() 一个数字的正弦值
  • math.sinh() 数字的双曲正弦值
  • math.sqrt() 数字的平方根
  • math.tan() 数字的切线
  • math.tanh() 数字的双曲正切
  • math.trunc() 数字的截断整数部分

Numpy

Reference

《Python for data analysis》
NumPy Reference — NumPy v1.12 Manual

基本操作

import numpy as np

a = np.arange(9).reshape(3,3)
b = a.view() # 视图方法创造一个新的数组对象指向同一数据
print 'a=\n', a
print '生成同型矩阵:\n', np.zeros_like(a)
np.empty_like(a), np.ones_like(a)
print 'a形状参数:', len(a), a.size, a.shape

b = [[2,4,1],[1,3,2]]
print np.reshape(b, (3,2))

c = (a==3) | (a==5)
print '数值筛选:下标\n{}\n取出的数值{}'.format(c, a[c])
a[2,1:] = 0
print '分块赋值后的a=\n{}'.format(a)
print 'np.all(a)={}, np.any(a)={}'.format(np.all(a), np.any(a))

a = np.arange(9) # 一维数组
a = a.reshape(3,3) # 一维变二维 (9,) -> (3,3)
print 'a=', a
print 'double:\n', a*2 # 逐个元素处理
a1 = np.repeat(a, 2) # 元素重复几遍(列向)
print 'repeat用法:(2)\n', a1
a1 = np.repeat(a, 2, axis=0) # 行向重复
print 'repeat用法:(2, axis=0)\n', a1
#print(np.repeat(a, (2, 1), axis=0))
a2 = np.tile(a, 2) # 以对象为单位重复
print 'tile用法:(2)\n', a2
a2 = np.tile(a, (2,2)) # 对a重复2*2
print 'tile用法:(2,2)\n', a2
a = np.squeeze(a)  # 从数组的形状中删除单维条目,即把shape中为1的维度去掉

拼接

Python中numpy数组的合并有很多方法,如

  • list.append(): list方法,占用内存大
  • np.concatenate():不会占用太多内存
  • np.stack()
  • np.hstack():水平拼接
  • np.vstack():垂直拼接
  • np.dstack() 其中最泛用的是第一个和第二个。第一个可读性好,比较灵活,但是占内存大。第二个则没有内存占用大的问题

复制操作:

  • np.repeat:复制的是多维数组的每一个元素;axis来控制复制的行和列
  • np.tile:复制的是多维数组本身;
import numpy as np

a = np.arange(9) # 一维数组
a = a.reshape(3,3) # 一维变二维 (9,) -> (3,3)
print 'a=', a
print 'double:', a*2 # 逐个元素处理
a1 = np.repeat(a, 2) # 元素重复几遍
print 'repeat用法:', a1
a2 = np.tile(a, 2) # 矩阵重复
print 'tile用法:', a2
a = np.squeeze(a)  # 从数组的形状中删除单维条目,即把shape中为1的维度去掉
a = np.arange(9).reshape(3,3)
b = a * 2

print '水平拼接:', np.hstack((a,b)) # 水平拼接
print '水平拼接:', np.concatenate((a,b),axis=1) # 水平拼接
print '垂直拼接:', np.concatenate((a,b),axis=0) # 水平拼接
print '垂直拼接:', np.vstack((a,b))
print '纵轴拼接:', np.dstack((a,b))
#print '列组合:', column_stack(a) # 一维场景,二维同hstack
#print '行组合:', row_stack(a) # 一维场景,二维同vstack

代数&矩阵

import numpy as np

a=[[400,-201],[-800,401]]
rank = np.linalg.matrix_rank(a) # 秩
norm = np.linalg.norm(a) # 范数
det = np.linalg.det(a) # 特征值 -399.99999
inv = np.linalg.inv(a) # 求逆
pinv = np.linalg.pinv(a) # 求伪逆
value, vector = np.linalg.eig(a) # 特征值和特征向量
value = np.linalg.eigvals(a) # 特征值
cond = np.linalg.cond(a) # 条件数 2503,病态
qr = np.linalg.qr(a) # 	QR分解: 正交矩阵*上三角
svd = np.linalg.svd(a) #  SVD分解

# one-hot
print(np.eye(5)) # 5*5单位矩阵
print(np.eye(5,4)) # 5*4矩阵,非方阵
print(np.eye(5,5,k=1)) # 单位阵,对角线上移k位
print(np.eye(5,5,k=-1)) # 单位阵,对角线下移k位
# ------ one-hot 编码-------
#设置类别的数量
num_classes = 10
#需要转换的整数
arr = [1,3,4,5,9]
#将整数转为一个10位的one hot编码
print(np.eye(num_classes)[arr])

# 由one-hot转换为普通np数组
data = [argmax(one_hot)for one_hot in one_hots]

文件处理

Numpy提供了几种数据保存的方法

  • 二进制:只能保存为二进制文件,且不能保存当前数据的行列信息
    • tofile与fromfile
    • save与load:Numpy专用的二进制格式保存数据,它们会自动处理元素类型和形状等信息。savez()提供了将多个数组存储至一个文件的能力,调用load()方法返回的对象,可以使用数组名对各个数组进行读取。这种方法,保存文件的后缀名字一定会被置为.npy
  • 文本格式:
    • savetxt与loadtxt
#------二进制-------
a.tofile("filename.bin")
 b = numpy.fromfile("filename.bin",dtype = **)
np.save("a.npy", a.reshape(3,4))
c = np.load("a.npy")
# 存储多个数组到一个文件
a = np.array([[1,2,3],[4,5,6]])
b = np.arange(0,1.0,0.1)
c = np.sin(b)
np.savez("result.npz", a, b, sin_arr=c)  #使用sin_arr命名数组c
r = np.load("result.npz") #加载一次即可
#------文本-------
numpy.savetxt("filename.txt",a)
b =  numpy.loadtxt("filename.txt")

问题

汇总遇到过的问题

【2024-3-5】AttributeError: module ‘numpy’ has no attribute ‘object’

解决方法

  • numpy版本倒退到支持使用 np.object 的最后一个版本1.23.4
pip install numpy==1.23.4
# 1.24不行!

Arrow

Apache arrow 是用于内存计算的高性能列式数据存储格式。

PyArrow 是 apache arrow 的 python库,PyArrow 与 NumPy、pandas和内置的ython对象有很好的集成。基于Arrow的C++实现。

Pandas

  • Pandas 提供了数据结构——DataFrame,可以高效的处理一些数据分析任务。我们日常分析的数据,大多是存储在类似 excel 的数据表中,Pandas 可以灵活的按列或行处理数据,几乎是最常用的工具了。
  • 官方api大全

介绍

Pandas(Python data analysis)是一个Python数据分析的开源库。名字源于panel data(计量经济学术语,面板数据)和python data analysis

pandas三种数据结构:

  • Series(一维): 系列(Series)是能够保存任何类型的数据(整数,字符串,浮点数,Python对象等)的一维标记数组。轴标签统称为索引。
  • DataFrame(二维)
  • Panel(三维)

Pandas按行按列遍历Dataframe的几种方式

简单对上面三种方法进行说明:

  • iterrows(): 按行遍历,将DataFrame的每一行迭代为(index, Series)对,可以通过row[name]对元素进行访问。
  • itertuples(): 按行遍历,将DataFrame的每一行迭代为元祖,可以通过row[name]对元素进行访问,比iterrows()效率高。
  • iteritems():按列遍历,将DataFrame的每一列迭代为(列名, Series)对,可以通过row[index]对元素进行访问。

Series

系列(Series)是能够保存任何类型的数据(整数,字符串,浮点数,Python对象等)的一维标记数组。轴标签统称为索引

  • 数据访问
  • 序列聚合
import pandas as pd
import numpy as np

# 从ndarray创建series
pd.Series(np.array([1,2,3,4])) # index自动设置:0-3
# 从字典创建series
pd.Series({'a':1, 'b':2, 'c':3}) # index为字典的key: a,b,c

DataFrame

DataFrame(数据帧)是带有标签的二维数据结构,列的类型可能不同。可以想象成一个电子表格或SQL表,或者 Series 对象的字典。它一般是最常用的pandas对象。

创建方式:列表、字段

import pandas as pd
import numpy as np

# 从 list 创建 dataframe
pd.DataFrame([[1,2],[3,4]]) # index自动设置
   
# List1 
lst = [['tom', 'reacher', 25], ['krish', 'pete', 30],
       ['nick', 'wilson', 26], ['juli', 'williams', 22]]
   
df = pd.DataFrame(lst, columns =['FName', 'LName', 'Age'], dtype = float)
print(df)
# 从 字典 创建 dataframe
pd.DataFrame({'a':[1,3], 'b':[2,4]}) # columns 为字典的key: a,b
pd.DataFrame({'a':[1,3], 'b':[2,4]}, columns=['a','b']) 

读文件

python中pd读取文件

  • parse_dates(动词,主动解析格式)
    • parse_dates = True : 尝试解析index为日期格式;
    • parse_dates = [ 0,1,2,3,4] : 尝试解析0,1,2,3,4列为时间格式;
    • parse_dates = [ [ ‘考试日期’,’考试时间’] ] :传入多列名,尝试将其解析并且拼接起来,parse_dates[[0,1,2]]也有同样的效果;
    • parse_dates = {‘考试安排时间’:[ ‘考试日期’,’考试时间’]},将会尝试解析日期和时间拼接起来,并将列名重置为’考试安排时间’;
    • 注意:重置后列名不能和原列名重复
  • date_parser(名词,指定解析格式去解析某种不常见的格式)
    • date_parser需要配合parse_dates工作,具体需要传入函数
    • 例如时间为2021年2月24日,可以传入
    • parse_dates=[ 0 ]
    • date_parser=lambda x:pd.to_datetime(x,format=’%Y年%m月%d日’)

从github加载

【2022-1-24】直接加载github数据

import pandas as pd

# github repo 中文件地址,需要转换:"https://github.com/sksujan58/Multivariate-time-series-forecasting-using-LSTM/blob/main/train.csv"
url = "https://raw.githubusercontent.com/sksujan58/Multivariate-time-series-forecasting-using-LSTM/master/train.csv"
#df = pd.read_csv(url,parse_dates=["Date"])
#df0 = pd.read_csv(url)
#df = pd.read_csv("train.csv", parse_dates=["Date"],index_col=[0])
df = pd.read_csv(url, parse_dates=["Date"], index_col=[0]) # Date格式转换(str→date),指定Date作为索引列

常规操作

  • 图解pandas模块21个常用操作

  • 列选择
  • 行选择
  • 元素选择
  • 条件查询:对各类数值型、文本型,单条件和多条件进行行选择
  • 聚合
    • 聚合函数:
      • data.function(axis=0) 按列计算
      • data.function(axis=1) 按行计算
  • 分组统计
  • 透视表:透视表是pandas的一个强大的操作,大量的参数完全能满足你个性化的需求。
  • 缺失值
  • 查找替换: pandas提供简单的查找替换功能,如果要复杂的查找替换,可以使用map(), apply()和applymap()
  • 数据合并: 两个DataFrame的合并,pandas会自动按照索引对齐,可以指定两个DataFrame的对齐方式,如内连接外连接等,也可以指定对齐的索引列。
  • 更改列名(columns index)
  • apply函数: 单值运算

类型转换

数值类型转换

【2023-6-4】使用 round 方法直接对浮点数做数值位数转换

  • round()函数是做四舍五入,而decimals参数是设置保留小数的位数
import numpy as np
import pandas as pd

df = pd.DataFrame([(.21, .32), (.01, .6), (.66, .03), (.21, .183)],columns=['dogs', 'cats'])
df
#    dogs   cats
# 0  0.21  0.320
# 1  0.01  0.600
# 2  0.66  0.030
# 3  0.21  0.183

df['NewColumn'] = df['Column'].astype(float)

# --------------
# 统一保持2位小数
df.round(2)
# 统一保持一位小数
df.round(1)
# ------------
# 等效表示
df['dogs'] = df['dogs'].map(lambda x: ('%.2f')%x)
df['dogs'] = df['dogs'].map(lambda x: format(x,'%.2%')) # 加百分号
df['dogs'] = df['dogs'].map(lambda x: format(x,',')) # 设置千分位

# 指定列名设置精度,未指定的则保持原样
df.round({'dogs': 2})
#    dogs   cats
# 0  0.21  0.320
# 1  0.01  0.600
# 2  0.66  0.030
# 3  0.21  0.183
# 两列分别设置不同的精度
df.round({'dogs':2, 'cats':1})
#    dogs  cats
# 0  0.21   0.3
# 1  0.01   0.6
# 2  0.66   0.0
# 3  0.21   0.2

变量类型转换

pandas 中的 to_dict 可以对DataFrame类型的数据进行转换,可以选择六种的转换类型,分别对应于参数 ‘dict’, ‘list’, ‘series’, ‘split’, ‘records’, ‘index’

to_dict()函数有两种用法

  • (1)pd.DataFrame.todict()
    • ‘dict’: {column->{index->values}}
    • ‘list’: {column:[values]}
    • ‘series’:{column->series}
    • ‘split’:{index->[index], column->[column], value->[value]}
    • ‘record’:[{column->value}] ,返回的为列表, 记录每个样本
    • ‘index’:{index->{column->value}} ,与”dict”类似,只不过index与column互换位置
  • (2)pd.Series.to_dict() : 将Series转换成{index:value}
  • 其中Series.to_dict()较简单
df = pd.DataFrame(data=[11,22,33,44], index=['a','b','c','d']).rename(columns={0:'code'})`
# 转换类型
# (1)利用itertuples()转成字典格式
{i:v for i,v in df.itertuples()}
# (2)使用to_dict方法
df.T.to_dict('r')[0]
df.code.to_dict()
# (3)高级版
new_df = df.set_index(['code', 'date']).unstack(level=0).droplevel(level=0, axis=1).to_dict()

#--------------
for t in ('dict', 'list', 'records', 'split', 'index', 'series'):
    print(t, df.to_dict(t))
# dict {'code': {'a': 11, 'b': 22, 'c': 33, 'd': 44}}
# list {'code': [11, 22, 33, 44]}
# records [{'code': 11}, {'code': 22}, {'code': 33}, {'code': 44}]
# split {'index': ['a', 'b', 'c', 'd'], 'columns': ['code'], 'data': [[11], [22], [33], [44]]}
# index {'a': {'code': 11}, 'b': {'code': 22}, 'c': {'code': 33}, 'd': {'code': 44}}
# series {'code': a    11
# b    22
# c    33
# d    44
# Name: code, dtype: int64}

可视化讲解

import pandas as pd
import io

csv = '''
breed,type,longevity,size,weight
German Shepherd,herding,9.73,large,
Beagle,hound,12.3,small,
Yorkshire Terrier,toy,12.6,small,5.5
Golden Retriever,sporting,12.04,medium,60.0
Bulldog,non-sporting,6.29,medium,45.0
Labrador Retriever,sporting,12.04,medium,67.5
Boxer,working,8.81,medium,
Poodle,non-sporting,11.95,medium,
Dachshund,hound,12.63,small,24.0
Rottweiler,working,9.11,large,
Boston Terrier,non-sporting,10.92,medium,
Shih Tzu,toy,13.2,small,12.5
Miniature Schnauzer,terrier,11.81,small,15.5
Doberman Pinscher,working,10.33,large,
Chihuahua,toy,16.5,small,5.5
Siberian Husky,working,12.58,medium,47.5
Pomeranian,toy,9.67,small,5.0
French Bulldog,non-sporting,9.0,medium,27.0
Great Dane,working,6.96,large,
Shetland Sheepdog,herding,12.53,small,22.0
Cavalier King Charles Spaniel,toy,11.29,small,15.5
German Shorthaired Pointer,sporting,11.46,large,62.5
Maltese,toy,12.25,small,5.0
'''

dogs = pd.read_csv(io.StringIO(csv))

dogs['longevity'] # 选择一列
dogs.drop(columns=['type']) # 删除列

df.loc[:, df.loc['two'] <= 20] # 过滤行(按two列取值过滤)
# 多条件过滤, 用 & 符号连接
dogs.loc[(dogs['size'] == 'medium') & (dogs['longevity'] > 12), 'breed']

dogs.groupby('size').mean() # 按size分组,组内求均值
dogs.groupby(['type', 'size']) # 多列分组

(dogs[dogs['size'] == 'medium'] # 筛选中型犬(size)
 .sort_values('type') # 按type列排序
 .groupby('type').median() # 按type列分组
)

(dogs
 .sort_values('size')
 .groupby('size')['height'] # 分组后取height列,应用到后面的聚合运算
 .agg(['sum', 'mean', 'std'])
)
# join操作
ppl.join(dogs)
ppl.merge(dogs, left_on='likes', right_on='breed', how='left') # merge操作
dogs.pivot_table(index='size', columns='kids', values='price') # 按kids钻取、聚合

map/apply/applymap

数据处理三板斧——map、apply、applymap详解

Pandas三板斧(map、apply和applymap)可以对DataFrame进行逐、逐和逐元素的操作,对应这些操作

  • map: 字典还是函数进行映射,map方法把对应数据逐个当作参数传入到字典或函数中,得到映射后的值。
  • apply: 和map方法类似,区别 apply能够传入功能更为复杂的函数
  • applymap: 对DataFrame中每个单元格执行指定函数的操作,虽然用途不如apply广泛,但在某些场合下还是比较有用的
  • 数据
# 数据准备
boolean=[True,False]
gender=["男","女"]
color=["white","black","yellow"]
data=pd.DataFrame({
    "height":np.random.randint(150,190,100),
    "weight":np.random.randint(40,90,100),
    "smoker":[boolean[x] for x in np.random.randint(0,2,100)],
    "gender":[gender[x] for x in np.random.randint(0,2,100)],
    "age":np.random.randint(15,90,100),
    "color":[color[x] for x in np.random.randint(0,len(color),100) ]
})

# ----- map ------
# ① 使用字典进行映射
data["gender"] = data["gender"].map({"男":1, "女":0})

# ② 使用函数
def gender_map(x):
    gender = 1 if x == "男" else 0
    return gender
# 注意这里传入的是函数名,不带括号
data["gender"] = data["gender"].map(gender_map)

# ------ apply -----
def apply_age(x,bias):
    return x+bias
#以元组的方式传入额外的参数
data["age"] = data["age"].apply(apply_age, args=(-3,))
# 沿着0轴求和
data[["height","weight","age"]].apply(np.sum, axis=0)
# 沿着0轴取对数
data[["height","weight","age"]].apply(np.log, axis=0)
# 计算BMI
def BMI(series):
    weight = series["weight"]
    height = series["height"]/100
    BMI = weight/height**2
    return BMI
data["BMI"] = data.apply(BMI,axis=1)

# ------ applymap -----
# 浮点数保留两位小数
df.applymap(lambda x:"%.2f" % x)

【2024-4-11】传入多列

import numpy as np
import pandas as pd
 
df = pd.DataFrame ({'a' : np.random.randn(6),
             'b' : ['foo', 'bar'] * 3,
             'c' : np.random.randn(6)})
 
def my_test(a, b):
    """
        多输入处理函数
    """
    return a + b
 
df['Value'] = df.apply(lambda row: my_test(row['a'], row['c']), axis=1)
print (df)
 
#输出结果形如:
#           a    b         c     Value
# 0 -0.276507  foo -3.122166 -3.398672
# 1 -0.589019  bar -1.150915 -1.739934
# 2 -0.485433  foo  1.296634  0.811200
# 3  0.469688  bar -0.554992 -0.085304
# 4  1.297845  foo  1.672957  2.970802
# 5 -0.702724  bar -1.609585 -2.312309

查看数据

代码:

import pandas as pd

#查看、检查数据
df.head(n)# 查看DataFrame对象的前n行
df.tail(n)# 查看DataFrame对象的最后n行
df.shape()# 查看行数和列数
df.info()# 查看索引、数据类型和内存信息
df.describe()# 查看数值型列的汇总统计
s.value_counts(dropna=False)# 查看Series对象的唯一值和计数
df.apply(pd.Series.value_counts)# 查看DataFrame对象中每一列的唯一值和计数
dict(df_out['关键路径'].map(parse).value_counts()) # 转成dict格式

# 数据统计
df.describe()# 查看数据值列的汇总统计
df.mean()# 返回所有列的均值
df.corr()# 返回列与列之间的相关系数
df.count()# 返回每一列中的非空值的个数
df.max()# 返回每一列的最大值
df.min()# 返回每一列的最小值
df.median()# 返回每一列的中位数
df.std()# 返回每一列的标准差
# ------ numpy --------
import numpy as np
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(np.median(a)) # 中位数
print(np.percentile(a, 25)) # 25%分位数
print(np.percentile(a, 75)) # 75%分位数

# 查找数据
df = pd.DataFrame({'BoolCol': [1, 2, 3, 3, 4],'attr': [22, 33, 22, 44, 66]}, index=[10,20,30,40,50])
df = pd.DataFrame({'BoolCol': [1, 2, 3, 3, 4],'attr': [22, 33, 22, 44, 66]})
print(df)
print(df['attr']) # series对象

a1 = df[(df.BoolCol==3)&(df.attr==22)].index.tolist() # 返回下标
a2 = df[(df.BoolCol==3)&(df.attr==22)].values.tolist() # 返回数值
print(a1,a2)

#df.index # 行序号
df.columns # 列名
df.values # 数据内容, 当df是series类型时直接转ndarray类型
df.values.tolist() # 转list结构
df.drop_duplicates().values.tolist() # 去除重复行
np.array(df).tolist() # 成功
df._get_value(2,'price') # 取指定元素数值
#df['lon'],df['lat'],df[:30] # 按照列名读取数据
#df.ix[:30,:3] # 使用ix、loc或者iloc(按照下标组合)进行行列双向读取,即切片操作
#df.ix[:20,['lon','lat']] # 跨属性组合选取
key_list = ['total_bedrooms','population']
df.loc[:100, key_list] # 同上?
#new = df.iloc[:20,[1,2]]
#new.describe # 基本统计信息
#type(new)
#df[df.lon>117] # 按照数值过滤筛选
#df[df.time<'2016-07-20']
#new.values.tolist() # DataFrame转成list结构
#[2018-11-2]另外一种方法:np.array(new).tolist()
#df.sort(columns='time') # 排序
#[2018-10-12] 将pandas元素提取(numpy结构)并转化为list结构
for idx in xrange(df.size):
    item = df.ix[idx,key_list].get_values().tolist()
#提取某个取值的数据,注意:==,不是=!
df[df.businesstype==7]

describe 用法

describe生成描述性统计数据,统计数据集的集中趋势,分散和行列的分布情况,不包括 NaN值。

  • 对于数值型数据,输出结果指标包括: count, mean,std,min,max以及第25百分位,中位数(第50百分位)和第75百分位。
  • 对于对象类型数据(例如字符串或时间戳),结果指数将包括 count,unique, top,和freq。top标识最常见的值。freq标识最常见的值的出现频次。时间戳还包括first和last指标。如果多个对象值具有最高计数,那么将从具有最高计数的那些中任意选择count和top结果。
  • 对于混合数据类型 DataFrame,默认情况下仅返回数值类型列的分析。如果数据框仅包含没有任何数值列的对象和文本类型数据,则默认情况下将返回对象和文本类型数据列的分析结果。如果include=’all’作为选项提供,则结果将包括每种类型数据的并集。当然,我们可以使用include和exclude参数限制其列在DataFrame被分析时的输出。在分析Series时会忽略这些参数。

三个参数:

  • percentiles:赋值类似列表形式,可选
    • 表示百分位数,介于0和1之间。默认值为 [.25, .5, .75],分别返回第25,第50和第75百分位数。
    • 可自定义其它值,用法为df.describe(percentiles=[.xx])。
  • include:’all’,类似于dtypes列表或None(默认值),可选
    • 要包含在结果中的数据类型的白名单。对于Series不可用。以下是选项:
    • ‘all’:输入的所有列都将包含在输出中。
    • 类似于dtypes的列表:将结果限制为提供的数据类型。将结果限制为数字类型用法:numpy.number。要将其限制为对象列用法:numpy.object。字符串也可以以select_dtypes(例如df.describe(include=[‘O’]))的方式使用。要选择分类类型,请使用’category’
    • 无(默认):结果将包括所有数字列。
  • exclude:类似于dtypes列表或None(默认值),可选,
    • 要从结果中除去的黑名单数据类型列表。Series不可用。以下是选项:
    • 类似于dtypes的列表:从结果中排除提供的数据类型。排除数值类型用法:numpy.number。要排除对象列,使用numpy.object。字符串也可以以select_dtypes(例如df.describe(include=[‘O’]))的方式使用。要排除分类类型,请使用’category’
    • 无(默认):结果将不包含任何内容。

【2022-8-29】pandas.DataFrame.describe方法小析

import pandas as pd  
import re 

# making data frame  
data = pd.read_csv("https://media.geeksforgeeks.org/wp-content/uploads/nba.csv")  
# removing null values to avoid errors  
data.dropna(inplace = True)  # 剔除空值
# percentile list
perc =[.20, .40, .60, .80] # 分位数列表
# list of dtypes to include 
include =['object', 'float', 'int'] # 要统计的列
# calling describe method 
desc = data.describe(percentiles = perc, include = include) 
desc = data.describe([.30]) # 限定输出30%分位数
desc = data.describe([0.05 * x for x in range(20)]) # 限定多个分位数
desc = data.describe(include='all') # 输出所有列
desc = data.describe(include=[np.number]) # 仅输出数值列
desc = data.describe(include=[np.object]) # 仅输出对象类型
desc = data.describe(include=[np.category]) # 仅输出离散类型
desc = data.describe(exclude=["category", np.number]) # 排除离散类型
# display 
desc

表格样式

【2022-11-4】表格格式调整,pandas 样式配置操作

  • 右对齐(默认)→ 左对齐
import pandas as pd
# initialise data of lists.
data = [['Raghav', 'Jeeva', 'Imon', 'Sandeep'],
        ['Deloitte', 'Apple', 'Amazon', 'Flipkart'],
        [2,3,7,8]]
# Create DataFrame
df = pd.DataFrame(data)
# 设置对齐方式
df_new = df.style.set_properties(**{'text-align': 'left'})
# 设置表格标题
df.style.set_caption('学生成绩表') 
# 保留两个小数
df.style.set_precision(2)
# 等同于
df.round(2).style
# 设置缺失值
df.style.set_na_rep("暂无")
data.style.format(None, na_rep="-")
# 隐藏索引和列
df.style.hide_index() # 不输出索引
df.style.hide_columns(['C','D']) # 不输出指定列

# ---- 单元格设置 CSS 样式 ----
# 指定列,设置字色为红色
df.style.set_properties(subset=['Q1'], **{'color': 'red'})
# 一些其他示例
df.style.set_properties(color="white", align="right")
df.style.set_properties(**{'background-color': 'yellow'})
df.style.set_properties(**{'width': '100px', 'font-size': '18px'})
df.style.set_properties(**{'background-color': 'black',
                           'color': 'lawngreen',
                           'border-color': 'white'})

# 给 <table> 标签增加属性,可以随意给定属性名和属性值。
df.style.set_table_attributes('class="pure-table"')
# ... <table class="pure-table"> ...
df.style.set_table_attributes('id="gairuo-table"')

# 固定表头:便于查看数据
df.style.set_sticky(axis='columns').to_html('333.html')
# 固定索引行标签
df.style.set_sticky(axis='index').to_html('333.html')
display(df_new)

表格美化

# 指定列最大值背景高亮
def highlight_max(s):    
    '''    对列最大值高亮(黄色)处理    '''
    is_max = s == s.max()    
    return ['background-color: yellow' if v else '' for v in is_max]data.style.apply(highlight_max,subset=['2021人口', '2020人口', '面积','单位面积人口','人口增幅','世界占比'])
# 数值颜色突出
def color_red(s):    
    is_max = s > 200    
    return ['color : red' if v else '' for v in is_max]data.style.apply(color_red,subset=['单位面积人口'])

# 数据条显示
import pandas as pd
data = pd.read_excel(r"2021世界人口数据.xlsx")# 数据条显示指定列数据大小
data.style.bar(subset=['2021人口', '2020人口'], color='#FFA500')

# 色阶显示, 用df.style.background_gradient实现
import seaborn as sns
# 使用seaborn获取颜色
cm = sns.light_palette("green", as_cmap=True)# 色阶实现
data.style.background_gradient(cmap=cm,subset=['2021人口', '2020人口', '面积','单位面积人口','人口增幅','世界占比'])
# 使用内置色阶类型,调节颜色范围
data.style.background_gradient(cmap='viridis',high=0.5,low=0.3,subset=['2021人口', '2020人口', '面积','单位面积人口','人口增幅','世界占比'])
# 将样式输出到表格
import pandas as pd
import numpy as np
data = pd.read_excel(r"2021世界人口数据.xlsx")
data.style.background_gradient(cmap='viridis',subset=['2021人口', '2020人口', '面积','单位面积人口','人口增幅','世界占比']).to_excel('style.xlsx', engine='openpyxl')

数据选择

代码:

  • 【2022-9-17】使用复合条件时,务必使用()将单个条件括起来!否则逻辑容易失效!

条件筛选

#数据选取
df[col]# 根据列名,并以Series的形式返回列
df[[col1, col2]]# 以DataFrame形式返回多列
s.iloc[0]# 按位置选取数据
s.loc['index_one']# 按索引选取数据
df.iloc[0,:]# 返回第一行
df.iloc[0,0]# 返回第一列的第一个元素

DataFrame.lookuprow_labelscol_labels # DataFrame基于标签的“花式索引”功能。 
DataFrame.popitem # 返回项目并从框架中删除。 类似方法del

# 多个条件通过&、|连接
student[(student['Sex']=='F') & (student['Age']>12)]
# Pandas实现where filter
df[df['sex'] == 'Female']
df[df['total_bill'] > 20]
#在where子句中常常会搭配and, or, in, not关键词,Pandas中也有对应的实现
df[(df['sex'] == 'Female') & (df['total_bill'] > 20)] # and
df[(df['sex'] == 'Female') | (df['total_bill'] > 20)] # or
df[(df['sex'] == 'Female') | (~df['total_bill'] > 20)] # not,非,用~符号
df[~(df['sex'] == 'Female')]
# 存在性判断
df[df['total_bill'].isin([21.01, 23.68, 24.59])] # in
df[-(df['sex'] == 'Male')] # not
df[-df['total_bill'].isin([21.01, 23.68, 24.59])]
# 判断元素是否存在 isin
a = 1 if 'NLU泛化' in stat['failure'].values else 0
# string function 使用str函数
df = df[(-df['sex'].isin(['male', 'female'])) & (-df.sex.str.contains('^m\d+$'))] # 不管用!
# [2022-10-15] str里使用正则表达式
df_out['关键路径'].str.contains('进入IM=>初始faq=>输入\([^=>]*?\)=>接通人工', regex=True)
# 对where条件筛选后只有一行的dataframe取其中某一列的值,其两种实现方式如下:
total = df.loc[df['tip'] == 1.66, 'total_bill'].values[0]
total = df.get_value(df.loc[df['tip'] == 1.66].index.values[0], 'total_bill')

极值

nlargest和nsmallest获取最大和最小值

df.nlargest(5, 'ColumnName')
df.nsmallest(5, 'ColumnName')

采样

Pandas 行采样/抽样技术:

  • (1) 随机抽样
  • (2) 有条件采样
  • (3) 以恒定速率采样
from sklearn.datasets import load_iris 
import pandas as pd

data = load_iris() 
df = pd.DataFrame(data.data, columns=data.feature_names)

print('----- 随机采样 ----- ')
subset = df.sample(n=100) # 行采样
print('行数采样: ', subset.shape)
subset = df.sample(frac=0.5)
print('比例采样: ', subset.shape)
print('----- 条件采样 ----- ')
condition = df['sepal width (cm)'] < 3
subset = df[condition].sample(n = 10)
print('条件采样: ', subset.shape)
print('----- 恒定速率采样 ----- ')
subset = df[::10] # 每隔10个采样

resample

对时间序列数据进行重新采样。

df.resample('D').sum()

query SQL

Pandas中的.query()函数像sql一样操作, 使用bool表达式

  • 不完全是SQL,但使一些基本查询变得更容易,一个简单的WHERE.filter()等价方法。
  • 使用&或者and、or、not等逻辑运算符,以及其它常见的操作符(例如==,<,>,!=等)来连接过滤器
# 常见的切片语法
query_df = df[df[df['Col_1'] > df['Col_2']]]
# query 语法
query_df = df.query("Col_1 > Col_2") # 不能用and/or, 改成 &,|
df.query('total_bill > 20')

SQL

pandas DataFrame是二维表格,数据库中的表也是二维表格,因此使用sql语句就显得水到渠成

pandasql

pandasql使用SQLite作为其操作数据库,Python自带SQLite模块,不需要安装,便可直接使用。

注意

  • pandasql读取DataFrame中日期格式的列,默认会读取年月日、时分秒,因此要学会使用sqlite中的日期处理函数,方便转换日期格式
  • sqlite函数大全
# !pip install pandasql
import pandas as pd
from pandasql import sqldf

df = pd.read_excel('test.xlsx')
# ======== 单次声明全局变量 ========
global df
query = "select * from df limit 10"
sqldf(query)

merge_data_sql = sqldf(""" SELECT * 
                            FROM apm_data_df 
                            LEFT OUTER JOIN pingips_data_df
                            ON apm_data_df.pingip = pingips_data_df.pingip 
                            WHERE apm_data_df IS null
                            """)
# 一条语句合并
merge_result = sqldf(merge_data_sql, globals())
# ======== 或一次性申明 ========
pysqldf = lambda q: sqldf(q, globals())
pysqldf(query)

DuckDB

DuckDB来查询DataFrame

DuckDB 是一个开源的内存分析型数据库,专为高效处理分析工作负载而设计。

  • 被称为SQLite的分析/OLAP等效工具,因为提供了类似SQL的查询语言,并支持在Pandas DataFrame上执行SQL查询。

DuckDB是一个强大而灵活的分析型数据库,它的集成性和性能优势使得在Pandas中使用SQL查询变得更加便捷和高效

# pip install duckdb
import duckdb

# pd读取
df = ....

# 简单查询
duckdb.query("select * from a limit 5").df()

# 使用复杂SQL
sql = """
select * 
    a."x", x
from a
where
    x == 'usa' and a."y" > 10

"""
duckdb.query(sql).df()

pandas 正则表达式

Python正则表达式

可使用函数:str.*

  • match
  • fullmatch
    • Stricter matching that requires the entire string to match.
  • contains
    • Analogous, but less strict, relying on re.search instead of re.match.
  • extract
    • Extract matched groups.
  • repalce
    • str.replace(r’(“.),(.”)’, r’\1 \2’, regex=True)

使用函数:

  • Series.str.contains(pat,case = True,flags = 0,na = nan,regex = True)
  • 测试 pattern 或 regex 是否包含在Series或Index的字符串中。
  • 返回布尔值系列或索引,具体取决于给定模式或正则表达式是否包含在系列或索引的字符串中。

参数说明

参数 类型 说明
pat str类型,字符序列或正则表达式 字符串或正则表达式
case bool,默认为True 如果为True,区分大小写。
flags int,默认为0(无标志) 标志传递到re模块,例如re.IGNORECASE。na : 默认NaN, 填写缺失值的值。
regex bool,默认为True 如果为True,则假定pat是正则表达式。
如果为False,则将pat视为文字字符串。

除此之外,还可以使用以下函数

import pandas as pd
import numpy as np
s1 = pd.Series(['Mouse', 'dog', 'house and parrot', '23', np.NaN])
s1.str.contains('\d', regex=True) # 启动正则,匹配数字,结果:23
s1.str.contains('og', regex=False) # 关闭正则,当做字符串,结果:只匹配到 dog
s1.str.contains('oG', case=True, regex=True) # 区分大小写,结果:无匹配
s1.str.contains('og', na=True, regex=True) # 把有NAN的转换为True,即空值都命中
s1.str.contains('og', na=False, regex=True) # 把有NAN的转换为False,即空值都过滤

import re
s1.str.contains('PARROT', flags=re.IGNORECASE, regex=True) # 使用re里的正则选项

# 正则替换
str.replace(r'(".*),(.*")', r'\1 \2', regex=True)
# 结合re工具包
import re
regex_pat = re.compile(r'FUZ', flags=re.IGNORECASE)
pd.Series(['foo', 'fuz', np.nan]).str.replace(regex_pat, 'bar', regex=True)
# 逆序
pat = r"(?P<one>\w+) (?P<two>\w+) (?P<three>\w+)"
repl = lambda m: m.group('two').swapcase()
ser = pd.Series(['One Two Three', 'Foo Bar Baz'])
ser.str.replace(pat, repl, regex=True)

# [2022-10-15] str里使用正则表达式
df_out['关键路径'].str.contains('进入IM=>初始faq=>输入\([^=>]*?\)=>接通人工', regex=True)
# 抽取
s.str.extract(r'(?P<letter>[ab])(?P<digit>\d)')
s.str.extract(r'[ab](\d)') # 返回字符串
s.str.extract(r'[ab](\d)', expand=False) # 返回 series格式(False)、dataframe格式(True)

赋值

多列赋值

如果列已存在,直接赋值

data1[['月份','企业']]=int(month),parmentname

否则,报错

如何一次对多列赋值?

  • 方法一:使用 apply 的参数 result_type 来处理
  • 方法二:使用 zip 打包返回结果来处理
import pandas as pd

df_tmp = pd.DataFrame([
    {"a":"data1", "cnt":100},{"a":"data2", "cnt":200},
])

def formatrow(row):
  """
     传入 pandas 一行
  """
  a = row["a"] + str(row["cnt"])
  b = str(row["cnt"]) + row["a"]
  return a, b 

# 方法一 apply 【2024-5-9】实测通过
df_tmp[["fomat1", "format2"]] = df_tmp.apply(formatrow, axis=1, result_type="expand")
# 方法二 zip
df_tmp["fomat1-1"], df_tmp["format2-2"] = zip(*df_tmp.apply(formatrow, axis=1))

预处理

重复值

duplicated和drop_duplicates处理重复值

  • duplicated 检测重复值
  • drop_duplicates 删除重复值
df.duplicated(subset=['Column1', 'Column2'])
df.drop_duplicates(subset=['Column1', 'Column2'], keep='first')

# 查找并删除重复行
df.duplicated(subset=['Name'])
df.drop_duplicates(subset=['Name'], keep='first')

缺失值

清理缺失值

# 数据清理
df.columns = ['a','b','c']# 重命名列名
pd.isnull()# 检查DataFrame对象中的空值,并返回一个Boolean数组
pd.notnull()# 检查DataFrame对象中的非空值,并返回一个Boolean数组
df.drop([16,17]) # 删除16,17行,得到新的dataframe, df仍然保留
df.drop(inex=['a', 'b']) # 或者这样
df.drop([16,17], inplace=True) # 删除16,17行, 原地操作
df.dropna()# 删除所有包含空值的行
df.dropna(axis=1)# 删除所有包含空值的列
df.drop(['a', 'b'], axis=1) # 删除列a,b
df.drop(columns=['a', 'b']) # 或者这样
df.dropna(axis=1,thresh=n)# 删除所有小于n个非空值的行
df.drop_duplicates() # 删除重复
df.drop_duplicates(keep='first') # 重复数据只保留第一个
df.fillna(x)# 用x替换DataFrame对象中所有的空值
s.astype(float)# 将Series中的数据类型更改为float类型

重命名

s.replace(1,'one')# 用'one'代替所有等于1的值
s.replace([1,3],['one','three'])# 用'one'代替1,用'three'代替3
#Series对象值替换
s = df.iloc[2]#获取行索引为2数据

#原文链接:https://blog.csdn.net/kancy110/article/details/72719340
df.rename(columns=lambda x: x + 1)# 批量更改列名
df.rename(columns={'old_name': 'new_name'})# 选择性更改列名
df.rename(columns={'ID': 'EmployeeID'}, inplace=True) # 原地生效
df.set_index('column_one')# 更改索引列
df.rename(index=lambda x: x + 1)# 批量重命名索引

日期

df['DateTimeColumn'] = pd.to_datetime(df['DateTimeColumn'])

分组

# 数据处理:Filter、Sort和GroupBy
df[df[col] > 0.5]# 选择col列的值大于0.5的行
df.sort_values(col1)# 按照列col1排序数据,默认升序排列
df.sort_values(col2, ascending=False)# 按照列col1降序排列数据
df.sort_values([col1,col2], ascending=[True,False])# 先按列col1升序排列,后按col2降序排列数据
df.groupby(col)# 返回一个按列col进行分组的Groupby对象
df.groupby([col1,col2])# 返回一个按多列进行分组的Groupby对象
df.groupby(col1)[col2]# 返回按列col1进行分组后,列col2的均值
df.groupby(col1).agg(np.mean)# 返回按列col1分组的所有列的均值

df.pivot_table(index=col1, values=[col2,col3], aggfunc=max)# 创建一个按列col1进行分组,并计算col2和col3的最大值的数据透视表
pd.pivot_table(df, values='ValueColumn', index='IndexColumn', columns='ColumnToPivot', aggfunc='mean')

data.apply(np.mean)# 对DataFrame中的每一列应用函数np.mean
data.apply(np.max,axis=1)# 对DataFrame中的每一行应用函数np.max
# -------------
tmp = lambda x:x.sort_values(ascending = False).iloc[0]
tmp.__name__ = 'func'
df['real_cost'].applymap(tmp) # 元素级别变换
df[['customer_type','real_cost']].agg({'customer_type':['sum','mean'],'real_cost':['sum',tmp]}) # 多个属性的统计信息(统计值不同,自定义聚合函数)
df.groupby(['real_cost']).transform(('sum')) # transform元素级别的变换
# -------------
# 数据过滤
import pandas as pd 
data_file = 'name.csv'
df = pd.read_csv(data_file)
# 编码转换 gbk -> utf8
df['path'] = df['path_name'].apply(lambda x:x.decode('gbk').encode('utf8'))
#print df['path_name'][3].decode('gbk').encode('utf8')
df

# 数据合并
# 【2024-3-15】新版pandas不再支持append, 改用concat方法
df1.append(df2)# 将df2中的行添加到df1的尾部
df1.concat(df2)# 【2024-3-15】不能这么用 'DataFrame' object has no attribute 'concat'
df.concat([df1, df2],axis=1)# 正确
df1.join(df2,on=col1,how='inner')# 对df1的列和df2的列执行SQL形式的join

字符串

str方法

  • 含有特定字符串的行:contains()
  • 字符串分割: split, 官方文档
import pandas as pd

data = pd.DataFrame({'班级':['1班','1班','1班','1班','1班','2班','2班','2班','2班','2班'],
'姓名':['韩愈','柳宗元','欧阳修','苏洵','苏轼','苏辙','曾巩','王安石','张三','小伍哥'],
'成绩':[80,70,70,40,10,60,60,50,50,40]})
#姓名长度不一样的,加个符号调整下
data['姓名'] = data['姓名'].str.rjust(3,'〇') # 填充o
# 看1班成绩名次
data_1 = data[data['班级']=='1班']
data_1['成绩_first'] = data_1['成绩'].rank(method='first',ascending=False)

# 字符串处理
## 1、空格处理
df[col_name] = df[col_name].str.lstrip() 
# 去除特定字符strip(包括lstrip和rstrip)
df1['expression']=df1['expression'].str.lstrip('mid:')
## 前缀/后缀判断,startswith 与 endswith
df1=df[df['expression'].str.startswith('m')]
## 3、字符串分割:expand=True不加的话,df1中将只有一列,其实就是一个series。
df[col_name].str.split('分割符')
s.str.split(n=2) # 限定分割次数
s.str.rsplit(n=2) # 从右边开始分割, 类似的, lsplit
s.str.split(pat="/") # 按字符分割
s.str.split(r"\.jpg", regex=True, expand=True) # 正则
s.str.split(re.compile(r"\.jpg"), expand=True) # 先编译
df1=df['columns_name'].str.split(':',expand=True) # 生成新的df
df[['exp1','exp2']]=df['expression'].str.split(':',expand=True) # 直接追加到原来的df
df['exp2']=df['exp2'].astype(int) # 类型转换
## 正则表达式
s=df['expression'].str.findall('[a-z]+')
## 4、字符串拼接
df[col_name].str.cat()
## 2、*%d等垃圾符处理
df[col_name].replace(' &#.*', '', regex=True, inplace=True)
#单值替换
s.replace('?', np.nan)#用np.nan替换?
s.replace({'?':'NA'})#用NA替换?
#多值替换
s.replace(['?',r'$'],[np.nan,'NA'])#列表值替换
s.replace({'?':np.nan,'$':'NA'})#字典映射
#同缺失值填充方法类似
s.replace(['?','$'],method='pad')#向前填充
s.replace(['?','$'],method='ffill')#向前填充
s.replace(['?','$'],method='bfill')#向后填充
#limit参数控制填充次数
s.replace(['?','$'],method='bfill',limit=1)
#DataFrame对象值替换
#单值替换
df.replace('?',np.nan)#用np.nan替换?
df.replace({'?':'NA'})#用NA替换?
#按列指定单值替换
df.replace({'EMPNO':'?'},np.nan)#用np.nan替换EMPNO列中?
df.replace({'EMPNO':'?','ENAME':'.'},np.nan)#用np.nan替换EMPNO列中?和ENAME中.
#多值替换
df.replace(['?','.','$'],[np.nan,'NA','None'])##用np.nan替换?用NA替换. 用None替换$
df.replace({'?':'NA','$':None})#用NA替换? 用None替换$
df.replace({'?','$'},{'NA',None})#用NA替换? 用None替换$
#正则替换
df.replace(r'\?|\.|\$',np.nan,regex=True)#用np.nan替换?或.或$原字符
df.replace([r'\?',r'\$'],np.nan,regex=True)#用np.nan替换?和$
df.replace([r'\?',r'\$'],[np.nan,'NA'],regex=True)#用np.nan替换?用NA替换$符号
df.replace(regex={r'\?':None})
#value参数显示传递
df.replace(regex=[r'\?|\.|\$'],value=np.nan)#用np.nan替换?或.或$原字符
# [2019-08-29]
d = pd.DataFrame({'BoolCol': [1, 2, 3, 3, 4],'attr': ['a.,b', 'hello', 'world', ',', '4,5']})
d.replace({'attr':','}, '->') # 完全匹配
d.replace(r',', '->',regex=True) # 正则替换
d.replace(r',', '->',regex=True, inplace=True) # 修改原值
d[(df_2.ocr.isnull) & (d.ocr.str.contains(','))] # 检测是否包含,

哑编码

将分类变量转换为独热编码

pd.get_dummies(df, columns=['CategoricalColumn'])

合并

  • pandas-数据的合并与拼接
  • Pandas包的mergejoinconcat方法可以完成数据的合并和拼接
    • merge 基于两个dataframe的共同列进行合并
    • join 基于两个dataframe的索引进行合并
    • concat 对series或dataframe进行行拼接列拼接

分析

  • concat和append可以实现的是表间”拼接“,而merge和join则实现的是表间”合并“。区别在于是否基于”键“来进行合并。
    • 如果只是简单地”堆砌“,则用concat和append比较合适
    • 而如果遇到关联表,需要根据”键“来合并,则用merge和join。
  • concat 和 merge是pandas的属性,所以调用时写成pd.concat()或者pd.merge();而append和join是对DataFrame的方法,所以调用的时候应该写成df.append()或者df.join()。
  • append 只能实现行拼接,concat的功能更加强大。理论上append可以完成的操作concat都可以完成,只需要更改相应的参数即可。
  • 类似于append之于concat,join可以完成的操作merge也都可以完成,因此merge更加强大。
  • append和join存在的意义在于简洁和易用。

concat后面对于df的参数形式是objs,这个objs可以是一个列表或者集合,里面可以有很多个df;而merge后面跟的参数形式是left和right,只有两个df。因此concat其实可以快速实现多表的拼接,而merge只能实现两表的合并。

append 追加

添加行、列

  • 添加行有df.loc[]以及df.append()两种方法
  • 添加列有df[]df.insert()两种方法
df = pd.DataFrame(columns=['name','number'])
# .loc 方法
df.loc[0]=['cat', 3]  # 插入df索引,默认整数型
# 或
df.loc['a'] = ['123',30]
# append
df = pd.DataFrame([[1, 2], [3, 4]], columns=list('AB'))
df2 = pd.DataFrame([[5, 6], [7, 8]], columns=list('AB'))
# 合并, ignore_index设置为 True可以重新排列索引
df.append(df2, ignore_index=True)
# 2. 采用append方法添加多行
df = pd.DataFrame(columns=['A'])
for i in range(5):
    df = df.append({'A': i}, ignore_index=True)
# 插入一列
df.insert(1, 'tail', 1, allow_duplicates=False)

merge

  • (1) pandas 的 merge方法是基于共同列,将两个dataframe连接起来。

merge方法的主要参数:

  • left/right:左/右位置的dataframe。
  • how:数据合并的方式。
    • left:基于左dataframe列的数据合并;
    • right:基于右dataframe列的数据合并;
    • outer:基于列的数据外合并(取并集);
    • inner:基于列的数据内合并(取交集);默认为’inner’。
  • on:用来合并的列名,这个参数需要保证两个dataframe有相同的列名。
  • left_on/right_on:左/右dataframe合并的列名,也可为索引,数组和列表。
  • left_index/right_index:是否以index作为数据合并的列名,True表示是。
  • sort:根据dataframe合并的keys排序,默认是。
  • suffixes:若有相同列且该列没有作为合并的列,可通过suffixes设置该列的后缀名,一般为元组和列表类型。

示例:

  • 内连接:
  • 外链接
  • index和column的内连接方法
# 单列的内连接
# 定义df1
import pandas as pd
import numpy as np

df1 = pd.DataFrame({'alpha':['A','B','B','C','D','E'],'feature1':[1,1,2,3,3,1],
            'feature2':['low','medium','medium','high','low','high']})
# 定义df2
df2 = pd.DataFrame({'alpha':['A','A','B','F'],'pazham':['apple','orange','pine','pear'],
            'kilo':['high','low','high','medium'],'price':np.array([5,6,5,7])})
# print(df1)
# print(df2)

pd.merge(left,right) # (1)
left.merge(right) # (2)

# 基于共同列alpha的内连接
df3 = pd.merge(df1,df2,how='inner',on='alpha')
# 基于共同列alpha的内连接,若两个dataframe间除了on设置的连接列外并无相同列,则该列的值置为NaN。
df4 = pd.merge(df1,df2,how='outer',on='alpha')
# 基于共同列alpha的左连接
df5 = pd.merge(df1,df2,how='left',on='alpha')
# 基于共同列alpha的右连接
df6 = pd.merge(df1,df2,how='right',on='alpha')
# 基于共同列alpha和beta的内连接
df7 = pd.merge(df1,df2,on=['alpha','beta'],how='inner')
# 基于共同列alpha和beta的右连接
df8 = pd.merge(df1,df2,on=['alpha','beta'],how='right')
# 基于df1的beta列和df2的index连接
df9 = pd.merge(df1,df2,how='inner',left_on='beta',right_index=True)
# 基于df1的alpha列和df2的index内连接(修改相同列的后缀名)
df9 = pd.merge(df1,df2,how='inner',left_on='beta',right_index=True,suffixes=('_df1','_df2'))
df3

【2024-1-23】多表merge

# 数据定义
import pandas as pd 
import functools

df1 = pd.DataFrame({
    "Name":['zhao','qian','sun','li','zhou','wu'],
    "Age":[21,23,20,20,24,23],
    "Grade-1":[100,99,90,98,99,95],
    })

df2 = pd.DataFrame({
    "Name":['zhao','qian','sun','li','zhou','wu'],
    "Age":[21,23,20,20,24,23],
    "Grade-2":[65,66,65,68,70,72],
    })

df3 = pd.DataFrame({
    "Name":['zhao','qian','sun','li','zhou','wu'],
    "Age":[21,23,20,20,24,23],
    "Grade-3":[77,88,99,100,88,55],
    })

df4 = pd.DataFrame({
    "Name":['zhao','qian','sun','li','zhou','wu'],
    "Age":[21,23,20,20,24,23],
    "Grade-4":[60,60,61,61,61,60],
    })

# 多表 merge
# ① 迭代merge —— 低效
# pd.merge(([df1, df2], on='key', how='left')
df1.merge(df2, on='key', how='left').merge(df3, on='key', how='left')

# ② reduce函数,将pd.merge函数递归应用于四个表单 —— 4个df遍历了一遍
df = functools.reduce(
    lambda left, right: pd.merge(left, right, how='left',on=['Name','Age']),
    [df1,df2,df3,df4]
)
print(df)
# 改进
all_files = []
for fn in glob.glob("../data/temp*.csv"):
    all_files.append(pandas.read_csv(fn))

df = functools.reduce(
    lambda left, right: pd.merge(left, right, how='left',on=['Name','Age']),
    all_files
) 

# ③ concat 方法
# 

join

  • (2) join方法
    • join方法是基于index连接dataframe,merge方法是基于column连接,连接方法有内连接,外连接,左连接和右连接,与merge一致。
    • join和merge的连接方法类似,这里就不展开join方法了,建议用merge方法。
  • 示例
caller = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3', 'K4', 'K5'], 'A': ['A0', 'A1', 'A2', 'A3', 'A4', 'A5']})
other = pd.DataFrame({'key': ['K0', 'K1', 'K2'],'B': ['B0', 'B1', 'B2']})
print(caller)
print(other)# lsuffix和rsuffix设置连接的后缀名
# 基于index连接
caller.join(other,lsuffix='_caller', rsuffix='_other',how='inner')
# 基于key列进行连接
caller.set_index('key').join(other.set_index('key'),how='inner')

concat

  • (3) concat方法
    • concat方法是拼接函数,有拼接和拼接,默认是行拼接,拼接方法默认是外拼接(并集),拼接的对象是pandas数据类型。
  • 示例
df1 = pd.Series([1.1,2.2,3.3],index=['i1','i2','i3'])
df2 = pd.Series([4.4,5.5,6.6],index=['i2','i3','i4'])
print(df1)
print(df2)

# 行拼接
pd.concat([df1,df2])
# 对行拼接分组,行拼接若有相同的索引,为了区分索引,我们在最外层定义了索引的分组情况。
pd.concat([df1,df2],keys=['fea1','fea2'])
# 列拼接,默认是并集
pd.concat([df1,df2],axis=1)
# 列拼接的内连接(交)
pd.concat([df1,df2],axis=1,join='inner')
# 列拼接的内连接(交)
pd.concat([df1,df2],axis=1,join='inner',keys=['fea1','fea2'])
# 指定索引[i1,i2,i3]的列拼接
pd.concat([df1,df2],axis=1,join_axes=[['i1','i2','i3']])

df1 = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3', 'K4', 'K5'], 'A': ['A0', 'A1', 'A2', 'A3', 'A4', 'A5']})
df2 = pd.DataFrame({'key': ['K0', 'K1', 'K2'],'B': ['B0', 'B1', 'B2']})
print(df1)
print(df2)

# 行拼接
pd.concat([df1,df2])
# 列拼接
pd.concat([df1,df2],axis=1)
# 判断是否有重复的列名,若有则报错
pd.concat([df1,df2],axis=1,verify_integrity = True)

Reference

《Python for data analysis》
pandas: powerful Python data analysis toolkit — pandas 0.20.1 documentation

遍历

【2020-12-25】遍历dataframe

  • iterrows:最慢
  • itertuples
  • zip:最快
# 总结
DataFrame.iteritems() # 迭代器结束(列名,系列)对。 
DataFrame.iterrows() # 将DataFrame行重复为(索引,系列)对。 
DataFrame.itertuples[indexname] # 将DataFrame行迭代为namedtuples,索引值作为元组的第一个元素。

df = pd.DataFrame({'a': range(0, 10000), 'b': range(10000, 20000)})
import time
list1 = []
start = time.time()
for i,r in df.iterrows():
    list1.append((r['a'], r['b']))
print("iterrows耗时  :",time.time()-start)

list1 = []
start = time.time()
for ir in df.itertuples():
    list1.append((ir[1], ir[2]))    
print("itertuples耗时:",time.time()-start)

list1 = []
start = time.time()
for r in zip(df['a'], df['b']):
    list1.append((r[0], r[1]))
print("zip耗时       :",time.time()-start)
  • 结果:
    • iterrows耗时 : 0.7355637550354004
    • itertuples耗时: 0.008462905883789062
    • zip耗时 : 0.003980875015258789

多表合并

  • 【2020-12-31】一次合并多张表
import pandas as pd
from functools import reduce
 
data_dir = '/home/work/data/北链讲盘培训价值分析'
#data_file = '/home/work/data/北链讲盘过关用户业绩样例.csv'
bu = ['京北', '京东南']
group = ['未参加', '低分', '高分']
df_dict = {}
for b in bu:
    df_dict[b] = {}
    for g in group:
        data_file = '{}/{}-{}.csv'.format(data_dir, b, g)
        print('开始读数据:{}'.format(data_file))
        df_dict[b][g] = pd.read_csv(data_file, encoding='gbk')
        df_dict[b][g] = df_dict[b][g].rename(columns={'stat_date':'日期', 'avg(assign_amt)':'培训{}的经纪人日均业绩'.format(g)})
        df_final.loc[:,'alpha'].apply(lambda x:'{}-hh'.format(x))
        df_dict[b][g]['日期'] = df_dict[b][g]['日期'].apply(lambda x: '{}-{}-{}'.format(str(x)[:4],str(x)[4:6],str(x)[6:8]))
        # .dt.dayofweek
        df_dict[b][g]['星期几'] = pd.to_datetime(df_dict[b][g]['日期']).dt.dayofweek
    #df_dict[b]['合并'] = df.merge(df_dict[b]['未参加'], df_dict[b]['低分'], df_dict[b]['高分'], how='inner', on='日期')
    merge_list = [df_dict[b]['未参加'], df_dict[b]['低分'], df_dict[b]['高分']]
    df_dict[b]['合并'] = reduce(lambda left,right: pd.merge(left,right,on='日期',how='outer'), merge_list).fillna(0)
    #df_dict[b]['合并'].to_excel('{}/{}_合并后.xlsx'.format(data_dir, b), encoding='utf8') # 指定编码utf8
    df_dict[b]['合并'].to_excel('{}/{}_合并后.xlsx'.format(data_dir, b), encoding='utf8')
 
#df = pd.read_csv(data_file, encoding='gbk')
#!head $data_file
#df3 = pd.merge(df1,df2,how='inner',on='alpha')
df_dict['京北']['合并']

crosstab进行交叉表

explode展开列表

pd.crosstab(df['Column1'], df['Column2'])
# explode展开列表
df.explode('ListColumn')
# pipe进行链式操作
df.pipe(func1).pipe(func2, arg1='value').pipe(func3)

日期转换


#提取年月日时分秒:方法1
df = pd.read_csv(r"spider.csv",header=None,names=['datetime','url','name','x','y'],encoding='utf-8')
df['datetime'] = pd.to_datetime(df['datetime'],errors='coerce')   #先转化为datetime类型,默认format='%Y-%m-%d %H:%M:%S'
df['date'] = df['datetime'].dt.date   #转化提取年-月-日
df['year'] =df['datetime'].dt.year.fillna(0).astype("int")   #转化提取年 ,
#如果有NaN元素则默认转化float64型,要转换数据类型则需要先填充空值,在做数据类型转换
df['month'] = df['datetime'].dt.month.fillna(0).astype("int")  #转化提取月
df['%Y_%m'] = df['year'].map(str) + '-' + df['month'].map(str) #转化获取年-月
df['day'] = df['datetime'].dt.day.fillna(0).astype("int")      #转化提取天
df['hour'] = df['datetime'].dt.hour.fillna(0).astype("int")    #转化提取小时
df['minute'] = df['datetime'].dt.minute.fillna(0).astype("int") #转化提取分钟
df['second'] = df['datetime'].dt.second.fillna(0).astype("int") #转化提取秒
df['dayofyear'] = df['datetime'].dt.dayofyear.fillna(0).astype("int") #一年中的第n天
df['weekofyear'] = df['datetime'].dt.weekofyear.fillna(0).astype("int") #一年中的第n周
df['weekday'] = df['datetime'].dt.weekday.fillna(0).astype("int") #周几,一周里的第几天,Monday=0, Sunday=6
df['quarter'] = df['datetime'].dt.quarter.fillna(0).astype("int")  #季度
display(df.head())

百分比计算

按 行 计算对应列的数值、百分比、累积占比

非重复

按取值去重,再用 value_counts 方法

import pandas as pd

df = pd.DataFrame({'col1': [1, 2, 3, 4, 5, 6]})

value_counts = df['col1'].value_counts(normalize=True) * 100
cumsum = value_counts.cumsum()
print(cumsum)

含重复行

直接计算,不去重

df['percent'] = (df['c'] / df['c'].sum()) * 100

详情

# 合并训练集、测试集轮数分布
df_stat_turn_merge = pd.merge(df_train['turns'].value_counts(), df_test['turns'].value_counts(), on='turns')
df_stat_turn_merge.rename(columns={"turns":"会话轮数", "count_x":"训练集轮数","count_y":"测试集轮数"},inplace=True)
df_stat_turn_merge = df_stat_turn_merge.sort_values(by='turns')
df_stat_turn_merge['占比-训练'] = df_stat_turn_merge['训练集轮数']/df_stat_turn_merge['训练集轮数'].sum() * 100
df_stat_turn_merge['累积占比-训练'] = df_stat_turn_merge['占比-训练'].cumsum()
df_stat_turn_merge['占比-测试'] = df_stat_turn_merge['测试集轮数']/df_stat_turn_merge['测试集轮数'].sum() * 100
df_stat_turn_merge['累积占比-测试'] = df_stat_turn_merge['占比-测试'].cumsum()
df_stat_turn_merge.to_csv('../data/session_turn.csv')
df_stat_turn_merge

调整列顺序

两种方法

  • 直接重组
  • 局部调整:先把需要调整的列的数据拿出来,之后,再将这个列删掉,最后,再用插入的方式把这个列调整到对应的位置上。
# 方法一
order = ['date', 'time', 'open', 'high', 'low', 'close', 'volumefrom', 'volumeto']
df = df[order]
# 方法二
df_id = df.id
df = df.drop('id',axis=1)
df.insert(0,'id',df_id)

sort 用法

数据排序sort_index()和sort_values()

sort_values()

  • 作用:既可以根据列数据,也可根据行数据排序。
  • 注意:必须指定by参数,即必须指定哪几行或哪几列;无法根据index名和columns名排序(由.sort_index()执行)

DataFrame.sort_values(by, axis=0, ascending=True, inplace=False, kind=’quicksort’, na_position=’last’)

  • axis:{0 or ‘index’, 1 or ‘columns’}, default 0,默认按照列排序,即纵向排序;如果为1,则是横向排序。
  • by:str or list of str;如果axis=0,那么by=”列名”;如果axis=1,那么by=”行名”。
  • ascending:布尔型,True则升序,如果by=[‘列名1’,’列名2’],则该参数可以是[True, False],即第一字段升序,第二个降序。
  • inplace:布尔型,是否用排序后的数据框替换现有的数据框。
  • kind:排序方法,{‘quicksort’, ‘mergesort’, ‘heapsort’}, default ‘quicksort’。似乎不用太关心。
  • na_position:{‘first’, ‘last’}, default ‘last’,默认缺失值排在最后面。

sort_index()

  • 作用:默认根据行标签对所有行排序,或根据列标签对所有列排序,或根据指定某列或某几列对行排序。
  • 注意:df. sort_index()可以完成和df. sort_values()完全相同的功能,但python更推荐用只用df. sort_index()对“根据行标签”和“根据列标签”排序,其他排序方式用df.sort_values()。

sort_index(axis=0, level=None, ascending=True, inplace=False, kind=’quicksort’, na_position=’last’, sort_remaining=True, by=None)

  • axis:0按照行名排序;1按照列名排序
  • level:默认None,否则按照给定的level顺序排列—貌似并不是,文档
  • ascending:默认True升序排列;False降序排列
  • inplace:默认False,否则排序之后的数据直接替换原来的数据框
  • kind:排序方法,{‘quicksort’, ‘mergesort’, ‘heapsort’}, default ‘quicksort’。似乎不用太关心。
  • na_position:缺失值默认排在最后{“first”,”last”}
  • by:按照某一列或几列数据进行排序,但是by参数貌似不建议使用
import pandas as pd  

df = pd.DataFrame({'b':[1,2,2,3],'a':[4,3,2,1],'c':[1,3,8,2]},index=[2,0,1,3]) 

df.sort_values(by='b') #等同于df.sort_values(by='b',axis=0)
df.sort_values(by=['b'], na_position='first') # 空值放前面
df.sort_values(by=['b','c'], ascending=False) # 多列,降序
df.sort_values(by=['b'], ascending=False, inplace=True, na_position='first') # 替换原数据
df.sort_values(by=0, ascending=False, axis=1) # 按第一行值降序排列
df.sort_values(by=[3,0], axis=1,ascending=[True,False]) # 排序故障
df.sort_values(by=['b','c'],ascending=[True,False]) # [2023-8-23] 实测有效,去掉 exis=1,下标改字符串
df.sort_values(by=3,axis=1) #按第三行升序,必须指定axis=1

df.sort_index() #默认按“行标签”升序排序,或df.sort_index(axis=0, ascending=True)
df.sort_index(axis=1) #按“列标签”升序排序
#先按b列“降序”排列,因为b列中有相同值,相同值再按a列的“升序”排列
df.sort_index(by = ['b','a'],ascending = [False,True]) 

Rank用法

【2022-9-13】python中分组排序–groupby(),rank()

rank()函数参数设置

  1. ascending :1 表示升序,0表示降序
  2. method : {‘average’, ‘min’, ‘max’, ‘first’, ‘dense’}, default ‘average’ 主要用来当排序时存在相同值参数设置;
    • 默认为average平均值:年龄为32的数值,排序应该为8,9取平均值则为8.5
    • min:排序中最小值,年龄排序中取值为8
    • max:排序中最大值,年龄排序中取值9
    • first:同样数值按照值出现的前后进行排序 5号性别为男的年龄排序为8,7号性别为女的排序为9
    • dense: like ‘min’, but rank always increases by 1 between groups 排序时当值相同时,相同的值为同一排名类似min值排序,后续值排名在此排名基础上加一
  3. na_option : {‘keep’, ‘top’, ‘bottom’}, default ‘keep’ 当排序数据中存在空值时,默认值设置为keep
    • How to rank NaN values:
    • keep: assign NaN rank to NaN values 默认空值不参与排序
    • top: assign smallest rank to NaN values if ascending 默认为升序时从空值为最小值排序
import pandas as pd

df = pd.DataFrame([['男',13,1], ['男',15,0], ['男',12,0],['男',14,1],['女',13,1],['女',14,0],['女',15,1],['男',13,0]],columns=['gender', 'age', 'is_good'])
# 累积和
df['sum_age'] = df['age'].cumsum()
df['sum_age_new'] = df.groupby(['gender','is_good'])['age'].cumsum() # 多维度分组,再累加求和
# 全局排序
df['rank_age'] = df['age'].rank(ascending=0, method='min')
df['rank'] = df['age'].rank()
df['rank_mean'] = df['age'].rank(method='average')
df['rank_min'] = df['age'].rank(method='min')
df['rank_max'] = df['age'].rank(method='max')
df['rank_first'] = df['age'].rank(method='first')
print(df)
# 局部排序:分组排序(按gender内容分组)
df['rank_group'] = df['age'].groupby(df['gender']).rank(ascending=0, method='min')
df['rank_g'] = df.groupby(['gender'])['age'].rank()
# 查看数据:依次按gender、rank_group排序
df.sort_values(by=['gender','rank_group'],ascending=(1,1))
# 默认值处理
df['rank'] = df['age'].rank(method='first')
df['rank_k'] = df['age'].rank(method='first',na_option='keep')
df['rank_t'] = df['age'].rank(method='first',na_option='top')
df['rank_b'] = df['age'].rank(method='first',na_option='bottom')
print(df)

groupby 聚合统计

词频统计,类似collections里的Counter

  • 聚合函数:sum,cumsum,prod,mean,max,min,idxmax,rank

【2022-8-22】25个例子学会Pandas Groupby 操作

import pandas as pd
sales = pd.read_csv("sales_data.csv")
sales.head()

df = pd.DataFrame([['Toyota', 40], ['Ford', 20], ['Ford', 30],['Toyota', 30]], columns=['brand', 'price'])
# 单列聚合
sales.groupby("store")["stock_qty"].mean()
# 输出storeDaisy 1811.861702Rose 1677.680000Violet 14622.406061Name: stock_qty, dtype: float64
sales.groupby("store",as_index=False)["stock_qty"].mean() # 不用分组值作为index
# 输出结果保留key值
out=pd_month_short.groupby('corp_name')['corp_name'].agg('count') # value作为index参数, 直接转list时,分组值丢失
list(out.to_dict().items()) # 结果转成dict,再list
# 多列聚合
sales.groupby("store")[["stock_qty","price"]].mean()
# 多列多个聚合 agg函数
sales.groupby("store")["stock_qty"].agg(["mean", "max"])
# 对聚合结果命名
sales.groupby("store").agg( avg_stock_qty = ("stock_qty", "mean"),max_stock_qty = ("stock_qty", "max"))
# 多个聚合和多个函数
sales.groupby("store")[["stock_qty","price"]].agg(["mean", "max"])
# 对不同列的聚合进行命名
sales.groupby("store").agg(avg_stock_qty = ("stock_qty", "mean"),avg_price = ("price", "mean"))
# as_index参数:groupby操作的输出是DataFrame,可以使用as_index参数使它们成为DataFrame中的一列
sales.groupby("store", as_index=False).agg(avg_stock_qty = ("stock_qty", "mean"),avg_price = ("price", "mean"))
# 用于分组的多列
sales.groupby(["store","product_group"], as_index=False).agg(avg_sales = ("last_week_sales", "mean")).head()
# 排序输出
sales.groupby(["store","product_group"], as_index=False).agg( avg_sales = ("last_week_sales", "mean")).sort_values(by="avg_sales", ascending=False).head()
# 最大的Top N:max扩展
sales.groupby("store")["last_week_sales"].nlargest(2)
# store Daisy 413 1883231 947Rose 948 883263 623Violet 991 3222339 2690Name: last_week_sales, dtype: int64
# 最小的Top N
sales.groupby("store")["last_week_sales"].nsmallest(2)
# 第n个值
sales_sorted = sales.sort_values(by=["store","last_month_sales"], ascending=False, ignore_index=True)
sales_sorted.groupby("store").nth(4)
# 第n个值,倒排序
sales_sorted.groupby("store").nth(-2)
# 唯一值:unique函数可用于查找每组中唯一的值
sales.groupby("store", as_index=False).agg(unique_values = ("product_code","unique"))
# 唯一值的数量: 用nunique函数找到每组中唯一值的数量
sales.groupby("store", as_index=False).agg(number_of_unique_values = ("product_code","nunique"))
# Lambda表达式
sales.groupby("store").agg(total_sales_in_thousands = ("last_month_sales", lambda x: round(x.sum() / 1000, 1)))
# apply函数:使用apply函数将Lambda表达式应用到每个组
sales.groupby("store").apply(lambda x: (x.last_week_sales - x.last_month_sales / 4).mean())
# storeDaisy 5.094149Rose 5.326250Violet 8.965152dtype: float64
# dropna: groupby函数默认忽略缺失值,dropna参数来改变这个行为
sales.groupby("store", dropna=False)["price"].mean() 
# storeDaisy 69.327426Rose 60.513700Violet 67.808727NaN 96.000000Name: price, dtype: float64
# 求组的个数
sales.groupby(["store", "product_group"]).ngroups # 18
# 获得一个特定分组:get_group函数可获取特定组并且返回DataFrame
aisy_pg1 = sales.groupby(["store", "product_group"]).get_group(("Daisy","PG1"))daisy_pg1.head()
# rank函数
sales["rank"] = sales.groupby("store"["price"].rank(ascending=False, method="dense")sales.head()
# 累计操作
import numpy as np
df = pd.DataFrame({"date": pd.date_range(start="2022-08-01", periods=8, freq="D"),"category": list("AAAABBBB"),"value": np.random.randint(10, 30, size=8)})
df["cum_sum"] = df.groupby("category")["value"].cumsum()
# expanding函数:提供展开转换。但是对于展开以后的操作还是需要一个累计函数来堆区操作。例如它与cumsum 函数一起使用,结果将与与sum函数相同。
df["cum_sum_2"] = df.groupby("category")["value"].expanding().sum().values
# 累积平均:利用展开函数和均值函数计算累积平均
df["cum_mean"] = df.groupby("category")["value"].expanding().mean().values
# 展开后的最大值:用expand和max函数记录组当前最大值。
df["current_highest"] = df.groupby("category")["value"].expanding().max().values

单列聚合

# 对train表里的intent列计算频次,并给出百分比(不用单独计算)
train.intent.value_counts() # 频次统计
train.intent.value_counts(ascending=True) # 升序
train.intent.value_counts(normalize=True) # 计算比例
# 聚合统计
df.groupby('total_bedrooms')['population'].agg(['count', 'sum', 'mean'])[:10]
df.groupby(['real_cost']).size().loc[10] # 查询某个分组下的聚合值
df.groupby(['real_cost']).get_group((10)) # 查询某个分组下所有记录
df.groupby(['real_cost'])['queuecount'].idxmin() # 各个分组下某个取值(queuecount)最小/大的记录数下标,idxmin,idxmax
df.groupby(['real_cost']).size().apply(lambda x:2*x).head() # apply,对统计值的二次处理
df.groupby(['real_cost']).size().reset_index()# reset_index重置index

# key内部求和,按照key分组,再对value求和
gp = data.groupby(["key"])["value"].sum().reset_index() # reset_index重置index
gp = data.groupby(["key"])["value"].cumsum().reset_index() # 累计和
gp = data.groupby(["key"])["value"].prod().reset_index() # 连乘,类似的min、max、mean、idxmin、idxmax
gp = data.groupby(["key"])["value"].rank().reset_index() # 编号
gp = data.groupby(["key"])["value"].agg(['count','sum']).reset_index() # agg多组值

gp.rename(columns={"value":"sum_of_value"},inplace=True) # rename改列名
grouped1 = df['data1'].astype(float).groupby(df['key1']).mean()     #先将data1转换成浮点型,然后分组求均值

# [2021-10-22] 转成字典格式: key → value
prov_data = dict(stat['location'].groupby('省')['所有频次'].agg(['sum'])['sum'])
# pandas groupby常用功能
df.groupby(['real_cost']).size().reset_index()# reset_index重置index
df.groupby(['dt','msg']).size().reset_index().rename(columns={0:"count"})
df.rename(columns={0:"count"})
df.groupby(['real_cost']).size() # 分组,频次计算,size()计算数目
df.groupby(['real_cost']).size() # 分组,频次计算,count()不含Nan值
df.groupby(['real_cost']).size().loc[10] # 查询某个分组下的聚合值
df.groupby(['real_cost']).get_group((10)) # 查询某个分组下所有记录
df.groupby(['real_cost'])['queuecount'].idxmin() # 各个分组下某个取值(queuecount)最小/大的记录数下标,idxmin,idxmax
df.groupby(['real_cost']).size().apply(lambda x:2*x).head() # apply,对统计值的二次处理
# 用户当天第几次搜索某个brand_id
gp = data.groupby(["user_id","day","brand_id"])["hour"].rank().reset_index() 
gp.rename(columns={"hour":"rank"},inplace=True)

df = pd.DataFrame([[1, 2, 3],
                    [4, 5, 6],
                    [7, 8, 9],
                    [np.nan, np.nan, np.nan]],
                   columns=['A', 'B', 'C'])
df.agg(sum)
df.agg(['sum', 'min'] # 行向汇总
df.agg({'A' : ['sum', 'min'], 'B' : ['min', 'max']}) # 每列统计方法不同
df.agg(x=('A', max), y=('B', 'min'), z=('C', np.mean))  # 列上聚合不同的函数,然后重命名结果DataFrame的索引

#agg 调用的时候要指定字段,apply 默认传入的是整个dataframe,transform 是针对输入的元素级别转换
df[['customer_type','real_cost']].agg(['sum','mean','min','max','median']) # 多个属性的统计信息
tmp = lambda x:x.sort_values(ascending = False).iloc[0]
tmp.__name__ = 'func'
df[['customer_type','real_cost']].agg({'customer_type':['sum','mean'],'real_cost':['sum',tmp]}) # 多个属性的统计信息(统计值不同,自定义聚合函数)
df.groupby(['real_cost']).transform(('sum')) # transform元素级别的变换
df['real_cost'].applymap(tmp) # 元素级别变换

分箱

cut函数将数值列分成不同的箱子,用标签表示

df['AgeGroup'] = pd.cut(df['Age'], bins=[20, 30, 40, 50], labels=['20-30', '30-40', '40-50'])

# groupby和transform进行组内操作
df['MeanSalaryByAge'] = df.groupby('Age')['Salary'].transform('mean')

采样

pandas随机采样,用于数据集划分;

# 随机采样
df = pd.DataFrame([[1,'a'],[2,'b'],[3,'c'],[4,'d']], columns=['id','name'])
# DataFrame.sample(n=None, frac=None, replace=False, weights=None, random_state=None, axis=None)
#df.sample(frac=1).reset_index(drop=True)
df.sample(frac=1) # 随机打乱,100%乱序,frac是比例,axis行还是列向,replace是否放回,weights字符索引或概率数组
df.sample(n=3,random_state=1) # 抽3行,可重复数据
df.sample(frac=0.8, replace=True, random_state=1) # replace可重复

窗口计算

【2022-9-20】pandas之滑动窗口学习笔记(shift, diff, pct_change)

pandas中有3类窗口,分别是:

  • 滑动窗口 rolling
  • 扩张窗口 expanding
  • 指数加权窗口 ewm
df['Column'].rolling(window=size).mean()

协方差、相关系数

滑窗对象

  • 要使用滑窗函数,就必须先要对一个序列使用 .rolling 得到滑窗对象,其最重要的参数为窗口大小 window
  • 再用相应的聚合函数进行计算
  • 注意:窗口包含当前行所在的元素,例如在第四个位置进行均值运算时,应当计算(2+3+4)/3,而不是(1+2+3)/3:
s = pd.Series([1,2,3,4,5])
roller = s.rolling(window = 3)
roller # Rolling [window=3,center=False,axis=0]
roller.mean() # 滑窗均值函数,得到:Nan Nan 2 3 4
roller.sum() # 滑窗求和函数,得到:Nan Nan 6 9 12

s2 = pd.Series([1,2,6,16,30])
roller.cov(s2) # 滑动协方差
roller.corr(s2) # 滑动相关系数
a = np.arange(1,10).reshape(3,3)
data = DataFrame(a,index=["a","b","c"],columns=["one","two","three"])
data.cov() # 协方差矩阵
data.one.cov(data.two) # 第一列、第二列协方差
data.one.corr(data.two) # 第一列、第二列相关系数
data.corr() # 相关系数矩阵
data.corrwith(data.three) # df各列与three列相关系数
roller.apply(lambda x:x.mean()) # apply 传入自定义函数

s = pd.Series(np.random.randint(-1,2,30).cumsum())
s.ewm(alpha=0.2).mean().head() # 指数加权

shift, diff, pct_change 是一组类滑窗函数,公共参数为 periods=n ,默认为1,这里的 n 可以为负,表示反方向的类似操作。

函数 说明
shift 取向前第 n 个元素的值
diff 与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)
pct_change 与向前第 n 个元素相比计算增长率

pct_change

df.pct_change()

  • DataFrame.pct_change(periods=1, fill_method=’pad’, limit=None, freq=None, **kwargs)
  • 表示当前元素与先前元素的相差百分比,当然指定periods=n,表示当前元素与先前n 个元素的相差百分比
# ---------------
s = pd.Series([90, 91, 85])
s.pct_change(periods=2)#表示当前元素与先前两个元素百分比
# ------ dataframe ------
df = pd.DataFrame({
   'FR': [4.0405, 4.0963, 4.3149],
   'GR': [1.7246, 1.7482, 1.8519],
   'IT': [804.74, 810.01, 860.13]},
   index=['1980-01-01', '1980-02-01', '1980-03-01'])
print(df)
print(df.pct_change())
print(df.pct_change(axis='columns'))#可以指定按照行还是列进行计算的

diff

将某行/列移动periods周期后,与原来数据进行对比,取得差值

import pandas as pd
import numpy as np

df = pd.DataFrame(np.arange(24).reshape(6,4),index=['r1','r2','r3','r4','r5','r6'],columns=['A','B','C','D'])
df

df.diff(periods=2,axis=0)

shift

功能:数据移动

  • 若freq=None时,根据axis的设置,行索引数据保持不变,列索引数据可以在行上上下移动或在列上左右移动;
  • 若行索引为时间序列,则可以设置freq参数,根据periods和freq参数值组合,使行索引每次发生periods*freq偏移量滚动,列索引数据不会移动

  • ① 对于DataFrame的行索引是日期型,行索引发生移动,列索引数据不变
    • 结论:对于时间索引而言,shift使时间索引发生移动,其他数据保存原样,且axis设置没有任何影响
  • ②对于DataFrame行索引为非时间序列,行索引数据保持不变,列索引数据发生移动
import pandas as p
import numpy as n

df = pd.DataFrame(np.arange(24).reshape(6,4), # 数据源,6*4矩阵
    index=pd.date_range(start='20170101',periods=6), # 指定index为日期值
    columns=['A','B','C','D']) # 指定列名
df
df.shift(2,axis=0,freq='2D')
df.shift(2,axis=1,freq='2D')
df.shift(2,freq='2D')
# ----- index非时间序列,行索引不变,列索引数据移动
df = pd.DataFrame(np.arange(24).reshape(6,4),index=['r1','r2','r3','r4','r5','r6'],columns=['A','B','C','D'])
df.shift(periods=2,axis=0) # 行数据向下移动两行
df.shift(periods=-2,axis=0)# 行数据向上移动两行
df.shift(periods=2,axis=1) # 列数据向右移动两列
df.shift(periods=-2,axis=1)# 列数据向左移动两列

文件读写

excel

excel文件:openpyxl、pandas、xlrd(2.0版本不再支持xlsx文件,因此我用的1.2版本)、xlwings分别读取测试性能

  • openpyxl读取得到1048553行,耗时32秒pandas读取得到1048553行(包括表头行),耗时49秒
  • xlrd读取得到63行,耗时15秒(将xlrd获取的数据用遍历方式读入list再转化为DataFrame的操作,耗时微秒级)
  • xlwings读取得到1048553行,耗时6秒(将xlwings获取的数据用expand方法读入list可以获得正确的行数,再转化为DataFrame的操作,耗时毫秒级)

xlrd是python环境下对excel中的数据进行读取的一个模板,可以进行的操作有:

  • 读取有效单元格的行数、列数
  • 读取指定行(列)的所有单元格的值
  • 读取指定单元格的值
  • 读取指定单元格的数据类型

pandas文件操作

# pandas读取excel数据示例
# 【2016-7-30】 参考:十分钟搞定pandas
import pandas as pd
import numpy as np
# 读取数据 D:\work\用户建模画像\家公司挖掘\code\warren.xls
# 数据格式:time
print 'start'
df = pd.read_excel('C:\Users\warren\Desktop\warren.xlsx',index='time')
#[2018-4-11] 先安装!pip install xlrd才可以
df = pd.read_excel(open('your_xls_xlsx_filename','rb'), sheetname=1) # 读取第二个sheet
df = pd.read_excel(open('your_xls_xlsx_filename','rb'), sheetname='Sheet 1')
df = pd.read_excel('your_xls_xlsx_filename', names=['a','b'])  # 自定义表头
df = pd.read_excel('your_xls_xlsx_filename', header=None)  # 不用表头
df = pd.read_excel('your_xls_xlsx_filename', header=0)  # 第1行作为表头
df = pd.read_excel('your_xls_xlsx_filename', header=1)  # 第2行作为表头
df = pd.read_excel('your_xls_xlsx_filename', header=[1,2,3])  # 选择2、3、4行作为表头
# 默认读取全部列,可以自定义读取哪些列
pd.read_excel('tmp.xlsx', usecols='A,B')  # 取 A 和 B 两列
pd.read_excel('tmp.xlsx', usecols='A:H')  # 取 A 到 H 列
pd.read_excel('tmp.xlsx', usecols='A,C,E:H')  # 取 A和C列,再加E到H列
pd.read_excel('tmp.xlsx', usecols=[0,1])  # 取前两列
pd.read_excel('tmp.xlsx', usecols=['姓名','性别'])  # 取指定列名的列
# 表头包含 Q 的
pd.read_excel('team.xlsx', usecols=lambda x: 'Q' in x)
# 指定去读行数
pd.read_excel(data, nrows=1000) # 读取前1000行
pd.read_excel(filename, skipfooter=1) # 最后一行不加载
# 跳过指定行 skiprows;list-like, int or callable, optional
pd.read_excel(data, skiprows=2) # 跳过前三行
pd.read_excel(data, skiprows=range(2)) # 跳过前三行
pd.read_excel(data, skiprows=[24,234,141]) # 跳过指定行
pd.read_excel(data, skiprows=np.array([2, 6, 11])) # 跳过指定行
pd.read_excel(data, skiprows=lambda x: x % 2 != 0) # 隔行跳过
# 跳过最后几行用 skipfooter=2
# 读取 子页面
df = pd.read_excel('your_xls_xlsx_filename', sheet_name='Sheet') # [2022-9-13]实测通过

# 显示excel里的sheet列表
ReadSheets=pd.ExcelFile('test.xlsx')
for i in ReadSheets.sheet_names:
    print(i)
# 替换控制
pd.read_excel(data, na_values=[5]) # 5 和 5.0 会被认为 NaN
pd.read_excel(data, na_values='?') # ? 会被认为 NaN
pd.read_excel(data, keep_default_na=False, na_values=[""]) # 空值为 NaN
pd.read_excel(data, keep_default_na=False, na_values=["NA", "0"]) # 字符 NA 字符 0 会被认为 NaN
pd.read_excel(data, na_values=["Nope"]) # Nope 会被认为 NaN
# a、b、c 均会被认为 NaN 等于 na_values=['a','b','c']
pd.read_excel(data, na_values='abc')
pd.read_excel(data, na_values={'c':3, 1:[2,5]}) # 指定列的指定值会被认为 NaN
# 保留默认空值 keep_default_na
# 指定 na_values 参数,并且 keep_default_na=False,那么默认的NaN将被覆盖,否则添加。
pd.read_excel(data, keep_default_na=False) # 不自动识别空值
# 自动解析时间日期
pd.read_excel(data, parse_dates=True) # 自动解析日期时间格式
pd.read_excel(data, parse_dates=['年份']) # 指定日期时间字段进行解析
# 将 1、4 列合并解析成名为 时间的 时间类型列
pd.read_excel(data, parse_dates={'时间':[1,4]})
长整数错乱

【2024-1-23】读取excel时,如果有数值较大的整数(id编号),会被自动转成科学计数法,无法恢复

怎么办? 知乎

  • 读文件时 强制将所有字段当 字符串 处理
  • 改用 read_csv 方法
  • 后处理:空值转换,将float类型转成int64
df = pd.read_excel('data/botskill标注答案汇总1.23.xlsx')
#df['Bot编号'] = df['Bot编号'].astype('str') # 后置类型转换失败
# 解决方法:
# ①  读文件时,强制将所有字段当 字符串 处理
df = pd.read_excel('data/botskill标注答案汇总1.23.xlsx', dtype=object) # 或
df = pd.read_excel('data/botskill标注答案汇总1.23.xlsx', dtype=str)
# 多个字段格式不同时,不便挨个设置类型, dtype 直接把所有列都无损读取
from numpy import dtype
df = pd.read_excel('data/botskill标注答案汇总1.23.xlsx', dtype=dtype)
# ② 后处理:空值转换,将float类型转成int64 —— 【2024-1-23】这种方法不行!取值变化,原因:数字长度超过16位就会因为float的精度问题无法还原原来的数字
# 前 7290913086481940488
# 后 7290913086481939456
df['Bot编号'] = df['Bot编号'].fillna(-1).astype('int64')
删除空行

参数说明:

  • axis:默认为 0,表示逢空值剔除整行,如果设置参数 axis=1 表示逢空值去掉整列
  • how:默认为 ‘any’ 如果一行(或一列)里任何一个数据有出现 NA 就去掉整行
    • 如果设置 how=’all’ 一行(或列)都是 NA 才去掉这整行。
  • thresh:设置需要多少非空值的数据才可以保留下来的。
  • subset:设置想要检查的列。如果是多个列,可以使用列名的 list 作为参数。
  • inplace:如果设置 True,将计算得到的值直接覆盖之前的值并返回 None,修改的是源数据。
import pandas as pd
df=pd.read_excel(r'清洗空值.xlsx')
df = pd.read_excel('file.xlsx', skipna=True) # 忽略空行,不管用
# 删除空行
df1=df.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False) # 删除全部为空的行
df2=df.dropna(axis=0, how='all', thresh=None, subset=None, inplace=False) # 只有全部为空才回被删除
df3=df.dropna(axis=0, how='all', thresh=5, subset=None, inplace=False) # 原地删除
print(df1)
print(df2)
print(df3)

csv

读取csv/tsv文件

df = pd.read_table('data/raw_tmp_new.txt',header=None,names=['a','b','c'])
data_file = 'D:/project/python_instruct/test_data1.csv'
df = pd.read_csv(data_file)#用read_csv读取的csv文件
# skiprows:排除前3行是skiprows=3 排除第3行是skiprows=[3]
df = pd.read_csv(data_file, skiprows=3)
df = pd.read_csv(data_file, encoding='utf8') # 编码
df = pd.read_table(data_file, sep=',')#用read_table读取csv文件
df = pd.read_csv("data.txt",sep="\s+") # 不规则分隔符
df = pd.read_csv(data_file, header=None)#用read_csv读取无标题行的csv文件
df.columns = ['A','B','C'] # 事后修改列名
df = pd.read_csv(data_file, names=['a', 'b', 'c', 'd', 'message']) # 用read_csv读取自定义标题行的csv文件

df =  pd.read_csv("./demo.txt",header=None, names=['a','b','c','d','e'])
df =  pd.read_csv("./demo.txt",header=None, index_col=False,names=['a','b','c','d','e'])
# 自定义某列处理方法:converters 设置指定列的处理函数,可以用"序号"也可以使用“列名”进行列的指定
def fun(x):
    return str(x)+"-haha"
df =  pd.read_csv("./test.txt",sep=' ',header=None,index_col=0,converters={3:fun})

names=['a', 'b', 'c', 'd', 'message']
df=pd.read_csv(data_file, names=names, index_col='message')#'read_csv读取时指定索引
parsed=pd.read_csv(data_file, index_col=['key1', 'key2'])#read_csv将多个列做成一个层次化索引
print(list(open(data_file)))
# read_table ≈ read_csv,区别仅在与分隔符
result=pd.read_table(data_file, sep='\s+')#read_table利用正则表达式处理文件读取
# 固定宽度文件
colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]
df = pd.read_fwf('demo.txt', colspecs=colspecs, header=None, index_col=0)
# read_msgpack 函数
#    pandas支持的一种新的可序列化的数据格式,这是一种轻量级的可移植二进制格式,类似于二进制JSON,这种数据空间利用率高,在写入(序列化)和读取(反序列化)方面都提供了良好的性能。
# read_clipboard 函数
#   读取剪贴板中的数据,可以看作read_table的剪贴板版本。在将网页转换为表格时很有用

# 直接用一个宽度列表,可以代替colspecs参数
widths = [6, 14, 13, 10]
df = pd.read_fwf('demo.txt', widths=widths, header=None)

# json
s = '{"index":[1,2,3],"columns":["a","b"],"data":[[1,3],[2,5],[6,9]]}'
df = pd.read_json(s, orient='split')
s = '[{"a":1,"b":2},{"a":3,"b":4}]'
df = pd.read_json(s,orient='records')
# html
df = pd.read_html("http://data.stcn.com/2019/0304/14899644.shtml",flavor ='lxml')

# ------- 更多用法 ---------
DataFrame.from_csvpath [headersep,…] # 读取CSV文件(DISCOURAGED,请改用pandas.read_csv())。 
DataFrame.from_dictdata [orientdtype] # 从类array或dicts的dict构造DataFrame 
DataFrame.from_itemsitems [columnsorient] # 将(键,值)对转换为DataFrame。 
DataFrame.from_recordsdata [index,…] # 将结构化或记录ndarray转换为DataFrame 
DataFrame.info[verbosebufmax_cols,…]# DataFrame的简明摘要。 
DataFrame.to_picklepath# Pickle(序列化)对象到输入文件路径。 
DataFrame.to_csv[path_or_bufsepna_rep,…] # 将DataFrame写入逗号分隔值(csv)文件 

# [2021-11-11] 注意:df.to_csv写入的字符串如果包含json串,结果会出现问题,所有"变成"",破坏json格式。
df = pd.DataFrame([{"test": 'id={"name":"test"}'}]) 
df.to_csv('t.txt', sep='\t') # 结果 id={""name"":""test""}
df.to_csv("test.txt", sep='|',index=False,header=True)
# 改进办法:
import csv
df.to_csv("test.csv", sep='|', quoting=csv.QUOTE_NONE, index=False, header=True)

DataFrame.to_hdfpath_or_bufkey, kwargs # 使用HDFStore将包含的数据写入HDF5文件。 
DataFrame.to_sqlnamecon [flavor,…] # 将存储在DataFrame中的记录写入SQL数据库。 
DataFrame.to_dict[orient] #将DataFrame转换成字典。 
DataFrame.to_excelexcel_writer [,…] #将DataFrame写入excel表 
DataFrame.to_json[path_or_buforient,…] # 将对象转换为JSON字符串。 默认是一个json串,未按记录换行
df.to_json(out_file, orient='records') # 【2024-3-15】保存为jsonl格式
df.to_json(out_file, orient='records', lines=True)
DataFrame.to_html[bufcolumnscol_space,…] # 将DataFrame呈现为HTML表格。 
DataFrame.to_latex[bufcolumns,…] # 将DataFrame呈现为表格环境表。 
DataFrame.to_statafname [convert_dates,…] # 从数组类对象中写入Stata二进制dta文件的类 
DataFrame.to_msgpack[path_or_bufencoding] # msgpack(serialize)对象到输入文件路径 
DataFrame.to_gbqdestination_tableproject_id# 将DataFrame写入Google - BigQuery表格。 
DataFrame.to_records[indexconvert_datetime64] # 将DataFrame转换为记录数组。 
DataFrame.to_sparse[fill_valuekind] # 转换为SparseDataFrame 
DataFrame.to_dense() # 返回NDFrame的密集表示(而不是稀疏) 
DataFrame.to_string[bufcolumns,…] # 将DataFrame呈现为控制台友好的表格输出。 
DataFrame.to_clipboard[excelsep]
# 【2023-2-11】输出为markdown格式
# pip install tabulate 先安装插件
df.to_markdown() 

import xlrd
data = xlrd.open_workbook("01.xls")#打开当前目录下名为01.xls的文档
#此时data相当于指向该文件的指针
table = data.sheet_by_index(0)#通过索引获取,例如打开第一个sheet表格
table = data.sheet_by_name("sheet1")#通过名称获取,如读取sheet1表单
table = data.sheets()[0]#通过索引顺序获取
# 以上三个函数都会返回一个xlrd.sheet.Sheet()对象
 
names = data.sheet_names()    #返回book中所有工作表的名字
data.sheet_loaded(sheet_name or indx)   # 检查某个sheet是否导入完毕
# ---- 行操作 ------
nrows = table.nrows  #获取该sheet中的有效行数
table.row(rowx)  #返回由该行中所有的单元格对象组成的列表
table.row_slice(rowx)  #返回由该列中所有的单元格对象组成的列表
table.row_types(rowx, start_colx=0, end_colx=None)    #返回由该行中所有单元格的数据类型组成的列表
table.row_values(rowx, start_colx=0, end_colx=None)   #返回由该行中所有单元格的数据组成的列表
table.row_len(rowx) #返回该列的有效单元格长度
# ---- 列操作 ------
ncols = table.ncols#获取列表的有效列数
table.col(colx, start_rowx=0, end_rowx=None)#返回由该列中所有的单元格对象组成的列表
table.col_slice(colx, start_rowx=0, end_rowx=None)#返回由该列中所有的单元格对象组成的列表
table.col_types(colx, start_rowx=0, end_rowx=None)#返回由该列中所有单元格的数据类型组成的列表
table.col_values(colx, start_rowx=0, end_rowx=None)#返回由该列中所有单元格的数据组成的列表
# ---- 单元格操作 ------
table.cell(rowx, colx)  # 返回单元格对象
table.cell_type(rowx, colx)  # 返回单元格中的数据类型
table.cell_value(rowx,colx)   #返回单元格中的数据

可视化

pandas自带matplotlib库

绘图

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

a = pd.Series(np.random.randn(1000),index=pd.date_range('20100101',periods=1000))
b = a.cumsum()

# df.plot(x='Column1', y='Column2', kind='scatter')

b.plot()
df.plot.area() # 面积图
df.plot.bar() # 柱形图
df.plot.barh() # 水平柱形图
df.plot.density() # 密度图
df.plot.kde()
df.plot.hist() # 直方图
# [2022-8-18] 自定义背景大小、分桶数目,打开网格线 —— 绘图参数参考matplotlib
df['总数'].plot.hist(figsize=(50,30),bins=100,,grid=True)
df.plot.scatter()
df.plot.pie()

散点图示例

import pandas as pd

df_test = pd.DataFrame([[5.1, 3.5, 0], [4.9, 3.0, 0], [7.0, 3.2, 1], [6.4, 3.2, 1], [5.9, 3.0, 2]], columns=['length', 'width', 'species'])
ax1 = df_test.plot.scatter(x='length', y='width', c='DarkBlue') # 散点格式
ax2 = df_test.plot.scatter(x='length', y='width', c='species', colormap='viridis') # 散点颜色区分不同数值

表格级别美化(适用于jupyter notebook), 官方style介绍

# -------- (1) -------
pd_list[pd_list['前两位']>0.5]
# 按照数值范围显示不同颜色
def showColor(val):
    color = 'red' if val <= 0 else 'green'
    return 'color:%s'%(color)
#正规方式,单独定义函数
pd_list.style.applymap(showColor)
#简洁方式,使用匿名函数,lambda
pd_list.style.applymap(lambda val: 'color:red' if val <= 1 else 'color:green')
# -------- (2) -------
# 调用seaborn的颜色主题
import seaborn as sns

cm = sns.light_palette("green", as_cmap=True)
s = df.style.background_gradient(cmap=cm)
#df.loc[:4].style.background_gradient(cmap='viridis')
s
# -----------------
import seaborn as sns
#!sudo pip install seaborn
cm = sns.light_palette("green", as_cmap=True)
#s = dh.style.background_gradient(cmap=cm)
col_list = [u'问题项名称',u'展现量41',u'流量占比',u'评价量41',u'参评率41',u'解决量41',u'解决率41']
#dh[dh[u'参评率41']>0.1, col_list]
out = dh[col_list][dh[u'参评率41']>0.05][dh[u'展现量41']> 50]
out.style.background_gradient(cmap=cm)
#out.style.background_gradient(cmap='viridis').highlight_null('red')#突出极值.highlight_max(axis=0)
#out.style.bar(subset=['A', 'B'], color='#d65f5f') # 柱状图显示
#out.style.bar(subset=['A', 'B'], align='mid', color=['#d65f5f', '#5fba7d']) # 正负双向柱状图

pandasgui

【2021-9-24】一个 GUI 接口,可以帮助我们完成自动化探索性数据分析的过程,并节省时间和精力

安装Pandasgui

pip install pandasgui
# 直接从 Github 安装最新的未发布更改:
pip install git+https://github.com/adamerose/pandasgui.git

功能:过滤、统计、绘图等

示例

from pandasgui.datasets import iris
#importing the show function
from pandasgui import show
show(iris)
# 自定义数据
import pandas as pd
from pandasgui import show
df = pd.DataFrame(([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['a', 'b', 'c'])
show(df)

Python技巧

Python 是机器学习最广泛采用的编程语言,它最重要的优势在于编程的易用性。如果读者对基本的 Python 语法已经有一些了解,那么这篇文章可能会给你一些启发。作者简单概览了 30 段代码,它们都是平常非常实用的技巧,我们只要花几分钟就能从头到尾浏览一遍。

1. 重复元素判定

以下方法可以检查给定列表是不是存在重复元素,它会使用 set() 函数来移除所有重复元素。

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


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

2. 字符元素组成判定

检查两个字符串的组成元素是不是一样的。

from collections import Counter

def anagram(first, second):
    return Counter(first) == Counter(second)

anagram("abcd3", "3acdb") # True

3. 内存占用

下面的代码块可以检查变量 variable 所占用的内存。

import sys 

variable = 30 
print(sys.getsizeof(variable)) # 24

4. 字节占用

下面的代码块可以检查字符串占用的字节数。

def byte_size(string):
    return(len(string.encode('utf-8')))

byte_size(' ') # 4
byte_size('Hello World') # 11  

5. 打印 N 次字符串

该代码块不需要循环语句就能打印 N 次字符串。

n = 2; 
s ="Programming"; 

print(s * n);
# ProgrammingProgramming  

6. 大写第一个字母

以下代码块会使用 title() 方法,从而大写字符串中每一个单词的首字母。

s = "programming is awesome"

print(s.title())
# Programming Is Awesome

7. 分块

给定具体的大小,定义一个函数以按照这个大小切割列表。

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]

8. 压缩

这个方法可以将布尔型的值去掉,例如(False,None,0,“”),它使用 filter() 函数。

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

compact([0, 1, False, 2, '', 3, 'a', 's', 34])
# [ 1, 2, 3, 'a', 's', 34 ]

9. 解包

如下代码段可以将打包好的成对列表解开成两组不同的元组。

array = [['a', 'b'], ['c', 'd'], ['e', 'f']]
transposed = zip(*array)
print(transposed)
# [('a', 'c', 'e'), ('b', 'd', 'f')]

10. 链式对比

我们可以在一行代码中使用不同的运算符对比多个不同的元素。

a = 3
print( 2 < a < 8) # True
print(1 == a < 2) # False

11. 逗号连接

下面的代码可以将列表连接成单个字符串,且每一个元素间的分隔方式设置为了逗号。

hobbies = ["basketball", "football", "swimming"]

print("My hobbies are: " + ", ".join(hobbies))
# My hobbies are: basketball, football, swimming

12. 元音统计

以下方法将统计字符串中的元音 (‘a’,’e’,’i’,’o’,’u’) 的个数,它是通过正则表达式做的。

import re

def count_vowels(str):
    return len(len(re.findall(r'[aeiou]', str, re.IGNORECASE)))

count_vowels('foobar') # 3
count_vowels('gym') # 0

13. 首字母小写

如下方法将令给定字符串的第一个字符统一为小写。

def decapitalize(string):
    return str[:1].lower() + str[1:]

decapitalize('FooBar') # 'fooBar'
decapitalize('FooBar') # 'fooBar'

14. 展开列表

该方法将通过递归的方式将列表的嵌套展开为单个列表。

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]

15. 列表的差

该方法将返回第一个列表的元素,其不在第二个列表内。如果同时要反馈第二个列表独有的元素,还需要加一句 set_b.difference(set_a)。

def difference(a, b):
    set_a = set(a)
    set_b = set(b)
    comparison = set_a.difference(set_b)
    return list(comparison)


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

16. 通过函数取差

如下方法首先会应用一个给定的函数,然后再返回应用函数后结果有差别的列表元素。

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 } ]

17. 链式函数调用

你可以在一行代码内调用多个函数。

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

a, b = 4, 5
print((subtract if a > b else add)(a, b)) # 9 

18. 检查重复项

如下代码将检查两个列表是不是有重复项。

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

19. 合并两个字典

下面的方法将用于合并两个字典。

def merge_two_dicts(a, b):
    c = a.copy()   # make a copy of a 
    c.update(b)    # modify keys and values of a with the ones from b
    return c

a = { 'x': 1, 'y': 2}
b = { 'y': 3, 'z': 4}
print(merge_two_dicts(a, b))
# {'y': 3, 'x': 1, 'z': 4}

在 Python 3.5 或更高版本中,我们也可以用以下方式合并字典:

def merge_dictionaries(a, b)
   return {**a, **b}

a = { 'x': 1, 'y': 2}
b = { 'y': 3, 'z': 4}
print(merge_dictionaries(a, b))
# {'y': 3, 'x': 1, 'z': 4}

20. 将两个列表转化为字典

如下方法将会把两个列表转化为单个字典。

def to_dictionary(keys, values):
    return dict(zip(keys, values))


keys = ["a", "b", "c"]    
values = [2, 3, 4]
print(to_dictionary(keys, values))
# {'a': 2, 'c': 4, 'b': 3}

21. 使用枚举

我们常用 For 循环来遍历某个列表,同样我们也能枚举列表的索引与值。

list = ["a", "b", "c", "d"]
for index, element in enumerate(list): 
    print("Value", element, "Index ", index, )

# ('Value', 'a', 'Index ', 0)
# ('Value', 'b', 'Index ', 1)
#('Value', 'c', 'Index ', 2)
# ('Value', 'd', 'Index ', 3)    

22. 执行时间

如下代码块可以用来计算执行特定代码所花费的时间。

import time

start_time = time.time()

a = 1
b = 2
c = a + b
print(c) #3

end_time = time.time()
total_time = end_time - start_time
print("Time: ", total_time)

# ('Time: ', 1.1205673217773438e-05)  

23. Try else

我们在使用 try/except 语句的时候也可以加一个 else 子句,如果没有触发错误的话,这个子句就会被运行。

try:
    2*3
except TypeError:
    print("An exception was raised")
else:
    print("Thank God, no exceptions were raised.")

#Thank God, no exceptions were raised.

24. 元素频率

下面的方法会根据元素频率取列表中最常见的元素。

def most_frequent(list):
    return max(set(list), key = list.count)

list = [1,2,1,2,3,2,1,4,2]
most_frequent(list)  

25. 回文序列

以下方法会检查给定的字符串是不是回文序列,它首先会把所有字母转化为小写,并移除非英文字母符号。最后,它会对比字符串与反向字符串是否相等,相等则表示为回文序列。

def palindrome(string):
    from re import sub
    s = sub('[\W_]', '', string.lower())
    return s == s[::-1]

palindrome('taco cat') # True

26. 不使用 if-else 的计算子

这一段代码可以不使用条件语句就实现加减乘除、求幂操作,它通过字典这一数据结构实现:

import operator
action = {
    "+": operator.add,
    "-": operator.sub,
    "/": operator.truediv,
    "*": operator.mul,
    "**": pow
}
print(action['-'](50, 25)) # 25

27. Shuffle

该算法会打乱列表元素的顺序,它主要会通过 Fisher-Yates 算法对新列表进行排序:

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]

28. 展开列表

将列表内的所有元素,包括子列表,都展开成一个列表。

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

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

29. 交换值

不需要额外的操作就能交换两个变量的值。

def swap(a, b):
  return b, a

a, b = -1, 14
swap(a, b) # (14, -1)
spread([1,2,3,[4,5,6],[7],8,9]) # [1,2,3,4,5,6,7,8,9]

30. 字典默认值

通过 Key 取对应的 Value 值,可以通过以下方式设置默认值。如果 get() 方法没有设置默认值,那么如果遇到不存在的 Key,则会返回 None。

d = {'a': 1, 'b': 2}
print(d.get('c', 3)) # 3

设计模式

二十三种设计模式及其python实现

本文源码寄方于github

参考文献:

  • 《大话设计模式》——吴强
  • 《Python设计模式》——pythontip.com
  • 《23种设计模式》——http://www.cnblogs.com/beijiguangyong/

设计模式是经过总结、优化的,针对一些编程问题的可重用解决方案。设计模式不像那样能够直接作用于代码,设计模式更为高级,是一种必须在特定情形下实现的一种方法模板。设计模式不会绑定具体的编程语言。

  • 好的设计模式应该能够用大部分编程语言实现(如果做不到全部的话,具体取决于语言特性)。
  • 设计模式也是一把双刃剑,如果用在不恰当的情形下将会造成灾难,进而带来无穷的麻烦。然而如果设计模式在正确的时间被用在正确地地方,它将是你的救星。

“模式”就是为了解决一类特定问题而特别想出来的明智之举。

虽然被称为“设计模式”,但是它们同“设计“领域并非紧密联系。设计模式同传统意义上的分析、设计与实现不同,事实上设计模式将一个完整的理念根植于程序中,所以它可能出现在分析阶段或是更高层的设计阶段。很有趣的是因为设计模式的具体体现是程序代码,因此可能会让你认为它不会在具体实现阶段之前出现(事实上在进入具体实现阶段之前你都没有意识到正在使用具体的设计模式)。

可以通过程序设计的基本概念来理解模式:增加一个抽象层。抽象一个事物就是隔离任何具体细节,这么做的目的是为了将那些不变的核心部分从其他细节中分离出来。当你发现你程序中的某些部分经常因为某些原因改动,而你不想让这些改动的部分引发其他部分的改动,这时候你就需要思考那些不会变动的设计方法了。这么做不仅会使代码可维护性更高,而且会让代码更易于理解,从而降低开发成本。

三种最基本的设计模式:

  • 创建模式,提供实例化的方法,为适合的状况提供相应的对象创建方法。
  • 结构化模式,通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。
  • 行为模式,用于在不同的实体建进行通信,为实体之间的通信提供更容易,更灵活的通信方法。

创建型

  1. Factory Method(工厂方法)
  2. Abstract Factory(抽象工厂)
  3. Builder(建造者)
  4. Prototype(原型)
  5. Singleton(单例)

Factory Method(工厂方法)

意图:

  • 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类

适用性:

  • 当一个类不知道它所必须创建的对象的类的时候。
  • 当一个类希望由它的子类来指定它所创建的对象的时候。
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
#!/usr/bin/python
#coding:utf8
'''
Factory Method
'''

class ChinaGetter:
    """A simple localizer a la gettext"""
    def __init__(self):
        self.trans = dict(dog=u"小狗", cat=u"小猫")
 
    def get(self, msgid):
        """We'll punt if we don't have a translation"""
        try:
            return self.trans[msgid]
        except KeyError:
            return str(msgid)

class EnglishGetter:
    """Simply echoes the msg ids"""
    def get(self, msgid):
        return str(msgid)

def get_localizer(language="English"):
    """The factory method"""
    languages = dict(English=EnglishGetter, China=ChinaGetter)
    return languages[language]()
 
# Create our localizers
e, g = get_localizer("English"), get_localizer("China")
# Localize some text
for msgid in "dog parrot cat bear".split():
    print(e.get(msgid), g.get(msgid))

Abstract Factory(抽象工厂)

意图:

  • 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 适用性:
  • 一个系统要独立于它的产品的创建、组合和表示时。
  • 一个系统要由多个产品系列中的一个来配置时。
  • 当要强调一系列相关的产品对象的设计以便进行联合使用时。
  • 当提供一个产品类库,而只想显示它们的接口而不是实现时。
#!/usr/bin/python
#coding:utf8
'''
Abstract Factory
'''

import random
 
class PetShop:
    """A pet shop"""
 
    def __init__(self, animal_factory=None):
        """pet_factory is our abstract factory. We can set it at will."""
 
        self.pet_factory = animal_factory
 
    def show_pet(self):
        """Creates and shows a pet using the abstract factory"""
 
        pet = self.pet_factory.get_pet()
        print("This is a lovely", str(pet))
        print("It says", pet.speak())
        print("It eats", self.pet_factory.get_food())

# Stuff that our factory makes
class Dog:
    def speak(self):
        return "woof"
 
    def __str__(self):
        return "Dog"

class Cat:
    def speak(self):
        return "meow"
 
    def __str__(self):
        return "Cat"

# Factory classes
 
class DogFactory:
    def get_pet(self):
        return Dog()
 
    def get_food(self):
        return "dog food"

class CatFactory:
    def get_pet(self):
        return Cat()
 
    def get_food(self):
        return "cat food"

# Create the proper family
def get_factory():
    """Let's be dynamic!"""
    return random.choice([DogFactory, CatFactory])()
 
# Show pets with various factories
if __name__ == "__main__":
    shop = PetShop()
    for i in range(3):
        shop.pet_factory = get_factory()
        shop.show_pet()
        print("=" * 20)

Builder(建造者)

意图:

  • 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

适用性:

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。
#!/usr/bin/python
#coding:utf8
 
"""
    Builder
"""
 
# Director
class Director(object):
    def __init__(self):
        self.builder = None
 
    def construct_building(self):
        self.builder.new_building()
        self.builder.build_floor()
        self.builder.build_size()
 
    def get_building(self):
        return self.builder.building

# Abstract Builder
class Builder(object):
    def __init__(self):
        self.building = None
 
    def new_building(self):
        self.building = Building()
 
# Concrete Builder
class BuilderHouse(Builder):
    def build_floor(self):
        self.building.floor = 'One'
 
    def build_size(self):
        self.building.size = 'Big'

class BuilderFlat(Builder):
    def build_floor(self):
        self.building.floor = 'More than One'
 
    def build_size(self):
        self.building.size = 'Small'
 
# Product
class Building(object):
    def __init__(self):
        self.floor = None
        self.size = None
 
    def __repr__(self):
        return 'Floor: %s | Size: %s' % (self.floor, self.size)
 
# Client
if __name__ == "__main__":
    director = Director()
    director.builder = BuilderHouse()
    director.construct_building()
    building = director.get_building()
    print(building)
    director.builder = BuilderFlat()
    director.construct_building()
    building = director.get_building()
    print(building)

Prototype(原型)

意图:

  • 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

适用性:

  • 当要实例化的类是在运行时刻指定时,例如,通过动态装载;或者为了避免创建一个与产品类层次平行的工厂类层次时;或者当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
#!/usr/bin/python
#coding:utf8
'''
Prototype
'''

import copy
 
class Prototype:
    def __init__(self):
        self._objects = {}
 
    def register_object(self, name, obj):
        """Register an object"""
        self._objects[name] = obj
 
    def unregister_object(self, name):
        """Unregister an object"""
        del self._objects[name]
 
    def clone(self, name, **attr):
        """Clone a registered object and update inner attributes dictionary"""
        obj = copy.deepcopy(self._objects.get(name))
        obj.__dict__.update(attr)
        return obj

def main():
    class A:
        def __str__(self):
            return "I am A"
 
    a = A()
    prototype = Prototype()
    prototype.register_object('a', a)
    b = prototype.clone('a', a=1, b=2, c=3)
 
    print(a)
    print(b.a, b.b, b.c)
 
 
if __name__ == '__main__':
    main()

Singleton(单例)

意图:

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用性:

  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
#!/usr/bin/python
#coding:utf8
'''
Singleton
'''
 
class Singleton(object):
    ''''' A python style singleton '''
 
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            org = super(Singleton, cls)
            cls._instance = org.__new__(cls, *args, **kw)
        return cls._instance
 
 
if __name__ == '__main__':
    class SingleSpam(Singleton):
        def __init__(self, s):
            self.s = s
 
        def __str__(self):
            return self.s
 
 
    s1 = SingleSpam('spam')
    print id(s1), s1
    s2 = SingleSpam('spa')
    print id(s2), s2
    print id(s1), s1

结构型

Adapter Class/Object(适配器)

意图:

  • 将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适用性:

  • 你想使用一个已经存在的类,而它的接口不符合你的需求。
  • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
  • (仅适用于对象Adapter )你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
#!/usr/bin/python
#coding:utf8
'''
Adapter
'''
 
import os
 
 
class Dog(object):
    def __init__(self):
        self.name = "Dog"
 
    def bark(self):
        return "woof!"
 
 
class Cat(object):
    def __init__(self):
        self.name = "Cat"
 
    def meow(self):
        return "meow!"
 
 
class Human(object):
    def __init__(self):
        self.name = "Human"
 
    def speak(self):
        return "'hello'"

class Car(object):
    def __init__(self):
        self.name = "Car"
 
    def make_noise(self, octane_level):
        return "vroom%s" % ("!" * octane_level)

class Adapter(object):
    """
    Adapts an object by replacing methods.
    Usage:
    dog = Dog
    dog = Adapter(dog, dict(make_noise=dog.bark))
    """
    def __init__(self, obj, adapted_methods):
        """We set the adapted methods in the object's dict"""
        self.obj = obj
        self.__dict__.update(adapted_methods)
 
    def __getattr__(self, attr):
        """All non-adapted calls are passed to the object"""
        return getattr(self.obj, attr)

def main():
    objects = []
    dog = Dog()
    objects.append(Adapter(dog, dict(make_noise=dog.bark)))
    cat = Cat()
    objects.append(Adapter(cat, dict(make_noise=cat.meow)))
    human = Human()
    objects.append(Adapter(human, dict(make_noise=human.speak)))
    car = Car()
    car_noise = lambda: car.make_noise(3)
    objects.append(Adapter(car, dict(make_noise=car_noise)))
 
    for obj in objects:
        print "A", obj.name, "goes", obj.make_noise()
 
 
if __name__ == "__main__":
    main()

Bridge(桥接)

意图:

  • 将抽象部分与它的实现部分分离,使它们都可以独立地变化。

适用性:

  • 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
  • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge 模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
  • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
  • (C++)你想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的。
  • 有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。Rumbaugh 称这种类层次结构为“嵌套的普化”(nested generalizations )。
  • 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。一个简单的例子便是Coplien 的String 类[ Cop92 ],在这个类中多个对象可以共享同一个字符串表示(StringRep)。
#!/usr/bin/python
#coding:utf8
'''
Bridge
'''

# ConcreteImplementor 1/2
class DrawingAPI1(object):
    def draw_circle(self, x, y, radius):
        print('API1.circle at {}:{} radius {}'.format(x, y, radius))
 
 
# ConcreteImplementor 2/2
class DrawingAPI2(object):
    def draw_circle(self, x, y, radius):
        print('API2.circle at {}:{} radius {}'.format(x, y, radius))
 
 
# Refined Abstraction
class CircleShape(object):
    def __init__(self, x, y, radius, drawing_api):
        self._x = x
        self._y = y
        self._radius = radius
        self._drawing_api = drawing_api
 
    # low-level i.e. Implementation specific
    def draw(self):
        self._drawing_api.draw_circle(self._x, self._y, self._radius)
 
    # high-level i.e. Abstraction specific
    def scale(self, pct):
        self._radius *= pct
 
 
def main():
    shapes = (
        CircleShape(1, 2, 3, DrawingAPI1()),
        CircleShape(5, 7, 11, DrawingAPI2())
    )
 
    for shape in shapes:
        shape.scale(2.5)
        shape.draw()
 
 
if __name__ == '__main__':
    main()

Composite(组合)

意图:

  • 将对象组合成树形结构以表示“部分-整体”的层次结构。C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性。

适用性:

  • 你想表示对象的部分-整体层次结构。
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
#!/usr/bin/python
#coding:utf8
 
"""
Composite
"""
 
class Component:
    def __init__(self,strName):
        self.m_strName = strName
    def Add(self,com):
        pass
    def Display(self,nDepth):
        pass
 
class Leaf(Component):
    def Add(self,com):
        print "leaf can't add"
    def Display(self,nDepth):
        strtemp = "-" * nDepth
        strtemp=strtemp+self.m_strName
        print strtemp
 
class Composite(Component):
    def __init__(self,strName):
        self.m_strName = strName
        self.c = []
    def Add(self,com):
        self.c.append(com)
    def Display(self,nDepth):
        strtemp = "-"*nDepth
        strtemp=strtemp+self.m_strName
        print strtemp
        for com in self.c:
            com.Display(nDepth+2)
 
if __name__ == "__main__":
    p = Composite("Wong")
    p.Add(Leaf("Lee"))
    p.Add(Leaf("Zhao"))
    p1 = Composite("Wu")
    p1.Add(Leaf("San"))
    p.Add(p1)
    p.Display(1);

Decorator(装饰)

意图:

  • 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。 适用性:
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理那些可以撤消的职责。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
#!/usr/bin/python
#coding:utf8
'''
Decorator
'''
 
class foo(object):
    def f1(self):
        print("original f1")
 
    def f2(self):
        print("original f2")
 
 
class foo_decorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee
 
    def f1(self):
        print("decorated f1")
        self._decoratee.f1()
 
    def __getattr__(self, name):
        return getattr(self._decoratee, name)
 
u = foo()
v = foo_decorator(u)
v.f1()
v.f2()

Facade(外观)

意图:

  • 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

适用性:

  • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。
#!/usr/bin/python
#coding:utf8
'''
Decorator
'''
import time
 
SLEEP = 0.5
 
# Complex Parts
class TC1:
    def run(self):
        print("###### In Test 1 ######")
        time.sleep(SLEEP)
        print("Setting up")
        time.sleep(SLEEP)
        print("Running test")
        time.sleep(SLEEP)
        print("Tearing down")
        time.sleep(SLEEP)
        print("Test Finished\n")
 
 
class TC2:
    def run(self):
        print("###### In Test 2 ######")
        time.sleep(SLEEP)
        print("Setting up")
        time.sleep(SLEEP)
        print("Running test")
        time.sleep(SLEEP)
        print("Tearing down")
        time.sleep(SLEEP)
        print("Test Finished\n")

class TC3:
    def run(self):
        print("###### In Test 3 ######")
        time.sleep(SLEEP)
        print("Setting up")
        time.sleep(SLEEP)
        print("Running test")
        time.sleep(SLEEP)
        print("Tearing down")
        time.sleep(SLEEP)
        print("Test Finished\n")

# Facade
class TestRunner:
    def __init__(self):
        self.tc1 = TC1()
        self.tc2 = TC2()
        self.tc3 = TC3()
        self.tests = [i for i in (self.tc1, self.tc2, self.tc3)]
 
    def runAll(self):
        [i.run() for i in self.tests]
 
# Client
if __name__ == '__main__':
    testrunner = TestRunner()
    testrunner.runAll()

Flyweight(享元)

意图:

  • 运用共享技术有效地支持大量细粒度的对象。

适用性:

  • 一个应用程序使用了大量的对象。
  • 完全由于使用大量的对象,造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
  • 应用程序不依赖于对象标识。由于Flyweight 对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。
#!/usr/bin/python
#coding:utf8
'''
Flyweight
'''
 
import weakref 
 
 
class Card(object):
    """The object pool. Has builtin reference counting"""
    _CardPool = weakref.WeakValueDictionary()
 
    """Flyweight implementation. If the object exists in the
    pool just return it (instead of creating a new one)"""
    def __new__(cls, value, suit):        
        obj = Card._CardPool.get(value + suit, None)        
        if not obj:            
            obj = object.__new__(cls)            
            Card._CardPool[value + suit] = obj            
            obj.value, obj.suit = value, suit         
        return obj
 
    # def __init__(self, value, suit):        
    #     self.value, self.suit = value, suit     
 
    def __repr__(self):        
        return "<Card: %s%s>" % (self.value, self.suit)     
 
 
if __name__ == '__main__':
    # comment __new__ and uncomment __init__ to see the difference
    c1 = Card('9', 'h')
    c2 = Card('9', 'h')
    print(c1, c2)
    print(c1 == c2)
    print(id(c1), id(c2))

Proxy(代理)

意图:

  • 为其他对象提供一种代理以控制对这个对象的访问。 适用性:
  • 在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。下面是一 些可以使用Proxy 模式常见情况:
    • 1) 远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。 NEXTSTEP[Add94] 使用NXProxy 类实现了这一目的。Coplien[Cop92] 称这种代理为“大使” (Ambassador )。
    • 2) 虚代理(Virtual Proxy )根据需要创建开销很大的对象。在动机一节描述的ImageProxy 就是这样一种代理的例子。
    • 3) 保护代理(Protection Proxy )控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候。例如,在Choices 操作系统[ CIRM93]中KemelProxies为操作系统对象提供 了访问保护。
    • 4) 智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。 它的典型用途包括:对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为SmartPointers[Ede92 ] )。
  • 当第一次引用一个持久对象时,将它装入内存。
  • 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
#!/usr/bin/python
#coding:utf8
'''
Proxy
'''
 
import time
 
class SalesManager:
    def work(self):
        print("Sales Manager working...")
 
    def talk(self):
        print("Sales Manager ready to talk")
 
class Proxy:
    def __init__(self):
        self.busy = 'No'
        self.sales = None
 
    def work(self):
        print("Proxy checking for Sales Manager availability")
        if self.busy == 'No':
            self.sales = SalesManager()
            time.sleep(2)
            self.sales.talk()
        else:
            time.sleep(2)
            print("Sales Manager is busy")

if __name__ == '__main__':
    p = Proxy()
    p.work()
    p.busy = 'Yes'
    p.work()

行为型

Interpreter(解释器)

意图:

  • 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

适用性:

  • 当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:
  • 该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式, 这样可以节省空间而且还可能节省时间。
  • 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的, 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。
#!/usr/bin/python
#coding:utf8
'''
Interpreter
'''
 
class Context:
    def __init__(self):
        self.input=""
        self.output=""
 
class AbstractExpression:
    def Interpret(self,context):
        pass
 
class Expression(AbstractExpression):
    def Interpret(self,context):
        print "terminal interpret"
 
class NonterminalExpression(AbstractExpression):
    def Interpret(self,context):
        print "Nonterminal interpret"
 
if __name__ == "__main__":
    context= ""
    c = []
    c = c + [Expression()]
    c = c + [NonterminalExpression()]
    c = c + [Expression()]
    c = c + [Expression()]
    for a in c:
        a.Interpret(context)

Template Method(模板方法)

意图:

  • 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

适用性:

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke 和Johnson所描述过的“重分解以一般化”的一个很好的例子[ OJ93 ]。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 控制子类扩展。模板方法只在特定点调用“hook ”操作(参见效果一节),这样就只允许在这些点进行扩展。
#!/usr/bin/python
#coding:utf8
'''
Template Method
'''
 
ingredients = "spam eggs apple"
line = '-' * 10
 
# Skeletons
def iter_elements(getter, action):    
    """Template skeleton that iterates items"""     
    for element in getter():        
        action(element)    
        print(line) 
 
def rev_elements(getter, action):
    """Template skeleton that iterates items in reverse order"""     
    for element in getter()[::-1]:        
        action(element)    
        print(line) 
 
# Getters
def get_list():    
    return ingredients.split() 
 
def get_lists():
    return [list(x) for x in ingredients.split()] 
 
# Actions
def print_item(item):    
    print(item) 
 
def reverse_item(item):
    print(item[::-1]) 
 
# Makes templates
def make_template(skeleton, getter, action):    
    """Instantiate a template method with getter and action"""    
    def template():        
        skeleton(getter, action)    
    return template 
 
# Create our template functions
templates = [make_template(s, g, a)             
             for g in (get_list, get_lists)             
             for a in (print_item, reverse_item)             
             for s in (iter_elements, rev_elements)] 
 
# Execute them
for template in templates:    
    template()

Chain of Responsibility(责任链)

意图:

  • 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

适用性:

  • 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
  • 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可处理一个请求的对象集合应被动态指定。
#!/usr/bin/python
#coding:utf8
 
"""
Chain
"""
class Handler:
    def successor(self, successor):
        self.successor = successor
 
class ConcreteHandler1(Handler):
    def handle(self, request):
        if request > 0 and request <= 10:
            print("in handler1")
        else:
            self.successor.handle(request)
 
class ConcreteHandler2(Handler):
    def handle(self, request):
        if request > 10 and request <= 20:
            print("in handler2")
        else:
            self.successor.handle(request)
 
class ConcreteHandler3(Handler):
    def handle(self, request):
        if request > 20 and request <= 30:
            print("in handler3")
        else:
            print('end of chain, no handler for {}'.format(request))
 
class Client:
    def __init__(self):
        h1 = ConcreteHandler1()
        h2 = ConcreteHandler2()
        h3 = ConcreteHandler3()
 
        h1.successor(h2)
        h2.successor(h3)
 
        requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
        for request in requests:
            h1.handle(request)
 
if __name__ == "__main__":
    client = Client()

Command(命令)

意图:

  • 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。

适用性:

  • 抽象出待执行的动作以参数化某对象,你可用过程语言中的回调(call back)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command 模式是回调机制的一个面向对象的替代品。
  • 在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
  • 支持取消操作。Command的Excute 操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command 接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。
  • 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
  • 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务( transaction)的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。
#!/usr/bin/python
#coding:utf8
 
"""
Command
"""
import os
 
class MoveFileCommand(object):
    def __init__(self, src, dest):
        self.src = src
        self.dest = dest
 
    def execute(self):
        self()
 
    def __call__(self):
        print('renaming {} to {}'.format(self.src, self.dest))
        os.rename(self.src, self.dest)
 
    def undo(self):
        print('renaming {} to {}'.format(self.dest, self.src))
        os.rename(self.dest, self.src)
 
 
if __name__ == "__main__":
    command_stack = []
 
    # commands are just pushed into the command stack
    command_stack.append(MoveFileCommand('foo.txt', 'bar.txt'))
    command_stack.append(MoveFileCommand('bar.txt', 'baz.txt'))
 
    # they can be executed later on
    for cmd in command_stack:
        cmd.execute()
 
    # and can also be undone at will
    for cmd in reversed(command_stack):
        cmd.undo()

Iterator(迭代器)

意图:

  • 提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。

适用性:

  • 访问一个聚合对象的内容而无需暴露它的内部表示。
  • 支持对聚合对象的多种遍历。
  • 为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。
#!/usr/bin/python
#coding:utf8
'''
Interator
'''
def count_to(count):
    """Counts by word numbers, up to a maximum of five"""
    numbers = ["one", "two", "three", "four", "five"]
    # enumerate() returns a tuple containing a count (from start which
    # defaults to 0) and the values obtained from iterating over sequence
    for pos, number in zip(range(count), numbers):
        yield number
 
# Test the generator
count_to_two = lambda: count_to(2)
count_to_five = lambda: count_to(5)
 
print('Counting to two...')
for number in count_to_two():
    print number
 
print " "
 
print('Counting to five...')
for number in count_to_five():
    print number
 
print " "

Mediator(中介者)

意图:

  • 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

适用性:

  • 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
  • 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
  • 想定制一个分布在多个类中的行为,而又不想生成太多的子类。
#!/usr/bin/python
#coding:utf8
'''
Mediator
'''
"""http://dpip.testingperspective.com/?p=28"""
 
import time
 
class TC:
    def __init__(self):
        self._tm = tm
        self._bProblem = 0
 
    def setup(self):
        print("Setting up the Test")
        time.sleep(1)
        self._tm.prepareReporting()
 
    def execute(self):
        if not self._bProblem:
            print("Executing the test")
            time.sleep(1)
        else:
            print("Problem in setup. Test not executed.")
 
    def tearDown(self):
        if not self._bProblem:
            print("Tearing down")
            time.sleep(1)
            self._tm.publishReport()
        else:
            print("Test not executed. No tear down required.")
 
    def setTM(self, TM):
        self._tm = tm
 
    def setProblem(self, value):
        self._bProblem = value
 
class Reporter:
    def __init__(self):
        self._tm = None
 
    def prepare(self):
        print("Reporter Class is preparing to report the results")
        time.sleep(1)
 
    def report(self):
        print("Reporting the results of Test")
        time.sleep(1)
 
    def setTM(self, TM):
        self._tm = tm
 
class DB:
    def __init__(self):
        self._tm = None
 
    def insert(self):
        print("Inserting the execution begin status in the Database")
        time.sleep(1)
        #Following code is to simulate a communication from DB to TC
        import random
        if random.randrange(1, 4) == 3:
            return -1
 
    def update(self):
        print("Updating the test results in Database")
        time.sleep(1)
 
    def setTM(self, TM):
        self._tm = tm
 
class TestManager:
    def __init__(self):
        self._reporter = None
        self._db = None
        self._tc = None
 
    def prepareReporting(self):
        rvalue = self._db.insert()
        if rvalue == -1:
            self._tc.setProblem(1)
            self._reporter.prepare()
 
    def setReporter(self, reporter):
        self._reporter = reporter
 
    def setDB(self, db):
        self._db = db
 
    def publishReport(self):
        self._db.update()
        rvalue = self._reporter.report()
 
    def setTC(self, tc):
        self._tc = tc
 
 
if __name__ == '__main__':
    reporter = Reporter()
    db = DB()
    tm = TestManager()
    tm.setReporter(reporter)
    tm.setDB(db)
    reporter.setTM(tm)
    db.setTM(tm)
    # For simplification we are looping on the same test.
    # Practically, it could be about various unique test classes and their
    # objects
    while (True):
        tc = TC()
        tc.setTM(tm)
        tm.setTC(tc)
        tc.setup()
        tc.execute()
        tc.tearDown()

Memento(备忘录)

意图:

  • 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

适用性:

  • 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
  • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
#!/usr/bin/python
#coding:utf8
'''
Memento
'''
 
import copy
 
def Memento(obj, deep=False):
    state = (copy.copy, copy.deepcopy)[bool(deep)](obj.__dict__)
 
    def Restore():
        obj.__dict__.clear()
        obj.__dict__.update(state)
    return Restore
 
class Transaction:
    """A transaction guard. This is really just
      syntactic suggar arount a memento closure.
      """
    deep = False
 
    def __init__(self, *targets):
        self.targets = targets
        self.Commit()
 
    def Commit(self):
        self.states = [Memento(target, self.deep) for target in self.targets]
 
    def Rollback(self):
        for st in self.states:
            st()
 
class transactional(object):
    """Adds transactional semantics to methods. Methods decorated  with
    @transactional will rollback to entry state upon exceptions.
    """
    def __init__(self, method):
        self.method = method
 
    def __get__(self, obj, T):
        def transaction(*args, **kwargs):
            state = Memento(obj)
            try:
                return self.method(obj, *args, **kwargs)
            except:
                state()
                raise
        return transaction
 
class NumObj(object):
    def __init__(self, value):
        self.value = value
 
    def __repr__(self):
        return '<%s: %r>' % (self.__class__.__name__, self.value)
 
    def Increment(self):
        self.value += 1
 
    @transactional
    def DoStuff(self):
        self.value = '1111'  # <- invalid value
        self.Increment()     # <- will fail and rollback
 
if __name__ == '__main__':
    n = NumObj(-1)
    print(n)
    t = Transaction(n)
    try:
        for i in range(3):
            n.Increment()
            print(n)
        t.Commit()
        print('-- commited')
        for i in range(3):
            n.Increment()
            print(n)
        n.value += 'x'  # will fail
        print(n)
    except:
        t.Rollback()
        print('-- rolled back')
    print(n)
    print('-- now doing stuff ...')
    try:
        n.DoStuff()
    except:
        print('-> doing stuff failed!')
        import traceback
        traceback.print_exc(0)
        pass
    print(n)

Observer(观察者)

意图:

  • 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。

适用性:

  • 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
#!/usr/bin/python
#coding:utf8
'''
Observer
'''
 
 
class Subject(object):
    def __init__(self):
        self._observers = []
 
    def attach(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)
 
    def detach(self, observer):
        try:
            self._observers.remove(observer)
        except ValueError:
            pass
 
    def notify(self, modifier=None):
        for observer in self._observers:
            if modifier != observer:
                observer.update(self)
 
# Example usage
class Data(Subject):
    def __init__(self, name=''):
        Subject.__init__(self)
        self.name = name
        self._data = 0
 
    @property
    def data(self):
        return self._data
 
    @data.setter
    def data(self, value):
        self._data = value
        self.notify()
 
class HexViewer:
    def update(self, subject):
        print('HexViewer: Subject %s has data 0x%x' %
              (subject.name, subject.data))
 
class DecimalViewer:
    def update(self, subject):
        print('DecimalViewer: Subject %s has data %d' %
              (subject.name, subject.data))
 
# Example usage...
def main():
    data1 = Data('Data 1')
    data2 = Data('Data 2')
    view1 = DecimalViewer()
    view2 = HexViewer()
    data1.attach(view1)
    data1.attach(view2)
    data2.attach(view2)
    data2.attach(view1)
 
    print("Setting Data 1 = 10")
    data1.data = 10
    print("Setting Data 2 = 15")
    data2.data = 15
    print("Setting Data 1 = 3")
    data1.data = 3
    print("Setting Data 2 = 5")
    data2.data = 5
    print("Detach HexViewer from data1 and data2.")
    data1.detach(view2)
    data2.detach(view2)
    print("Setting Data 1 = 10")
    data1.data = 10
    print("Setting Data 2 = 15")
    data2.data = 15
 
if __name__ == '__main__':
    main()

State(状态)

意图:

  • 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

适用性:

  • 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
  • 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常, 有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
#!/usr/bin/python
#coding:utf8
'''
State
'''
 
class State(object):
    """Base state. This is to share functionality"""
 
    def scan(self):
        """Scan the dial to the next station"""
        self.pos += 1
        if self.pos == len(self.stations):
            self.pos = 0
        print("Scanning... Station is", self.stations[self.pos], self.name)
 
 
class AmState(State):
    def __init__(self, radio):
        self.radio = radio
        self.stations = ["1250", "1380", "1510"]
        self.pos = 0
        self.name = "AM"
 
    def toggle_amfm(self):
        print("Switching to FM")
        self.radio.state = self.radio.fmstate
 
class FmState(State):
    def __init__(self, radio):
        self.radio = radio
        self.stations = ["81.3", "89.1", "103.9"]
        self.pos = 0
        self.name = "FM"
 
    def toggle_amfm(self):
        print("Switching to AM")
        self.radio.state = self.radio.amstate
 
class Radio(object):
    """A radio.     It has a scan button, and an AM/FM toggle switch."""
    def __init__(self):
        """We have an AM state and an FM state"""
        self.amstate = AmState(self)
        self.fmstate = FmState(self)
        self.state = self.amstate
 
    def toggle_amfm(self):
        self.state.toggle_amfm()
 
    def scan(self):
        self.state.scan()
 
# Test our radio out
if __name__ == '__main__':
    radio = Radio()
    actions = [radio.scan] * 2 + [radio.toggle_amfm] + [radio.scan] * 2
    actions = actions * 2
 
    for action in actions:
        action()

Strategy(策略)

意图:

  • 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

适用性:

  • 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
  • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时[H087] ,可以使用策略模式。
  • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
  • 一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
#!/usr/bin/python
#coding:utf8
"""
Strategy
In most of other languages Strategy pattern is implemented via creating some base strategy interface/abstract class and
subclassing it with a number of concrete strategies (as we can see at http://en.wikipedia.org/wiki/Strategy_pattern),
however Python supports higher-order functions and allows us to have only one class and inject functions into it's
instances, as shown in this example.
"""
import types
 
 
class StrategyExample:
    def __init__(self, func=None):
        self.name = 'Strategy Example 0'        
        if func is not None:
            self.execute = types.MethodType(func, self)     
 
    def execute(self):        
        print(self.name)  
 
def execute_replacement1(self):
    print(self.name + ' from execute 1')  
 
def execute_replacement2(self):
    print(self.name + ' from execute 2') 
 
if __name__ == '__main__':
    strat0 = StrategyExample()    
 
    strat1 = StrategyExample(execute_replacement1)
    strat1.name = 'Strategy Example 1'    
 
    strat2 = StrategyExample(execute_replacement2)
    strat2.name = 'Strategy Example 2'
 
    strat0.execute()
    strat1.execute()    
    strat2.execute()

Visitor(访问者)

意图:

  • 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

适用性:

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke和Johnson所描述过的“重分解以一般化”的一个很好的例子[OJ93]。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 控制子类扩展。模板方法只在特定点调用“hook ”操作(参见效果一节),这样就只允许在这些点进行扩展。
#!/usr/bin/python
#coding:utf8
'''
Visitor
'''
class Node(object):
    pass
 
class A(Node):
    pass
 
class B(Node):
    pass
 
class C(A, B):
    pass
 
class Visitor(object):
    def visit(self, node, *args, **kwargs):
        meth = None
        for cls in node.__class__.__mro__:
            meth_name = 'visit_'+cls.__name__
            meth = getattr(self, meth_name, None)
            if meth:
                break
 
        if not meth:
            meth = self.generic_visit
        return meth(node, *args, **kwargs)
 
    def generic_visit(self, node, *args, **kwargs):
        print('generic_visit '+node.__class__.__name__)
 
    def visit_B(self, node, *args, **kwargs):
        print('visit_B '+node.__class__.__name__)
 
a = A()
b = B()
c = C()
visitor = Visitor()
visitor.visit(a)
visitor.visit(b)
visitor.visit(c)

单例模式

【2021-7-14】单例设计模式的python实现

单例模式就是确保一个类只有一个实例.当你希望整个系统中,某个类只有一个实例时,单例模式就派上了用场.

  • 比如,某个服务器的配置信息存在在一个文件中,客户端通过AppConfig类来读取配置文件的信息.如果程序的运行的过程中,很多地方都会用到配置文件信息,则就需要创建很多的AppConfig实例,这样就导致内存中有很多AppConfig对象的实例,造成资源的浪费.其实这个时候AppConfig我们希望它只有一份,就可以使用单例模式.

模块实现

python的模块就是天然的单例模式,因为模块在第一次导入的时候,会生成.pyc文件,当第二次导入的时候,就会直接加载.pyc文件,而不是再次执行模块代码.如果我们把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了

新建一个python模块叫singleton, 文件名 mysingleton.py

class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()

调用时:

from singleton.mysingleton import singleton

装饰器实现

装饰器里面的外层变量定义一个字典,里面存放这个类的实例.当第一次创建的收,就将这个实例保存到这个字典中. 然后以后每次创建对象的时候,都去这个字典中判断一下,如果已经被实例化,就直接取这个实例对象.如果不存在就保存到字典中.

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 10:22'

def singleton(cls):
    # 单下划线的作用是这个变量只能在当前模块里访问,仅仅是一种提示作用
    # 创建一个字典用来保存类的实例对象
    _instance = {}

    def _singleton(*args, **kwargs):
        # 先判断这个类有没有对象
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)  # 创建一个对象,并保存到字典当中
        # 将实例对象返回
        return _instance[cls]

    return _singleton

@singleton
class A(object):
    a = 1

    def __init__(self, x=0):
        self.x = x
        print('这是A的类的初始化方法')

a1 = A(2)
a2 = A(3)
print(id(a1), id(a2))

类实现

调用类的instance方法,这样有一个弊端就是在使用类创建的时候,并不是单例了.也就是说在创建类的时候一定要用类里面规定的方法创建

注意:

  • 这样的单例模式在单线程下是安全的,但是如果遇到多线程,就会出现问题.如果遇到多个线程同时创建这个类的实例的时候就会出现问题.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:06'

class Singleton(object):
    def __init__(self,*args,**kwargs):
        pass

    @classmethod
    def get_instance(cls, *args, **kwargs):
        # 利用反射,看看这个类有没有_instance属性
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance

s1 = Singleton()  # 使用这种方式创建实例的时候,并不能保证单例
s2 = Singleton.get_instance()  # 只有使用这种方式创建的时候才可以实现单例
s3 = Singleton()
s4 = Singleton.get_instance()

print(id(s1), id(s2), id(s3), id(s4))

多线程安全版本

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:26'
import threading


class Singleton(object):
    def __init__(self, *args, **kwargs):
        time.sleep(1) # init时加阻塞,以便暴露问题
        pass

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance

def task(arg):
    obj = Singleton.get_instance(arg)
    print(obj)

for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()

结果创建了10个不同的实例对象,因为在一个对象创建的过程中,另外一个对象也创建了.当它判断的时候,会先去获取_instance属性,因为这个时候还没有,它就会调用init()方法.结果就是调用了10次,然后就创建了10个对象.

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:38'

import time
import threading

class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self,*args,**kwargs):
        time.sleep(1)

    @classmethod
    def get_instance(cls,*args,**kwargs):
        if not hasattr(Singleton,'_instance'):
            with Singleton._instance_lock:
                if not hasattr(Singleton,'_instance'):
                    Singleton._instance = Singleton(*args,**kwargs)

        return Singleton._instance

def task(arg):
    obj = Singleton.get_instance(arg)
    print(obj)

for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()

obj = Singleton.get_instance()
print(obj)

这种方式创建的单例,必须使用Singleton_get_instance()方法,如果使用Singleton()的话,得到的并不是单例.所以我们推荐使用__new__()方法来创建单例,这样创建的单例可以使用类名()的方法进行实例化对象

基于__new__方法实现的单例模式(推荐使用,方便)

知识点:

  • 一个对象的实例化过程是先执行类的__new__方法,如果没有写,默认会调用object的__new__方法,返回一个实例化对象,然后再调用__init__方法,对这个对象进行初始化,我们可以根据这个实现单例.
  • 在一个类的__new__方法中先判断是不是存在实例,如果存在实例,就直接返回,如果不存在实例就创建.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 13:36'
import threading


class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self, *args, **kwargs):
        pass

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            with Singleton._instance_lock:
                if not hasattr(cls, '_instance'):
                    Singleton._instance = super().__new__(cls)

            return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)

def task(arg):
    obj = Singleton()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()

Python GUI

常见GUI工具包:

  • PyQt 的介绍 : http://en.wikipedia.org/wiki/PyQt
  • Tkinter 的介绍 : http://en.wikipedia.org/wiki/Tkinter
  • wxPython 的介绍 : http://en.wikipedia.org/wiki/WxPython
  • PyGTK 的介绍 : http://en.wikipedia.org/wiki/PyGTK
  • PySide 的介绍 : http://en.wikipedia.org/wiki/PySide

  • 推荐8款常用的Python GUI图形界面开发框架

总结

Python下各种GUI简介、使用优缺点对比

GUI编程对比 简介特点 优缺点
PyQt Python 对跨平台的 GUI 工具集 Qt 的包装实现了 440 个类以及 6000 个函数或者方法 ,PyQt 是作为 Python 的插件实现的。 比较流行的一个 Tkinter 的替代品,功能 非常强大,可以用Qt开发多美漂亮的界面,也就可以用PyQt开发多么漂亮的界面。 跨平台的支持很好,不过在商业授权上似乎存在一些问题。
Tkinter 绑定了 Python 的 Tk GUI 工具集 ,就是Python 包装的Tcl代码,通过内嵌在 Python 解释器内部的 Tcl 解释器实现, Tkinter的调用转换成 Tcl 命令,然后交给 Tcl 解释器进行解释,实现 Python 的 GUI 界面。 对比Tk和其它语言的绑定,比如 PerlTk ,是直接由 Tk 中的 C 库实现的。历史最悠久, Python 事实上的标准 GUI , Python 中使用 Tk GUI 工具集的标准接口,已经包括在标准的 Python Windows 安装中,著名的 IDLE 就是使用 Tkinter 实现 GUI 的创建的 GUI 简单,学起来和用起来也简单。
wxPython Python 对跨平台的 GUI 工具集 wxWidgets ( C++ 编写)的包装,作为 Python 的一个 扩展模块实现。 比较流行的一个 Tkinter 的替代品,在 各种平台下都表现挺好。
PyGTK 一系列的 Python 对 GTK+ GUI 库的包装。 比较流行的一个 Tkinter 的替代品,许多 Gnome 下的著名应用程序的 GUI 都是使用 PyGTK 实现的,比如 BitTorrent , GIMP和 Gedit 都有可选的实现,在 Windows 平台 似乎表现不太好,这点也无可厚非,毕竟使用的是GTK 的 GUI 库。
PySide 另一个 Python 对跨平台的 GUI 工具集 Qt 的包装,捆绑在 Python 当中,最初由 BoostC++ 库实现,后来迁移到 Shiboken。 比较流行的一个 Tkinter 的替代品,和上 面类似,至于两者的区别,这里 有一个介绍。

基本概念

概念:窗口和控件、事件驱动处理、布局管理器。

  • 窗体控件: 窗体、标签、按钮、列表框、滚动条等。
  • 事件驱动:按下按钮及释放、鼠标移动、按回车键等。
  • 布局管理:Tk有3种布局管理器:Placer、Packer、Grid

Tkinter

Tkinter(也叫Tk接口)是Tk图形用户界面工具包标准的Python接口。Tk是一个轻量级的跨平台图形用户界面(GUI)开发工具。Tk和Tkinter可以运行在大多数的Unix平台、Windows、和Macintosh系统。

Tkinter 由一定数量的模块组成。Tkinter位于一个名为_tkinter(较早的版本名为tkinter)的二进制模块中 。Tkinter包含了对Tk的低 级接口模块,低级接口并不会被应用级程序员直接使用,通常是一个共享库(或DLL),但是在一些情况下它也被Python解释器静态链接。

tkinter提供各种控件,如按钮、标签和文本框等,在一个GUI应用程序中使用。这些控件有时也被称为部件。目前有19种tkinter的控件。

语法

from tkinter import *

win = Tk() # 生成根窗体
win.title("Python GUI") # 标题

def _quit():
    win.quit()
    win.destroy()
    exit()
    
# 创建菜单栏功能
menuBar = Menu(win) 
win.config(menu = menuBar)

font = ("黑体", 10, "bold")

fileMenu = Menu(menuBar, tearoff=0, font=font, bg="white", fg="black")
menuBar.add_cascade(label="File", menu=fileMenu)
fileMenu.add_command(labe="New File") 
fileMenu.add_separator()
fileMenu.add_command(labe="Exit",command=_quit) 

helpMenu = Menu(menuBar, tearoff=0, font=font, bg="white", fg="black")
menuBar.add_cascade(label="Help", menu=helpMenu)
helpMenu.add_command(labe="About") 

check_music = IntVar()
check_video = IntVar()

# ---- 准备各类组件 -----
label = Label(win, text = "hello tkinter") # 文字
button = Button(win ,text="第一个按钮") # 按钮
#   Checkbutton控件
check_m = Checkbutton(win,text="Music", variable = check_music, onvalue =1, offvalue =0)
check_v = Checkbutton(win,text="Video", variable = check_video, onvalue =1, offvalue =0)
#   文本控件
text = Text(win,height=5, width=30, font=font, bg="white", fg="black")
text.insert(INSERT,"Hello GUI,")
text.insert(END, "Bye!")

# ---- 组件添加到窗体中 -----
label.pack()
button.pack()
check_m.pack()
check_v.pack()
text.pack()

win .mainloop() # 根窗体(活跃状态?)

案例

制作TCP通信的Server 和 Client

  • 服务端
  • 客户端
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 26 15:34:10 2020

@author: sinlearn
"""
import tkinter as tk
import tkinter.ttk as ttk
import socket
import threading
import time

class TCP_Server():
    # 服务端
    def __init__(self):
        winserver = tk.Tk()
        winserver.title("TCP Server")
        winserver.geometry("500x500")
        winserver.resizable(width=False, height=False)
        font = ("宋体", 10)

        self.rbtn = tk.Radiobutton(winserver, text="未连接", fg="red")
        self.label_port = tk.Label(winserver, text=" 端口:", font=font)
        self.label_send = tk.Label(winserver, text=" 发送区:", font=font)
        self.label_recv = tk.Label(winserver, text=" 接收区:", font=font)
        self.label_clist = tk.Label(winserver, text=" 客户端列表:", font=font)
        self.spinbox_port = tk.Spinbox(winserver, from_=1024, to=10000)
        self.btn_start = tk.Button(winserver, text="启动", bg="white", command=self.do_start)
        self.btn_stop = tk.Button(winserver, text="停止", bg="white", command=self.do_stop)
        self.btn_send = tk.Button(winserver, text="发送", bg="white", command=self.send_to_client)
        self.en_send = tk.Entry(winserver, text="Test", bd=2)
        self.text_recv = tk.Text(winserver, height=5, width=43, font=font, bg="white", fg="black")
        self.client_list = ttk.Treeview(winserver, height=10, show="headings",
                                        columns=('col1', 'col2', 'col3'))  # show = "headings" 隐藏默认的col0列
        self.client_list.column('col1', width=50, anchor='center')
        self.client_list.column('col2', width=200, anchor='center')
        self.client_list.column('col3', width=100, anchor='center')
        self.client_list.heading('col1', text='序号')
        self.client_list.heading('col2', text='IP地址')
        self.client_list.heading('col3', text='端口号')

        self.rbtn.place(x=10, y=10)
        self.label_port.place(x=100, y=15)
        self.label_send.place(x=100, y=50)
        self.label_recv.place(x=100, y=140)
        self.spinbox_port.place(x=150, y=15)
        self.btn_start.place(x=400, y=10)
        self.btn_stop.place(x=440, y=10)
        self.btn_send.place(x=440, y=70)
        self.en_send.place(x=120, y=70, width=300, height=60)
        self.text_recv.place(x=120, y=160)
        self.label_clist.place(x=100, y=240)
        self.client_list.place(x=120, y=260)

        for i in range(10):
            self.client_list.insert("", i, values=(i, "192.168.2.3" + str(i), "9999"))  # 插入数据
            self.client_list.bind("<Double-1>", self.onDBClick)
        winserver.mainloop()

    def onDBClick(self, event):
        item = self.client_list.selection()[0]
        print("you clicked on ", self.client_list.item(item, "values"))

    def do_start(self):
        self.rbtn["fg"] = "green"
        self.rbtn["text"] = "已连接"

    def do_stop(self):
        print("正在断开连接....")
        self.rbtn["fg"] = "red"
        self.rbtn["text"] = "未连接"

    def send_to_client(self):
        if self.rbtn["text"] == "已连接":
            print("正在发送数据....")
        else:
            print("连接未建立,不能发送数据....")

    def tcp_link(self, sock, addr):
        print(f" {addr} 正在请求连接........")
        sock.send("欢迎您连接到服务器........".encode('utf-8'))
        while True:
            data = sock.recv(1024)
            time.sleep(1)
            if data and data.decode('utf-8') != "exit":
                print(data.decode('utf-8'))
                self.text_recv["text"] = data.decode('utf-8')
                sock.send("服务器正在接收数据,请稍等........".encode('utf-8'))
            else:
                break


class TCP_Client():
    def __init__(self):
        self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        winclient = tk.Tk()
        winclient.title("TCP Client")
        winclient.geometry("600x250")
        winclient.resizable(width=False, height=False)
        font = ("宋体", 10)

        self.rbtn = tk.Radiobutton(winclient, text="未连接", fg="red")
        self.label_ip = tk.Label(winclient, text=" IP地址:", font=font)
        self.label_port = tk.Label(winclient, text=" 端口:", font=font)
        self.label_send = tk.Label(winclient, text=" 发送区:", font=font)
        self.label_recv = tk.Label(winclient, text=" 接收区:", font=font)
        self.spinbox_port = tk.Spinbox(winclient, from_=1024, to=10000)
        self.btn_start = tk.Button(winclient, text="连接", bg="white", command=self.do_connect)
        self.btn_stop = tk.Button(winclient, text="断开", bg="white", command=self.do_stopconnect)
        self.btn_send = tk.Button(winclient, text="发送", bg="white", command=self.send_to_server)
        self.en_ip = tk.Entry(winclient, text="IP地址", bd=2)
        self.en_send = tk.Entry(winclient, text="Test", bd=2)
        self.text_recv = tk.Text(winclient, height=5, width=43, font=font, bg="white", fg="black")

        self.label_ip.place(x=100, y=15)
        self.label_port.place(x=360, y=15)
        self.label_send.place(x=100, y=50)
        self.label_recv.place(x=100, y=150)
        self.rbtn.place(x=10, y=10)
        self.btn_start.place(x=480, y=10)
        self.btn_stop.place(x=520, y=10)
        self.btn_send.place(x=480, y=70)
        self.en_ip.place(x=160, y=15, width=200, height=20)
        self.spinbox_port.place(x=410, y=15, width=50, height=20)
        self.en_send.place(x=120, y=70, width=300, height=60)
        self.text_recv.place(x=120, y=170)
        winclient.mainloop()

    def do_connect(self):
        print("正在连接服务器....")
        self.rbtn["fg"] = "green"
        self.rbtn["text"] = "已连接"
       
    def do_stopconnect(self):
        self.rbtn["fg"] = "red"
        self.rbtn["text"] = "未连接"
       

    def send_to_server(self):
        print("正在往服务器发送数据.....")
        
if __name__ == "__main__":
    TCP_Server()
    TCP_Client()

PyQT

pyqt5是一套Python绑定Digia QT5应用的框架。它可用于Python 2和3。Qt库是最强大的GUI库之一。

pyqt5的类别分为几个模块,包括以下:

  • QtCore: 包含了核心的非GUI功能。此模块用于处理时间、文件和目录、各种数据类型、流、URL、MIME类型、线程或进程。
  • QtGui: 包含类窗口系统集成、事件处理、二维图形、基本成像、字体和文本。
  • qtwidgets: 包含创造经典桌面风格的用户界面提供了一套UI元素的类。
  • QtMultimedia: 包含的类来处理多媒体内容和API来访问相机和收音机的功能。
  • Qtbluetooth: 包含类的扫描设备和连接并与他们互动。描述模块包含了网络编程的类。
  • Qtpositioning: 包含类的利用各种可能的来源,确定位置,包括卫星、Wi-Fi、或一个文本文件。
  • Enginio: 实现了客户端库访问Qt云服务托管的应用程序运行时。
  • Qtwebsockets: 包含实现WebSocket协议类。
  • QtWebKit: 包含一个基于Webkit2图书馆Web浏览器实现类。
  • Qtwebkitwidgets: 的类的基础webkit1一用于qtwidgets应用Web浏览器的实现。
  • QtXml: 包含与XML文件的类。这个模块为SAX和DOM API提供了实现。
  • QtSvg: 提供了显示SVG文件内容的类。可伸缩矢量图形(SVG)是一种描述二维图形和图形应用的语言。
  • QtSql: 提供操作数据库的类。
  • QtTest: 包含的功能,使pyqt5应用程序的单元测试

安装

pip install pyqt5
brew install pyqt # mac下专用

代码

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
Py40 PyQt5 tutorial 

This example shows a tooltip on 
a window and a button.

author: Jan Bodnar
website: py40.com 
last edited: January 2015
"""

import sys
from PyQt5.QtWidgets import (QWidget, QToolTip, 
    QPushButton, QApplication)
from PyQt5.QtGui import QFont    


class Example(QWidget):
    
    def __init__(self):
        super().__init__()
        
        self.initUI()
        
        
    def initUI(self):
        #这种静态的方法设置一个用于显示工具提示的字体。我们使用10px滑体字体。
        QToolTip.setFont(QFont('SansSerif', 10))
        
        #创建一个提示,我们称之为settooltip()方法。我们可以使用丰富的文本格式
        self.setToolTip('This is a <b>QWidget</b> widget')
        
        #创建一个PushButton并为他设置一个tooltip
        btn = QPushButton('Button', self)
        btn.setToolTip('This is a <b>QPushButton</b> widget')
        
        #btn.sizeHint()显示默认尺寸
        btn.resize(btn.sizeHint())
        
        #移动窗口的位置
        btn.move(50, 50)       
        
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Tooltips')    
        self.show()
        
        
if __name__ == '__main__':
    
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

俄罗斯方块游戏

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
ZetCode PyQt5 tutorial 

This is a Tetris game clone.

Author: Jan Bodnar
Website: zetcode.com 
Last edited: August 2017
"""

from PyQt5.QtWidgets import QMainWindow, QFrame, QDesktopWidget, QApplication
from PyQt5.QtCore import Qt, QBasicTimer, pyqtSignal
from PyQt5.QtGui import QPainter, QColor 
import sys, random

class Tetris(QMainWindow):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):    
        '''initiates application UI'''

        self.tboard = Board(self)
        self.setCentralWidget(self.tboard)

        self.statusbar = self.statusBar()        
        self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)

        self.tboard.start()

        self.resize(180, 380)
        self.center()
        self.setWindowTitle('Tetris')        
        self.show()


    def center(self):
        '''centers the window on the screen'''

        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2, 
            (screen.height()-size.height())/2)


class Board(QFrame):

    msg2Statusbar = pyqtSignal(str)

    BoardWidth = 10
    BoardHeight = 22
    Speed = 300

    def __init__(self, parent):
        super().__init__(parent)

        self.initBoard()


    def initBoard(self):     
        '''initiates board'''

        self.timer = QBasicTimer()
        self.isWaitingAfterLine = False

        self.curX = 0
        self.curY = 0
        self.numLinesRemoved = 0
        self.board = []

        self.setFocusPolicy(Qt.StrongFocus)
        self.isStarted = False
        self.isPaused = False
        self.clearBoard()


    def shapeAt(self, x, y):
        '''determines shape at the board position'''

        return self.board[(y * Board.BoardWidth) + x]


    def setShapeAt(self, x, y, shape):
        '''sets a shape at the board'''

        self.board[(y * Board.BoardWidth) + x] = shape


    def squareWidth(self):
        '''returns the width of one square'''

        return self.contentsRect().width() // Board.BoardWidth


    def squareHeight(self):
        '''returns the height of one square'''

        return self.contentsRect().height() // Board.BoardHeight


    def start(self):
        '''starts game'''

        if self.isPaused:
            return

        self.isStarted = True
        self.isWaitingAfterLine = False
        self.numLinesRemoved = 0
        self.clearBoard()

        self.msg2Statusbar.emit(str(self.numLinesRemoved))

        self.newPiece()
        self.timer.start(Board.Speed, self)


    def pause(self):
        '''pauses game'''

        if not self.isStarted:
            return

        self.isPaused = not self.isPaused

        if self.isPaused:
            self.timer.stop()
            self.msg2Statusbar.emit("paused")

        else:
            self.timer.start(Board.Speed, self)
            self.msg2Statusbar.emit(str(self.numLinesRemoved))

        self.update()


    def paintEvent(self, event):
        '''paints all shapes of the game'''

        painter = QPainter(self)
        rect = self.contentsRect()

        boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()

        for i in range(Board.BoardHeight):
            for j in range(Board.BoardWidth):
                shape = self.shapeAt(j, Board.BoardHeight - i - 1)

                if shape != Tetrominoe.NoShape:
                    self.drawSquare(painter,
                        rect.left() + j * self.squareWidth(),
                        boardTop + i * self.squareHeight(), shape)

        if self.curPiece.shape() != Tetrominoe.NoShape:

            for i in range(4):

                x = self.curX + self.curPiece.x(i)
                y = self.curY - self.curPiece.y(i)
                self.drawSquare(painter, rect.left() + x * self.squareWidth(),
                    boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
                    self.curPiece.shape())


    def keyPressEvent(self, event):
        '''processes key press events'''

        if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
            super(Board, self).keyPressEvent(event)
            return

        key = event.key()

        if key == Qt.Key_P:
            self.pause()
            return

        if self.isPaused:
            return

        elif key == Qt.Key_Left:
            self.tryMove(self.curPiece, self.curX - 1, self.curY)

        elif key == Qt.Key_Right:
            self.tryMove(self.curPiece, self.curX + 1, self.curY)

        elif key == Qt.Key_Down:
            self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)

        elif key == Qt.Key_Up:
            self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)

        elif key == Qt.Key_Space:
            self.dropDown()

        elif key == Qt.Key_D:
            self.oneLineDown()

        else:
            super(Board, self).keyPressEvent(event)


    def timerEvent(self, event):
        '''handles timer event'''

        if event.timerId() == self.timer.timerId():

            if self.isWaitingAfterLine:
                self.isWaitingAfterLine = False
                self.newPiece()
            else:
                self.oneLineDown()

        else:
            super(Board, self).timerEvent(event)


    def clearBoard(self):
        '''clears shapes from the board'''

        for i in range(Board.BoardHeight * Board.BoardWidth):
            self.board.append(Tetrominoe.NoShape)


    def dropDown(self):
        '''drops down a shape'''

        newY = self.curY

        while newY > 0:

            if not self.tryMove(self.curPiece, self.curX, newY - 1):
                break

            newY -= 1

        self.pieceDropped()


    def oneLineDown(self):
        '''goes one line down with a shape'''

        if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
            self.pieceDropped()


    def pieceDropped(self):
        '''after dropping shape, remove full lines and create new shape'''

        for i in range(4):

            x = self.curX + self.curPiece.x(i)
            y = self.curY - self.curPiece.y(i)
            self.setShapeAt(x, y, self.curPiece.shape())

        self.removeFullLines()

        if not self.isWaitingAfterLine:
            self.newPiece()


    def removeFullLines(self):
        '''removes all full lines from the board'''

        numFullLines = 0
        rowsToRemove = []

        for i in range(Board.BoardHeight):

            n = 0
            for j in range(Board.BoardWidth):
                if not self.shapeAt(j, i) == Tetrominoe.NoShape:
                    n = n + 1

            if n == 10:
                rowsToRemove.append(i)

        rowsToRemove.reverse()


        for m in rowsToRemove:

            for k in range(m, Board.BoardHeight):
                for l in range(Board.BoardWidth):
                        self.setShapeAt(l, k, self.shapeAt(l, k + 1))

        numFullLines = numFullLines + len(rowsToRemove)

        if numFullLines > 0:

            self.numLinesRemoved = self.numLinesRemoved + numFullLines
            self.msg2Statusbar.emit(str(self.numLinesRemoved))

            self.isWaitingAfterLine = True
            self.curPiece.setShape(Tetrominoe.NoShape)
            self.update()


    def newPiece(self):
        '''creates a new shape'''

        self.curPiece = Shape()
        self.curPiece.setRandomShape()
        self.curX = Board.BoardWidth // 2 + 1
        self.curY = Board.BoardHeight - 1 + self.curPiece.minY()

        if not self.tryMove(self.curPiece, self.curX, self.curY):

            self.curPiece.setShape(Tetrominoe.NoShape)
            self.timer.stop()
            self.isStarted = False
            self.msg2Statusbar.emit("Game over")



    def tryMove(self, newPiece, newX, newY):
        '''tries to move a shape'''

        for i in range(4):

            x = newX + newPiece.x(i)
            y = newY - newPiece.y(i)

            if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
                return False

            if self.shapeAt(x, y) != Tetrominoe.NoShape:
                return False

        self.curPiece = newPiece
        self.curX = newX
        self.curY = newY
        self.update()

        return True


    def drawSquare(self, painter, x, y, shape):
        '''draws a square of a shape'''        

        colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
                      0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]

        color = QColor(colorTable[shape])
        painter.fillRect(x + 1, y + 1, self.squareWidth() - 2, 
            self.squareHeight() - 2, color)

        painter.setPen(color.lighter())
        painter.drawLine(x, y + self.squareHeight() - 1, x, y)
        painter.drawLine(x, y, x + self.squareWidth() - 1, y)

        painter.setPen(color.darker())
        painter.drawLine(x + 1, y + self.squareHeight() - 1,
            x + self.squareWidth() - 1, y + self.squareHeight() - 1)
        painter.drawLine(x + self.squareWidth() - 1, 
            y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)


class Tetrominoe(object):

    NoShape = 0
    ZShape = 1
    SShape = 2
    LineShape = 3
    TShape = 4
    SquareShape = 5
    LShape = 6
    MirroredLShape = 7


class Shape(object):

    coordsTable = (
        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
        ((0, -1),    (0, 0),     (1, 0),     (1, 1)),
        ((0, -1),    (0, 0),     (0, 1),     (0, 2)),
        ((-1, 0),    (0, 0),     (1, 0),     (0, 1)),
        ((0, 0),     (1, 0),     (0, 1),     (1, 1)),
        ((-1, -1),   (0, -1),    (0, 0),     (0, 1)),
        ((1, -1),    (0, -1),    (0, 0),     (0, 1))
    )

    def __init__(self):

        self.coords = [[0,0] for i in range(4)]
        self.pieceShape = Tetrominoe.NoShape

        self.setShape(Tetrominoe.NoShape)


    def shape(self):
        '''returns shape'''

        return self.pieceShape


    def setShape(self, shape):
        '''sets a shape'''

        table = Shape.coordsTable[shape]

        for i in range(4):
            for j in range(2):
                self.coords[i][j] = table[i][j]

        self.pieceShape = shape


    def setRandomShape(self):
        '''chooses a random shape'''

        self.setShape(random.randint(1, 7))


    def x(self, index):
        '''returns x coordinate'''

        return self.coords[index][0]


    def y(self, index):
        '''returns y coordinate'''

        return self.coords[index][1]


    def setX(self, index, x):
        '''sets x coordinate'''

        self.coords[index][0] = x


    def setY(self, index, y):
        '''sets y coordinate'''

        self.coords[index][1] = y


    def minX(self):
        '''returns min x value'''

        m = self.coords[0][0]
        for i in range(4):
            m = min(m, self.coords[i][0])

        return m


    def maxX(self):
        '''returns max x value'''

        m = self.coords[0][0]
        for i in range(4):
            m = max(m, self.coords[i][0])

        return m


    def minY(self):
        '''returns min y value'''

        m = self.coords[0][1]
        for i in range(4):
            m = min(m, self.coords[i][1])

        return m


    def maxY(self):
        '''returns max y value'''

        m = self.coords[0][1]
        for i in range(4):
            m = max(m, self.coords[i][1])

        return m


    def rotateLeft(self):
        '''rotates shape to the left'''

        if self.pieceShape == Tetrominoe.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape

        for i in range(4):

            result.setX(i, self.y(i))
            result.setY(i, -self.x(i))

        return result


    def rotateRight(self):
        '''rotates shape to the right'''

        if self.pieceShape == Tetrominoe.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape

        for i in range(4):

            result.setX(i, -self.y(i))
            result.setY(i, self.x(i))

        return result


if __name__ == '__main__':

    app = QApplication([])
    tetris = Tetris()    
    sys.exit(app.exec_())

wxPython

wxPython 是 Python 语言的一套优秀的 GUI 图形库,允许 Python 程序员很方便的创建完整的、功能键全的 GUI 用户界面。 wxPython 是作为优秀的跨平台 GUI 库 wxWidgets 的 Python 封装和 Python 模块的方式提供给用户的。

就如同Python和wxWidgets一样,wxPython也是一款开源软件,并且具有非常优秀的跨平台能力,能够运行在32位windows、绝大多数的Unix或类Unix系统、Macintosh OS X上。

PyGTK

PyGTK让你用Python轻松创建具有图形用户界面的程序.底层的GTK+提供了各式的可视元素和功能,如果需要,你能开发在GNOME桌面系统运行的功能完整的软件.

PyGTK真正具有跨平台性,它能不加修改地,稳定运行各种操作系统之上,如Linux,Windows,MacOS等.除了简单易用和快速的原型开发能力外,PyGTK还有一流的处理本地化语言的独特功能.

模型快速部署

Pynecone

【2022-12-15】GitHub 上的开源 Python 全栈开发框架:Pynecone

hugginface space

【2022-10-18】huggingface(抱抱脸)支持快速部署模型demo,兼容 gradio、streamlit、静态html页面

示例1示例2

  • 在 huggingface 上创建 space;
    • 如:创建gradio demo
  • 添加代码到 app.py 文件中,提交
  • 等待几分钟,再次点击即可看到部署效果

gradio

Gradio 是一个开源的 Python 库,用于构建机器学习和数据科学演示和 Web 应用。github

  • Gradio is the fastest way to demo your machine learning model with a friendly web interface so that anyone can use it, anywhere!
pip install -q gradio

功能

  • Fast, easy setup 简单迅速
    • Gradio can be installed with pip. Creating a Gradio interface only requires adding a couple lines of code to your project.
    • You can choose from a variety of interface types to interface your function.
  • Present and share 便捷分享
    • Gradio can be embedded in Python notebooks or presented as a webpage.
    • A Gradio interface can automatically generate a public link (生成临时链接,72h) you can share with colleagues that lets them interact with the model on your computer remotely from their own devices.
  • Permanent hosting 永久部署
    • Once you’ve created an interface, you can permanently host it on Hugging Face.
    • Hugging Face Spaces will host the interface on its servers and provide you with a link you can share.

gradio 组件

gradio 核心是它的 gr.Interface 函数,用来构建可视化界面。

  • fn:处理函数
  • inputs:输入类型,这里输入的是图像,所以是”image”
  • outputs:输出类型,这里输出的是图像,所以是”image”
import gradio as gr
import cv2

def to_black(image):
    output = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    return output

interface = gr.Interface(fn=to_black, inputs="image", outputs="image")
# 增加示例
interface = gr.Interface(fn=to_black, inputs="image", outputs="image", examples=[["test.png"]])
# 启动
interface.launch()

用interface.lauch()把页面一发布,一个本地静态交互页面就完成了

launch 功能

  • share: 创建外部访问链接非常简单,只需要launch(share=True)即可,在打印信息中会看到你的外部访问链接。
    • 免费用户的链接可以使用24小时,想要长期的话需要在gradio官方购买云服务
  • server_port: 端口
# share 生成外部链接
# server_name 服务器ip
# server_port 服务器端口
iface.launch(debug=True, enable_queue=True, share=False, server_name="0.0.0.0", server_port=7860)  # 写好对象直接launch就行

【2023-4-20】基于Gradio可视化部署机器学习应用

  • Interface 类:高层API,只需提供输入输出,就可以快速构建纯机器学习应用
    • Interface 高效但不灵活
  • Blocks 类:低级别API,用于设计布局、数据流更灵活的网络应用。
    • Blocks 允许控制组件在页面上出现的位置,处理复杂的数据流(例如,输出可以作为其他函数的输入),多步应用,并根据用户交互更新组件的属性可见性。
    • 如:创建多个Tab页、定制输入输出组件布局多步交互(一个模型输出作为另一个模型的输入)、根据用户输入更改组件属性

官方提供的几个示例

  • Sketch Recognition 你画我猜
  • Question Answering 简易文档QA
  • Image Segmentation 图像分割
  • Time Series Forecasting 时间序列预测
  • XGBoost with Explainability 带解释能力的XGB预测

Bug

【2023-4-21】

Invalid port

  • 升级httpx
pip install httpx==0.23.3

OSError: [Errno 98] Address already in use

  • 端口被占用,换端口,或关闭已有服务
# 关闭已有gradio端口
gr.close_all()

Interface 高级页面

简易文本示例

import gradio as gr

def greet(name):
    return "Hello " + name + "!"

demo = gr.Interface(fn=greet, inputs="text", outputs="text") # 文本组件
demo = gr.Interface(sepia, gr.Image(shape=(200, 200)), "image") # 图像组件
# 启动服务
demo.launch()
# 密码验证
demo.launch(auth=("admin", "pass1234"))
# 更复杂:账户和密码相同就可以通过
def same_auth(username, password):
    return username == password
demo.launch(auth=same_auth,auth_message="username and password must be the same")
# 分享
demo.launch(share=True, auth=("xfl","xfl"),inline=False, server_port=9999)
# show_error为True表示在控制台显示错误信息。
demo.launch(server_name='0.0.0.0', server_port=8080, show_error=True)

复杂文本示例:document QA

import gradio as gr

def question_answer(context, question):
    pass  # Implement your question-answering model here...
    return ('答案返回', 'hello')

gr.Interface(fn=question_answer, 
    # ----- 输入 ------
    inputs=["text", "text"], 
    # 自定义 input
    inputs=gr.Textbox(lines=3, placeholder="占位符",label="名称"),
    # ----- 输出 ------
    outputs=["textbox", "text"],
    # outputs=gr.HighlightedText(label="分析结果:", show_legend=True, combine_adjacent=True),  # 输出结果以什么样的形式展示,随意替换
    # 自定义输出名称
    outputs=[ gr.outputs.Textbox(label="抽取式QA"),
       gr.outputs.Textbox(label="小区测评")]
    )
    # ---- 多输入、多输出 ----- 对应函数 question_answer 的输入、输出
    # 按照处理程序设置输入组件
    inputs=["text", "checkbox", gr.Slider(0, 100)],
    # 按照处理程序设置输出组件
    outputs=["text", "number"],
    # 示例
	examples=[['该楼盘的住宅户型60平到239平价格900万到4000万,产品是开间,一居,二居,三居,户型总共17种。园区整体楼间距可达约230米-260米。精装交付。园区整体楼间距可达约230米-260米。项目共有1类产品类型分别为:平层。','该楼盘的装修交付标准是什么?']]
	).launch()
	#).launch(share=True)

有了 Gradio,可以围绕机器学习模型或数据科学工作流程快速创建一个漂亮的用户界面,让人们通过拖放自己的图片、粘贴文本、录制自己的声音来 “试用”,并通过浏览器与你的演示进行互动。

import gradio as gr
import cv2

def to_black(image):
    output = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    return output

# 支持裁剪、拖拽
interface = gr.Interface(fn=to_black, inputs="image", outputs="image")
# 注意:图片目录在当前文件录下下,否则无法识别
interface = gr.Interface(fn=to_black, inputs="image", outputs="image", examples=[["test.png"]]) # 增加图片示例
# 创建公共链接
interface = gr.Interface(fn=to_black, inputs="image", outputs="image", examples=[["test.png"]], share=True) # 链接
interface.launch()

gradio的核心是它的 gr.Interface 函数,用来构建可视化界面。

  • fn:放你用来处理的函数
  • inputs:写你的输入类型,这里输入的是图像,所以是”image”
  • outputs:写你的输出类型,这里输出的是图像,所以是”image”

最后用interface.lauch()把页面一发布,一个本地静态交互页面就完成了!

  • 在浏览器输入 http://127.0.0.1:7860/, 查收页面

自定义UI

import gradio as gr

def sentence_builder(quantity, animal, place, activity_list, morning):
    return f"""The {quantity} {animal}s went to the {place} where they {" and ".join(activity_list)} until the {"morning" if morning else "night"}"""

iface = gr.Interface(
    sentence_builder,
    [
        gr.inputs.Slider(2, 20),
        gr.inputs.Dropdown(["cat", "dog", "bird"]),
        gr.inputs.Radio(["park", "zoo", "road"]),
        gr.inputs.CheckboxGroup(["ran", "swam", "ate", "slept"]),
        gr.inputs.Checkbox(label="Is it the morning?"),
    ],
    "text",
    examples=[
        [2, "cat", "park", ["ran", "swam"], True],
        [4, "dog", "zoo", ["ate", "swam"], False],
        [10, "bird", "road", ["ran"], False],
        [8, "cat", "zoo", ["ate"], True],
    ],
)

iface.launch()

【2023-3-13】示例

import gradio as gr

def question_answer(question):
	return question

gr.Interface(fn=question_answer, inputs=["text"], outputs=["textbox"],
	examples=[['小区一周之内发生两次跳楼事件,刚交了三个月房租我是租还是搬走呢?'],
              ['出租出去的房屋家电坏了,应该谁负责出维修费用?'],
              ['女方婚前父母出资买房,登记在女方名下,父母还贷,婚后怎么划分?'],
              ['退租房东不还押金怎么办?'],['小区环境太差,可以找物业吗'],
    ]).launch(share=True)
# 启用 密码
#gr.launch(share=True, auth=("xfl","xfl"),inline=False, server_port=9999)

图像输入、输出

  • Image 组件将图像当做NumPy array, 维度是 (width, height, 3)
  • 允许裁剪、缩放
import numpy as np
import gradio as gr

def sepia(input_img):
    sepia_filter = np.array([
        [0.393, 0.769, 0.189], 
        [0.349, 0.686, 0.168], 
        [0.272, 0.534, 0.131]
    ])
    sepia_img = input_img.dot(sepia_filter.T)
    sepia_img /= sepia_img.max()
    return sepia_img

demo = gr.Interface(sepia, gr.Image(shape=(200, 200)), "image")
demo.launch()

多输入、多输出

import gradio as gr

def greet(name, is_morning, temperature):
    salutation = "Good morning" if is_morning else "Good evening"
    greeting = f"{salutation} {name}. It is {temperature} degrees today"
    celsius = (temperature - 32) * 5 / 9
    return greeting, round(celsius, 2)

demo = gr.Interface(
    fn=greet,
    inputs=["text", "checkbox", gr.Slider(0, 100)],
    outputs=["text", "number"], # 默认名称
demo.launch()

加载大模型

import gradio as gr

api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B")

def complete_with_gpt(text):
    # Use the last 50 characters of the text as context
    return text[:-50] + api(text[-50:])

with gr.Blocks() as demo:
    textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4)
    btn = gr.Button("Generate")
    btn.click(complete_with_gpt, textbox, textbox)

demo.launch()

ChatInterface 聊天页面

对话类样式 ChatInterface,支持集中输出、流式输出

  • 先升级 gradio 版本
# pip install --upgrade gradio
import gradio as gr

def echo(message, history):
    # 对话逻辑控制
    return message

demo = gr.ChatInterface(fn=echo, examples=["hello", "hola", "merhaba"], title="Echo Bot")
demo.launch(share=True)

【2024-2-22】搭建Google gamma模型demo

# gamma web demo
# pip install --upgrade gradio

import gradio as gr
# 聊天模型
from test_gamma_bernald import *

def chat(question, history):
    """
        一次性输出
    """
    res = request_llm(question) # [time, answer]
    answer = res[1].replace('[L]', '\n')
    return answer

def chat_stream(question, history):
    """
        流式输出(非真实)
    """
    res = request_llm(question) # [time, answer]
    answer = res[1].replace('[L]', '\n')
    for i in range(len(answer)):
        yield answer[: i+1]

demo = gr.ChatInterface(
    # fn=chat, 
    fn=chat_stream,
    examples=["你好", "hola", "merhaba"], 
    cache_examples=True,
    title="ChatBot",
    description="""
    - Google 2024年2月21日发布的Gamma系列开源模型, 达到sota
    - 当前版本是Gamma-7b-it,经过指令微调""")
demo.launch(share=True)

高级参数

gr.ChatInterface(
    chat,
    chatbot=gr.Chatbot(height=300),
    textbox=gr.Textbox(placeholder="Ask me a yes or no question", container=False, scale=7),
    title="Yes Man",
    description="Ask Yes Man any question",
    theme="soft",
    examples=["Hello", "Am I cool?", "Are tomatoes vegetables?"],
    cache_examples=True,
    retry_btn=None,
    undo_btn="Delete Previous",
    clear_btn="Clear",
).launch()

# ------- interface 用法 --------
gr.Interface( fn = chat, 
    #inputs=gr.Textbox(lines=3, placeholder="占位符",label="名称"),
    inputs = ['textbox'],
    # ----- 输出 ------
    # outputs=["textbox", "text"],
    # outputs=gr.HighlightedText(label="分析结果:", show_legend=True, combine_adjacent=True),  # 输出结果以什么样的形式展示,随意替换
    # 自定义输出名称
    #outputs=[ gr.outputs.Textbox(label="响应时间"), gr.outputs.Textbox(label="回复内容")],
    # inputs=["text", "checkbox", gr.Slider(0, 100)],
    outputs=["number", "text"],
    examples=[['你是谁'], ['你叫什么名字'], ['你多大了']]
).launch(share=True)

引入附加参数

import gradio as gr
import time

def echo(message, history, system_prompt, tokens):
    response = f"System prompt: {system_prompt}\n Message: {message}."
    for i in range(min(len(response), int(tokens))):
        time.sleep(0.05)
        yield response[: i+1]

demo = gr.ChatInterface(echo, 
                        additional_inputs=[
                            gr.Textbox("You are helpful AI.", label="System Prompt"), 
                            gr.Slider(10, 100)
                        ]
                       )

if __name__ == "__main__":
    demo.queue().launch()

结合langchain的示例

from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage
import openai
import gradio as gr

os.environ["OPENAI_API_KEY"] = "sk-..."  # Replace with your key

llm = ChatOpenAI(temperature=1.0, model='gpt-3.5-turbo-0613')

def predict(message, history):
    history_langchain_format = []
    for human, ai in history:
        history_langchain_format.append(HumanMessage(content=human))
        history_langchain_format.append(AIMessage(content=ai))
    history_langchain_format.append(HumanMessage(content=message))
    gpt_response = llm(history_langchain_format)
    return gpt_response.content

gr.ChatInterface(predict).launch()

使用OpenAI接口实现流式请求

from openai import OpenAI
import gradio as gr

api_key = "sk-..."  # Replace with your key
client = OpenAI(api_key=api_key)

def predict(message, history):
    history_openai_format = []
    for human, assistant in history:
        history_openai_format.append({"role": "user", "content": human })
        history_openai_format.append({"role": "assistant", "content":assistant})
    history_openai_format.append({"role": "user", "content": message})
  
    response = client.chat.completions.create(model='gpt-3.5-turbo',
    messages= history_openai_format,
    temperature=1.0,
    stream=True)

    partial_message = ""
    for chunk in response:
        if chunk.choices[0].delta.content is not None:
              partial_message = partial_message + chunk.choices[0].delta.content
              yield partial_message

gr.ChatInterface(predict).launch()

使用开源模型

import gradio as gr
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, StoppingCriteria, StoppingCriteriaList, TextIteratorStreamer
from threading import Thread

tokenizer = AutoTokenizer.from_pretrained("togethercomputer/RedPajama-INCITE-Chat-3B-v1")
model = AutoModelForCausalLM.from_pretrained("togethercomputer/RedPajama-INCITE-Chat-3B-v1", torch_dtype=torch.float16)
model = model.to('cuda:0')

class StopOnTokens(StoppingCriteria):
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
        stop_ids = [29, 0]
        for stop_id in stop_ids:
            if input_ids[0][-1] == stop_id:
                return True
        return False

def predict(message, history):

    history_transformer_format = history + [[message, ""]]
    stop = StopOnTokens()

    messages = "".join(["".join(["\n<human>:"+item[0], "\n<bot>:"+item[1]])  #curr_system_message +
                for item in history_transformer_format])

    model_inputs = tokenizer([messages], return_tensors="pt").to("cuda")
    streamer = TextIteratorStreamer(tokenizer, timeout=10., skip_prompt=True, skip_special_tokens=True)
    generate_kwargs = dict(
        model_inputs,
        streamer=streamer,
        max_new_tokens=1024,
        do_sample=True,
        top_p=0.95,
        top_k=1000,
        temperature=1.0,
        num_beams=1,
        stopping_criteria=StoppingCriteriaList([stop])
        )
    t = Thread(target=model.generate, kwargs=generate_kwargs)
    t.start()

    partial_message  = ""
    for new_token in streamer:
        if new_token != '<':
            partial_message += new_token
            yield partial_message

gr.ChatInterface(predict).launch()

Block 低级页面

Blocks方式要with语句添加组件,如果不设置布局方式,组件将按照创建顺序垂直出现在应用程序中

import gradio as gr

def greet(name):
    return "Hello " + name + "!"

with gr.Blocks() as demo:
    # 设置输入组件
    name = gr.Textbox(label="Name")
    # 设置输出组件
    output = gr.Textbox(label="Output Box")
    # 设置按钮
    greet_btn = gr.Button("Greet")
    # 设置按钮点击事件
    greet_btn.click(fn=greet, inputs=name, outputs=output)

demo.launch()

行内布局: Row

with gr.Blocks() as demo:
    with gr.Row():
        btn0 = gr.Button("Button 0", scale=0)
        btn1 = gr.Button("Button 1", scale=1) # 组件扩大
        btn2 = gr.Button("Button 2", scale=2) # 组件更大

列向布局及嵌套

  • Column
import gradio as gr

with gr.Blocks() as demo:
    with gr.Row():
        text1 = gr.Textbox(label="t1")
        slider2 = gr.Textbox(label="s2")
        drop3 = gr.Dropdown(["a", "b", "c"], label="d3")
    with gr.Row(): # 扩大一倍,并至少600宽
        with gr.Column(scale=1, min_width=600):
            text1 = gr.Textbox(label="prompt 1")
            text2 = gr.Textbox(label="prompt 2")
            inbtw = gr.Button("Between")
            text4 = gr.Textbox(label="prompt 1")
            text5 = gr.Textbox(label="prompt 2")
        with gr.Column(scale=2, min_width=600):
            img1 = gr.Image("images/cheetah.jpg")
            btn = gr.Button("Go").style(full_width=True)

demo.launch()

Tab 多页面

多模块:使用tab页

import numpy as np
import gradio as gr

def flip_text(x):
    return x[::-1]

def flip_image(x):
    return np.fliplr(x)

with gr.Blocks() as demo:
    gr.Markdown("Flip text or image files using this demo.")
    with gr.Tab("Flip Text"):
        text_input = gr.Textbox()
        text_output = gr.Textbox()
        text_button = gr.Button("Flip")
    with gr.Tab("Flip Image"):
        with gr.Row():
            image_input = gr.Image()
            image_output = gr.Image()
        image_button = gr.Button("Flip")

    with gr.Accordion("Open for More!"):
        gr.Markdown("Look at me...")

    text_button.click(flip_text, inputs=text_input, outputs=text_output)
    image_button.click(flip_image, inputs=image_input, outputs=image_output)

demo.launch()

tab中植入示例(example)

import gradio as gr

with gr.Blocks() as demo:
    input = gr.Textbox(label="Input")
    output = gr.Textbox(label="Output")
    input.change(lambda s: s, inputs=input, outputs=output)
    with gr.Tab("Examples Page 1"):
        gr.Examples(["a", "b", "c"], inputs=input)
    with gr.Tab("Examples Page 2"):
        gr.Examples(["d", "e", "f"], inputs=input)
    with gr.Tab("Examples Page 2"):
        gr.Examples(["g", "h", "i"], inputs=input)
    with gr.Row() 
        # 缓存
        examples = gr.Examples(examples=examples, cache_examples=False)
        textbox = gr.Textbox()
        # 同时包含输入、输出
        examples = gr.Examples(examples=examples, inputs=textbox, outputs=label, fn=predict, cache_examples=True)

demo.launch()

多个Tab页公用:官方【2023-5-18】亲测有效

  • 使用gr.Tabs()将页面包起来,子页面使用gr.TabItem
import gradio as gr

def add_text(history, text):
    history = history + [(text, None)]
    return history, ""

def add_file(history, file):
    history = history + [((file.name,), None)]
    return history

def bot(history):
    response = "**That's cool!**"
    history[-1][1] = response
    return history

def flip_text(x):
    return x[::-1]

with gr.Blocks() as demo:
    gr.Markdown("LLM Demo工具集")
    gr.Markdown("""
        # title
        ## content
    """)
    with gr.Tabs():
        with gr.TabItem("Flip Text"):
            text_input = gr.Textbox()
            text_output = gr.Textbox()
            text_button = gr.Button("Flip")
        
        chatbot = gr.Chatbot([], elem_id="chatbot").style(height=750)

        with gr.TabItem("ChatGLM-6B"):
            with gr.Row():
                with gr.Column(scale=0.85):
                    txt = gr.Textbox(
                        show_label=False,
                        placeholder="Enter text and press enter, or upload an image",
                    ).style(container=False)
                with gr.Column(scale=0.15, min_width=0):
                    btn = gr.UploadButton("📁", file_types=["image", "video", "audio"])

            txt.submit(add_text, [chatbot, txt], [chatbot, txt]).then(
                bot, chatbot, chatbot
            )
            btn.upload(add_file, [chatbot, btn], [chatbot]).then(
                bot, chatbot, chatbot
            )
        with gr.Accordion("Open for More!"):
            gr.Markdown("Look at me...")

    text_button.click(flip_text, inputs=text_input, outputs=text_output)

demo.launch(share=True)

对话框: 参考 miniGPT-4 案例

多步输入

多步输入

  • 将一个函数的输出作为输入
  • 将语音文件转录出来,判断情感
from transformers import pipeline

import gradio as gr

asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
classifier = pipeline("text-classification")

def speech_to_text(speech):
    text = asr(speech)["text"]
    return text
def text_to_sentiment(text):
    return classifier(text)[0]["label"]

demo = gr.Blocks()

with demo:
    audio_file = gr.Audio(type="filepath")
    text = gr.Textbox()
    label = gr.Label()

    b1 = gr.Button("Recognize Speech")
    b2 = gr.Button("Classify Sentiment")

    b1.click(speech_to_text, inputs=audio_file, outputs=text)
    b2.click(text_to_sentiment, inputs=text, outputs=label)

demo.launch()

颜色区分

【2023-7-27】通过颜色高亮突出文本差异

  • NER、PoS序列标准

方法, 官方示例

  • gradio.HighlightedText
from difflib import Differ

import gradio as gr


def diff_texts(text1, text2):
    d = Differ()
    return [
        (token[2:], token[0] if token[0] != " " else None)
        for token in d.compare(text1, text2)
    ]

demo = gr.Interface(
    diff_texts,
    [
        gr.Textbox(
            label="Text 1",
            info="Initial text",
            lines=3,
            value="The quick brown fox jumped over the lazy dogs.",
        ),
        gr.Textbox(
            label="Text 2",
            info="Text to compare",
            lines=3,
            value="The fast brown fox jumps over lazy dogs.",
        ),
    ],
    gr.HighlightedText(
        label="Diff",
        combine_adjacent=True,
        show_legend=True,
    ).style(color_map={"+": "red", "-": "green"}),
    theme=gr.themes.Base()
)
if __name__ == "__main__":
    demo.launch()

gradio_client

【2023-11-13】Use a Gradio app as an API – in 3 lines of Python

除了web形式,gradio也支持api形式访问

  • Gradio client 与 Gradio Space 一起协作
  • gradio token
# $ pip install gradio_client
from gradio_client import Client

client = Client("abidlabs/whisper") # 连接公共的 gradio space地址
client = Client("abidlabs/my-private-space", hf_token="...") # 连接私有的gradio space 地址
client = Client.duplicate("abidlabs/whisper") # 复制公开space当私用,Duplicating a Space for private use
client = Client("https://bec81a83-5b5c-471e.gradio.live") # 使用临时生成的分享链接,使用 .view_api() 查看传参信息

client.predict("audio_sample.wav")
# 多参数
client = Client("gradio/calculator")
client.predict(4, "add", 5)

>> "This is a test of the whisper speech recognition model."

api信息示例(view_api返回)

Client.predict() Usage Info
---------------------------
Named API endpoints: 1

 - predict(input_audio, api_name="/predict") -> value_0
    Parameters:
     - [Audio] input_audio: str (filepath or URL)
    Returns:
     - [Textbox] value_0: str (value)

实测通过

#pip install gradio_client
from gradio_client import Client

client = Client("abidlabs/en2fr")
res = client.predict("Hello")
print('翻译:英语→法语:', res)

client = Client("abidlabs/whisper")
res = client.predict("https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3")
print('ASR: audio → text:', res)
#>> "My thought I have nobody by a beauty and will as you poured. Mr. Rochester is serve in that so don't find simpus, and devoted abode, to at might in a r—"

DEMO

实用DEMO集合

streamlit

【2022-10-9】streamlit,A faster way to build and share data apps

  • Streamlit turns data scripts into shareable web apps in minutes. All in pure Python. No front‑end experience required.
  • 支持markdown格式,编辑HTML页面
  • 支持交互式UI
  • 跨平台、终端
  • 与github代码库衔接

安装

# 安装
pip install streamlit
# 测试
streamlit hello
#streamlit 运行项目 a.py 指定端口 8000 不指定就是默认端口是 8051
streamlit run project/a.py --server.port 8000

功能

  • Streamlit templates
  • Science & technology
  • NLP & language
  • Computer vision & images
  • Finance & business
  • Data visualization
  • Geography & society
  • Education

案例

有趣的Python脚本

心电图异常诊断界面(PyQT):房颤的诊断和解释,代码

心电图诊断界面(bokeh)

  • 在线演示的界面,代码

结束


支付宝打赏 微信打赏

~ 海内存知已,天涯若比邻 ~

Share

Related Posts

标题:计算机知识点汇总-脑图笔记

摘要:汇总数林觅风的IT技能笔记,方便回复、巩固知识点

标题:代码规范及测试

摘要:Web开发相关技术知识点

Comments

--disqus--

    Content
    My Moment ( 微信公众号 )
    欢迎关注鹤啸九天