目录
一、Jinja2
1.1 概述
- 模板系统:模板文件中放置特定的占位符、占位语句,python程序文件用变量值来替换模板文件中的占位符
- Jinja2:由flask作者开发的一款模板语言,原始目的为了web网页开发
- 安装:
pip3 install Jinja2
1.2 语法
1.2.1 变量
- 代码示例
{# 这里是注释的写法,文件名index.html #} <p> 来自字典的值:{{ mydict['one'] }}</p> <p> 来自列表的值:{{ mylist[0] }}</p> <p> 来自对象方法的值:{{ myobj.meth() }}</p>
注意:大括号内两侧的空格是必须的
- 过滤器表
过滤器名 说明 safe 渲染时不转意 upper、lower、capital 把值全转大写、小写、首字母大写 title 每个单词首字母大写 trim 去首位空格 join 拼接多个值为字符串 replace 替换字符串值 round、int 对数字四舍五入、值转成整型 - 用法
{{ 11.1 | round | int }} >>> 11 {{ "hello world" | replace("hello", "goodbye") | upper }} >>> GOODBYE WORLD # names为列表:拼接字符为- {{ names | join("-") }} # 选列表第一个元素去除两侧空 {{ [' a b'] | first | trim }} >>> a b # 在给定宽度内将字符串行居中对齐 {{ '居中标题' | center }} # hosts为列表,添加换行符 {{ hosts | join('\n') }} # 添加默认值 {{ name | default('duke') }} # 默认按键对字典排序:按值排序写法dictsort(by='value') {% for key, value in dic | dictsort %} # 先管道转换,再比较 {% if str_value | float >= 4.22 %}
注:过滤器通过管道符与变量连接
1.2.2 控制循环结构
- if语句
{% if duke.sick %} duke is sick {% elif duke.dead %} duke is dead {% else %} duke is ok {% endif %}
比较运算符:==, !=, >, >=, <, <=
逻辑运算符:and, or, not - for语句
{# 遍历列表:语句排版,按没语句块考虑效果 #} <ul> {% for user in users %} <li>{{ user }}</li> {% endfor %} </ul> {# 遍历字典 #} <ul> {% for key, value in users.items() %} {% if loop.first %} <li>这是首轮循环</li> {% endif %} <li>当前键为:{{ key }}</li> <li>当前值为:{{ value }}</li> {% endfor %} </ul>
loop.index:从1开始记录循环次数,loop.index()为从0开始
loop.first:若为第一次迭代则为True,配合if使用,loop.last
loop.length:循环次数 - for + if语句:
{% for num in nums if num>10 %}
1.2.3 宏
- 定义位置:建议集中写在模板的尾部
- 宏定义
{% macro input(name, age=18) -%} <h1>{{ name }}:{{ age }}</h1> {%- endmacro -%}
- 宏使用
<h1>{{input('duke', 12)}}</h1>
1.3 空行
1.3.1 分析
- 示例
- 显示:右侧渲染结果有多处空行
- 原因分析:渲染模板时,所有语言块都将被删除,但所有空都保留在原处。也就是说,如果在块之前或之后有空格、制表符或换行符,都保留
1.3.2 解决方案
- 方法一:
- -语句:
{%- 语句 %}
,去除语言块之前的空,{% 语句 -%}
,去除之后的空(含换行符) - 渲染if语句:删除
{% if true %}
语句块,末尾换行符保留;删除{% if false %}
语句块,末尾换行符也删除;删除{% endif %}
语句块,不管真假,都保留前后空及换行符
- -语句:
- 方法二:
- 启用渲染选项:删除空行trim_blocks,删除块左侧空lstrip_blocks
- 补充选项:Strict check,若有值未传则报错
1.4 渲染
1.4.1 渲染函数
- /root/main/main.py
#!/usr/bin/python # -*- codinig: UTF-8 -*- import os import sys import jinja2 # 定义模板渲染函数:模板路径,填充数据以json格式 def render(tpl_path='index.html', **kwargs): # 拆分路径:路径与文件名 path, filename = os.path.split(tpl_path) # 渲染后的文件内容:模板父文件夹路径,模板文件名,传入字典 return jinja2.Environment( loader=jinja2.FileSystemLoader(path or './'), trim_blocks=True, lstrip_blocks=True ).get_template(filename).render(**kwargs) # 命令行操作函数 def cmd_format(**data): # 获取命令行参数列表 filepath = sys.argv # 若未输入参数就通过if语句用默认模板 filepath.append('') if filepath[1]: out = render(filepath[1], **data) print(out) else: out = render(**data) print(out) if __name__ == '__main__': # 数据填充字典:json格式 datainput = { 'head': "这是标题", 'context': "这是内容" } # 这里是命令行方式,也可以直接调用render函数 cmd_format(**datainput)
- 模板/root/main/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ head }}</title> </head> <body> <div> <h1>{{ context }}</h1> </div> </body> </html>
- 运行
python3 main.py
- 终端输出
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>这是标题</title> </head> <body> <div> <h1>这是内容</h1> </div> </body> </html>
1.4.2 继承和super函数
- 模板继承:父模板中留
block语句块
,子模板继承父模板,并只需要写block语句块
即可 - super函数:父模板中
block语句块
中的语句,在子模板中用{{ super() }}
语句可拿到,否则子覆盖父 - /root/main/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>欢迎</h1> <h2>以下是块内容</h2> {% block content %} <h3>这是块内的语句</h3> {%- endblock %} <h2>再见</h2> </body> </html>
- /root/main/login.html
{% extends "index.html" %} {% block content %} {{ super() }} <form> 用户名:<input type="text" name="user"> 密码:<input type="text" name="pwd"> </form> {% endblock %}
- 运行
python3 main.py login.html
- 终端输出
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>欢迎</h1> <h2>以下是内容</h2> <h3>这是块内的语句</h3> <form> 用户名:<input type="text" name="user"> 密码:<input type="text" name="pwd"> </form> <h2>再见</h2> </body> </html>
二、word模板
- docxtpl模块:使用Jinja2思路,以docx为word模板
- docxtpl 主要依赖两个包:python-docx 用来读写word,jinja2用来管理插入到模板中的标签
- 官方文档:传送门
- 语法种类:注释:
{#p #}
,变量取值:{{ }}
(转义{{ variable|e }}
),控制结构:{%p %}
,p代表段落,否则会出现1.3节的空行现象,注释和控制结构每行只能出现一个,灵活运用1.3.1方法一
连接长句子 - 安装:
pip3 install docxtpl
2.1 渲染函数
-
/root/main/main.py
#!/usr/bin/python # -*- codinig: UTF-8 -*- from docxtpl import DocxTemplate import sys import jinja2 # 定义模板渲染函数:模板路径,输出路径,填充数据以字典格式 def render(tpl_path='index.docx', output='sample.docx', **kwargs): tpl = DocxTemplate(tpl_path) # 添加参数:清除空行 jinja_env = jinja2.Environment(trim_blocks=True, lstrip_blocks=True) # 渲染函数:传入数据、jinja2环境变量 tpl.render(kwargs, jinja_env) tpl.save(output) print("completed") # 命令行构造函数 def cmd_format(**data): filepath = sys.argv # 保证有三个参数:防止少于两个参数的话报错 filepath.append('') filepath.append('') # 指定:模版文件路径、输出文件路径(路径必须存在,否则报错) if filepath[1] and filepath[2]: render(filepath[1], filepath[2], **data) # 指定:模版文件路径、输出文件为./sample.docx elif filepath[1]: render(filepath[1], **data) # 默认:模版文件./index.docx、输出文件为./sample.docx else: render(**data) if __name__ == '__main__': # 数据填充字典:json格式 datainput = { 'head': "内容", 'context': "context内容" } # 这里是命令行方式,也可以直接调用render函数 cmd_format(**datainput)
-
模版:/root/main/index.docx
-
运行
python3 main.py
-
输出:/root/main/sample.docx
2.2 表格
- 模版/root/main/index.docx
2.2.1 动态水平合并
- 模版写法
{%tc for col in context %} {% hm %}标题 {%tc endfor %} {%tc for col in context %} {{col}} {%tc endfor %} hm:水平动态合并,如下图效果
- 效果
context:[‘a’, ‘b’, ‘c’]
2.2.2 动态垂直合并
- 模版写法
{%tr for row in context %} {% vm %}竖标题 {{row}} {%tr endfor %} hm:水平动态合并,如下图效果
- 效果
2.3 插入图片
- 模版:/root/main/index.docx
{{ title_pic1 }} {{ title_pic2 }} {{ title_pic3 }}
- 渲染函数
#!/usr/bin/python # -*- codinig: UTF-8 -*- import sys import jinja2 from docxtpl import DocxTemplate, InlineImage from docx.shared import Mm, Pt # 定义模板渲染函数:模板路径,输出路径,填充数据以字典格式 def render(tpl_path='index.docx', output='sample.docx', **kwargs): tpl = DocxTemplate(tpl_path) # 添加参数:清除空行 jinja_env = jinja2.Environment(trim_blocks=True, lstrip_blocks=True) # 展开image项的每个子项,重新添加进kwargs if 'images' in kwargs: for pic in kwargs['images']: width = pic.get('width') height = pic.get('height') if width and height: # 给数据字典增加项:图片的单位是像素(Pt)、毫米(Mm) kwargs.setdefault(pic.get('name'), InlineImage(tpl, pic.get('path'), width=Pt(width), height=Pt(height))) elif width: kwargs.setdefault(pic.get('name'), InlineImage(tpl, pic.get('path'), width=Pt(width))) else: kwargs.setdefault(pic.get('name'), InlineImage(tpl, pic.get('path'), height=Pt(height))) del kwargs['images'] # 渲染函数:传入数据、jinja2环境变量 tpl.render(kwargs, jinja_env) tpl.save(output) print("completed") # 命令行构造函数 def cmd_format(**data): filepath = sys.argv # 保证有三个参数:防止少于两个参数的话报错 filepath.append('') filepath.append('') # 指定:模版文件路径、输出文件路径(路径必须存在,否则报错) if filepath[1] and filepath[2]: render(filepath[1], filepath[2], **data) # 指定:模版文件路径、输出文件为./sample.docx elif filepath[1]: render(filepath[1], **data) # 默认:模版文件./index.docx、输出文件为./sample.docx else: render(**data) if __name__ == '__main__': # 数据填充字典:json格式 datainput = { # 若没有内插图像,则删掉此项,不可为空,否则报错 'images': [ # 1、指定宽,2、指定高,3、指定宽高 {'name': 'title_pic1', 'path': 'pic/test.png', 'height': 100}, {'name': 'title_pic2', 'path': 'pic/test.png', 'width': 100}, {'name': 'title_pic3', 'path': 'pic/test.png', 'width': 100, 'height': 100} ] } # 这里是命令行方式,也可以直接调用render函数 cmd_format(**datainput)
- 效果
三、Excel操作
3.1 概述
- openpyxl:一个读写Excel的python库,安装
pip3 install openpyxl
- 层级
- Workbook:对工作薄(Excel文件)对象的抽象
- Worksheet:对表单对象的抽象
- Cell:对单元格对象的抽象
- 操作工作簿
3.2 工作簿对象Wrokbook
- 新建空Excel
import openpyxl # 创建工作簿对象 wb = openpyxl.Workbook() ... # 保存工作簿对象 wb.save("新建表格.xlsx")
- 打开已存在Excel
import openpyxl # 打开已存在的Excel文件并返回工作簿对象 wb = openpyxl.load_workbook("新建表格.xlsx") ... wb.save("新建表格.xlsx")
- workbook对象属性
写法 解释 wb.read_only 判断文件是否以只读打开,返回布尔值 wb.encoding 返回文件编码方式,如’utf-8’ wb.properties 返回文档元数据对象,如作者、创建时间、版本…
改写wb.properties.version = 1.0
- 操作Worksheet方法
# 增表单:第二个参数为插入索引值,可选 In [1]: wb.create_sheet('boss') Out[1]: <Worksheet "boss"> ################################################### # 删表单:方法一,若不存在,报错KeyError In [2]: wb.remove(wb['boss']) # 方法二:del wb['boss'],若不存在,报错KeyError ################################################### # 查表单:获取表单名称列表 In [3]: wb.sheetnames Out[3]: ['student', 'teacher'] # 获取表单对象:以名称方式 In [4]: wb["student"] Out[4]: <Worksheet "student"> # 获取当前活动表单对象 In [5]: wb.active Out[5]: <Worksheet "student">
3.3 表单对象Worksheet
3.3.1 表单对象属性
- 表单对象属性
写法 解释 wb.title 表格标题字符串,例如 'student'
wb.dimensions 返回表格中含有数据的表格大小,例如 'A1:D4'
ws.max_row 返回含数据的最大行数 4
,还有min_row,max_column,min_column
3.3.2 遍历表格
- 按行遍历cell对象:按列columns同
In [1]: for row in ws.rows: ...: print(row) # row为每行cell对象的元组,rows为生成器 (<Cell 'student'.A1>, <Cell 'student'.B1>, <Cell 'student'.C1>, <Cell 'student'.D1>) (<Cell 'student'.A2>, <Cell 'student'.B2>, <Cell 'student'.C2>, <Cell 'student'.D2>) (<Cell 'student'.A3>, <Cell 'student'.B3>, <Cell 'student'.C3>, <Cell 'student'.D3>) (<Cell 'student'.A4>, <Cell 'student'.B4>, <Cell 'student'.C4>, <Cell 'student'.D4>)
- 按行遍历值
In [2]: for row in ws.values: ...: print(row) # row为每行数据的元组,values为生成器 ('no', 'name', 'chinese', 'math') (1, 'duke', 97, 88) (2, 'park', 95, 89) (3, 'sam', 96, 87)
- 限定范围按行遍历cell对象,iter_rows迭代器
In [3]: for row in ws.iter_rows(min_row=2,max_row=3,min_col=2,max_col=3): ...: print(row) # row为限定数据 (<Cell 'student'.B2>, <Cell 'student'.C2>) (<Cell 'student'.B3>, <Cell 'student'.C3>)
3.4 单元格对象Cell
- 单元格对象属性
写法 解释 ws[‘B2’] 获取cell对象,例如<Cell ‘student’.B2>, ws(row=2, column=2)
效果同cell.row 获取行数 2
,cell.column获取列数cell.value 获取单元格值 'duke'
cell.coordinate 获取单元格坐标 'B2'
- 通过cell对象获取值
In [1]: for row in ws.rows: ...: scores = [cell.value for cell in row] ...: print(scores) # 跟3.3.2节按行遍历值对比 ['no', 'name', 'chinese', 'math'] [1, 'duke', 97, 88] [2, 'park', 95, 89] [3, 'sam', 96, 87]
- 工作簿到单元格数据
In [1]: wb = openpyxl.load_workbook("新建表格.xlsx") # 链式获取与修改 In [2]: wb['student'].cell(row=2,column=2).value='jack' # 有数据的表格尾部追加 In [3]: unit = [4, 'smith', 93, 90] In [4]: wb['student'].append(unit) # 将更改保存到文件 In [5]: wb.save("新建表格.xlsx")