python笔记-文本处理(第三天)
声明:本人是每天学习赖明星《python linux系统管理与自动化运维》一书后,整理成自己的笔记,供学习和分享使用,如有需要,请购买作者正版书,谢谢
1、字符串常量
1.1、定义字符串
python不区分字符和字符串,所以python可以使用引号或者双引号来定义字符串,如下:
一般来说,字符串的值本身包含单引号的情况下,我们一般使用双引号来定义字符串;同理,如果字符串的值本身包含双引号的情况下,我们可以使用单引号来定义字符串,如下:
当然,python也可以使用转译字符来进行转译,转译字符是\。
\`:单引号
\n:换行
\a:响铃
\b:退格,将当前位置移动到前一列
\f:换页,将当前位置移动到下页开头
\r:回车,将当前位置移动到本行开头
\t:水平制表,跳到下一个TAB位置
\v:垂直制表
\\:代表一个反斜杠字符'\'
在编程语言中,使用\定义转译字符,在URL中,使用%定义转译字符。
对于前面的例子,还可以用转译字符来表示:
大家通过前面的介绍,知道python遇到\时,会认为是一个转移符,但是如下,可能改变我们的初衷:
上面的是因为python对\n进行了转移,为了修正这个问题,我们可以在字符串前面加一个r,表示使用原始字符串,或者用\\进行转移:
或者:
在python中,还可以使用三引号来定义字符串,如下:
python字符串还有一个容易忽略的特性,即两个相连的字符串会自动组成一个新字符串:
1.2、字符串是不可变的有序集合
python语言的字符串有两大特点:
1)字符串是不可变的,不能直接对字符串进行修改(python工程师应该谨记这个特性)
2)字符串是字符的有序集合
python字符串每次操作都会产生一个新的字符串,新的字符串会占用一块独立的内存。因此,操作字符串时需要避免产生太多的中间结果。
枚举元组里面东西,应该用字符串的join方法,如下:
Python字符串可以通过下标和切片进行访问。
a)下标操作每次只能访问一个元素;
b)切片操作每次可以访问一个范围,可以指定切片操作的起点,终点和步长,也可以省略。
例子:
另外,下标和切片,可以应用于任何有序的集合,包括字符串,元组,列表。
上面我们用如下方式进行了逆转字符串,但是这种可读性不好:
In [52]: s[::-1] #这是切片,起点是开头,结尾是末尾,步长是-1
Out[52]: 'dlrow,olleH'
逆转字符串不如使用reversed函数清晰,这种可读性更好,如下:
2、字符串函数
2.1 通用操作
相同的方式应用于列表中:
在Python语言的设计哲学中,字符串、列表和元组具有一些共性,即他们都是元素的有序集合,python语言将对共性的操作提炼成了通用操作。因此,下标访问、序列切片操作、求长度和判断元素是否存在于集合中都是通过更加通用的函数和表达式提供支持。
2.2 与大小写相关的方法
以下是几个函数是与字符大小写相关的字符串处理函数:
1)upper:将字符串转换为大写;
2)lower:将字符串转换为小写;
3)isupper:判断字符串是否都为大写;
4)islower:判断字符串是否都为小写;
5)swapcase:将字符串中的大写转换为小写,小写转换为大写;
6)capitalize:将首字符转换为大写;
7)istitle:判断字符串是不是一个标题。
例子:
[root@localhost ~]# python test3.py
Please input yes or no:"yes"
continue do something
3、判断类方法
1)isupper:判断字符串是否都为大写;
2)islower:判断字符串是否都为小写;
3)isalpha:如果字符串只包含字母,并且非空,则返回True,否则False;
4)isalnum:如果字符串值包含字母和数字,并且非空,则返回True,否则False;
5)isspace:如果字符串值包含空格、制表符、换行符,并且非空,则返回True,否则False;
6)isdecimal:如果字符串只包含数字字符,并且非空,则返回True,否则False。
例子:
u"Python 3.6".isdecimal()
u/U:表示unicode字符串
不是仅仅是针对中文, 可以针对任何的字符串,代表是对字符串进行unicode编码。
一般英文字符在使用各种编码下, 基本都可以正常解析, 所以一般不带u;但是中文, 必须表明所需编码, 否则一旦编码转换就会出现乱码。
建议所有编码方式采用utf8
4、字符串方法startswith和endswith
startswith和endswith也是两个判断函数,用来判断方法的参数是否为字符串的前缀和后缀。
下面我们看个使用的例子:
假设当前目录下存在文本文件、python文件和图片文件,如下:
[root@localhost ~]# ls
a.txt b.txt c.txt d.txt e.py f.py g.py h.jpg
现在我们想找出所有文本文件或者Python文件,在python中使用内置的字符串方法非常方法:
在我们的实际工作中,更多的时候可能需要前缀匹配:
利用python查看文件大小:
5、查找类函数
下面几个函数都是用来查找子串出现在字符串中的位置,他们之间的区别就是查找的方向不同,或者是处理异常情况的方式不同:
1)find:查找子串出现在字符串中的位置,如果查找失败,返回-1;
2)index:与find函数类似,如果查找失败,抛出ValueError异常;
3)rfind:与find函数类似,区别在于rfind是从后向前找;
4)findex:与index类似,区别在于rindex是从后向桥找。
例子:
注意上面的 a = 'Return the lowest index in S where substring sub is found'里面,有两个in,第一个出现在下标18的位置,第二个出现在19的位置,在不指定范围的情况下,查找的是第一个in。
注意:find函数最容易用错的地方在于使用find函数判断一个子串是否出现在字符串中。
判断一个字符串是否是另外一个字符串的子串,正确的做法是使用In或者not in,如下:
6、字符串操作方法
前面说过,python的字符串是不可变的,无法修改。如果要修改原来的字符串,可以对字符串执行修改操作,将修改后的字符串再赋值回原来的变量。
6.1 join函数
字符串的join函数用以连接字符串列表,组成一个新的、更大的字符串。
另外,print函数本身就可以通过sep参数指定分割符进行拼接,不需要join函数,如下:
注:笔者发现只有python3的print才有sep功能。
6.2 split函数
split函数与join函数的作用正好相反,split将一个字符串拆分成字符串列表,如下:
6.3 strip函数
strip:对字符串两边进行裁剪;
lstrip:对字符串左边进行裁剪;
rstrip:对字符串右边进行裁剪。
使用场景:strip函数使用最多的场景是去除字符串两边的空白字符,如下:
当然,也可以给strip函数传递参数,参数中的所有字符都可以被裁剪,传递给strip函数的参数是需要裁剪的字符集合,因为是集合,所以字符串的顺序并不重要,重复字符串也没有任何效果,如下:
6.4、relapace函数
作用:将字符串的子串替换成另外一个新的子串:
7、使用Python分析apache的访问日志
需求1:通过apache访问日志,统计PV和UV(pv是访问的访问请求数,uv是网站独立访客数)。
accesslog日志如下:
注意:access.log最后一行不能是空行,否则列表会报错,说下标超出范围。
解释:我们将解析出来的ip添加到一个列表中,那么列表的长度就是网站的访问数PV。当我们求UV时,只需要对刚才的列表进行去重,然后统计去重以后的元素个数,就得到UV。对于这个需求,在Python中可以通过将列表保存到一个集合中实现,因为集合天生就具有去重的功能。
小知识,set创建集合
需求2:通过apache访问日志找到网站中最热的资源(Counter)。
这个信息对工程师来说非常有用,工程师知道哪些资源是比较热的资源后,可以对这些资源的访问进行额外的的优化,比如缓存、反向代理、CDN等技术手段。
Python中可以使用collections.Counter保存资源的热度。
Counter是dict的子类,使用方式和字典类似,如下:
Counter是python2.7及以上版本才有的功能,这点要注意。
上面可以看到,Counter是使用一个字典保存了资源的热度,字典的键是资源的名称,字典的值是访问的次数。
下面的代码使用Counter统计网站中最热4项资源:
cat d:\\temp\\accesslog.txt
下面是python代码:
cat test.py
>>>python test.py
Popular resources :[('/index.php', 4), ('/a.php', 1)]
需求3:统计网站的出错比例
思路:如果HTTP CODE为2xx或3xx,则视为访问正确;如果HTTP CODE为4xx或者5xx,则视为访问出错。
出错比例=出错的请求数/总请求数
方法:
python代码:
小知识:
python字典的items方法作用:是可以将字典中的所有项,以列表方式返回。如果对字典项的概念不理解,可以查看Python映射类型字典基础知识一文。因为字典是无序的,所以用items方法返回字典的所有项,也是没有顺序的。
python字典的iteritems方法作用:与items方法相比作用大致相同,只是它的返回值不是列表,而是一个迭代器。
调用格式
字典items()与iteritems()都是函数,调用标准格式和其它函数格式是一样的:变量.方法()
操作方法
字典items()操作方法:
>>> x = {'title':'python web site','url':''}
>>> x.items()
[('url', ''), ('title', 'python web site')]
从结果中可以看到,items()方法是将字典中的每个项分别做为元组,添加到一个列表中,形成了一个新的列表容器。如果有需要也可以将返回的结果赋值给新变量,这个新的变量就会是一个列表数据类型。
>>> a=x.items()
>>> a
[('url', ''), ('title', 'python web site')]
>>> type(a)
<type 'list'>
dict iteritems()操作方法:
>>> f = x.iteritems()
>>> f
<dictionary-itemiterator object at 0xb74d5e3c>
>>> type(f)
<type 'dictionary-itemiterator'> #字典项的迭代器
>>> list(f)
[('url', ''), ('title', 'python web site')]
字典.iteritems()方法在需要迭代结果的时候使用最适合,而且它的工作效率非常的高。
|
8、字符串格式化
在python中,存在两种格式化字符串的方法,即%表达式和format函数。
虽然%表达式目前还被广泛使用,但是format函数才是字符串格式的为了,因此本节只介绍format函数。
最简单的format函数使用应该是通过参数的位置访问参数。如下所示,通过{}来表示一个占位符,python会自动将format函数的参数依次传递给{}占位符:
在参数较少的情况下,通过占位符或下标的形式访问format函数的参数并没什么问题,如果参数较多就不太适合了,这个时候可以使用解释下更强的关键字参数形式,如下:
format函数也可以直接访问对象的属性,如下所示:
说明:namedtuple是继承自tuple的子类。namedtuple创建一个和tuple类似的对象,而且对象拥有可访问的属性。
tuple是元组的意思。
下面的例子是对format函数的精度、符号、宽度、对齐方式、字符填充、逗号分隔等格式进行测试:
9、正则表达式
内置的字符串处理函数能解决很多的字符处理问题,但是也是有一些比较复杂的情况,内置字符串无法处理,或者没法优雅的处理,这时用更具表达能力的正则表达式就好办了。如下的一个字符串,现在需要同时用":"和“.”进行split,这对于python的内置字符串函数来说,就比较棘手了。但此时如果用正则表达式,就能轻松解决:
9.1正则表达式语法
1)要匹配给定文本中的所用单词,可以使用下面的正则表达式:
?[a-zA-Z]+
?用于匹配单词前后可能出现的空格,[a-zA-Z]+代码一个或多个英文字母。
2)要匹配一个ip,可以使用下面的正则表达式:
[0-9](1,3)\.[0-9](1,3)\.[0-9](1,3)\.[0-9](1,3)
表 1. 正则表达式基本语法
|
正则表达式的基本语法
|
|
正则表达式
|
描述
|
示例
|
|
^
|
行起始标记
|
^imp匹配以imp起始的行
|
|
$
|
行尾标记
|
import$匹配以import结尾的行
|
|
,
|
匹配任意一个字符
|
它只能匹配单个字符,但是可以匹配任意字符,如linu.可以匹配linux与linus
|
|
[]
|
匹配包含在[字符]之间的任意字符
|
coo[lk]能够匹配cook或者cool
|
|
[^]
|
匹配包含在[字符]之外的任意字符
|
9[^01]可以匹配92,93,但是不匹配91或90
|
|
[-]
|
匹配[]中指定范围内的任意一个字符
|
[1-5]匹配1~5的任意一个数字,[a-z]匹配任意一个小写字母
|
|
?
|
匹配之前项的一次或0次
|
hel?o匹配hello或helo,但不能匹配helllo
|
|
+
|
匹配之前项的1次或多次
|
hel+匹配hel和hell,但是不能匹配he
|
|
*
|
匹配之前项的0次或多次
|
hel*匹配he,hel,hell
|
|
{n}
|
匹配之前项的n次
|
[0-9]{3}匹配任意一个三位数
|
|
{n,}
|
之前的项至少要匹配n次
|
[0-9]{3,}匹配任意一个三位数或更多的数字
|
|
{n,m}
|
指定之前的项所必须匹配的最小次数和最大次数
|
[0-9]{2,5}匹配从两位数到五位数之间的任意一个数字
|
表 2. 正则表达式特殊序列
9.2 利用re库处理正则表达式
python中,标准库的re模块用来处理正则表达式,它能够顺利处理unicode和普通字符串,这个模块包含了与正则表达式相关的函数、标志和一个异常。
最常用的是re模块下的findall函数,用来输出所有符合模式匹配的子串,如下:
在python中,有两种使用正则表达式的方式:
第一种就是直接使用re模块中的函数,正如前面例子演示的那样;
第二种是创建一个特定模式编译的正则表达式对象,然后使用这个对象的方法。
编译的正则表达式:它是一个简单的对象,通过传递模式给re.compile函数创建。
编译和非编译正则表达式,在性能方面是有差异的;如果需要处理的数据量比较大,编译以后的正则表达式处理效率会更好。
编译的正则表达式使用方法如下:
在python中,根据个人喜好选择使用哪种正则表达式的方法,大部分情况下都不会有什么问题。但是如果需要处理的数据量比较大,则使用编译的正则表达式处理性能会更高。例如,我们使用Linux下的seq命令产生1000万个整数保存到文件中,该数据文件大约76M。接下来,对文件的每一行应用模式[0-9]+,并使用linux下的time工具统计程序运行的时间。
非编译的正则表达式版本源码如下:
编译的正则表示版本源码如下:
可以看到:非编译版本的python代码花费了16s,编译版本代码花费了7s。
9.3 常用的re方法
9.3.1 匹配类函数
re模块中的findall函数。
re模块中的match函数类似于字符串中的startswith函数,只是match应用在正则表达式中更强大,更具有表现力。match函数用以匹配字符串的开始部分,如果匹配成功,
则返回一个SRE_Match类型的对象,如果匹配失败,则返回一个None。因此,对于普通的前缀匹配,它的用法和startswith一模一样。例如我们要判断data字符串是否以What和not what开头:
虽然简单使用时match函数和startswith函数类似,但是对于复杂情况时,match函数能够轻易解决,startswith则无能无力。例如我们需要判断一个文本字符是否以一个数字开头。由于我们不知道是哪个数字,只知道要求是数字,因此无法使用startswith函数,这个时候,可以使用re模块的match函数轻松解决,如下:
match函数匹配成功时返回SRE_Match类型的对象,也可以通过该对象获取匹配的字符串:
re模块中的search函数模式匹配成功时,也会返回一个SRE_Match对象。search函数与match函数用法几乎一模一样,区别在于search函数在字符串的任意位置进行匹配,match函数仅在字符串的开始部分进行匹配。他们的共同点是,如果匹配成功,返回SRE_Match对象,如果匹配失败,返回一个None。
前面说过,search仅仅在查找第一次匹配,也就是说,一个字符串中包含了多个模式的匹配,也只返回第一个匹配的结果。如果我们要返回所有的结果应该怎么做呢?返回所有结果的最简单放放风就是用findall函数,除此之外,也可以使用finditer函数。
finditer返回一个迭代器,遍历迭代器可以得到一个SRE_Match对象,如下:
9.3.2 修改类函数
re模块的sub函数类似于字符串的replace函数,只是sub函数支持使用正则表达式。所以,re模块中的sub函数使用场景更加广泛。
如下面的正则表达式,可以同时匹配2.7.13和3.6.0,并将它们都替换为x.x.x,如下:
利用sub函数将下面的日期进行格式化:
re模块的split函数与python字符串的split函数功能一样,都是将一个字符串拆分成子串的列表,区别在于re模块的split函数能够使用正则表达式。例如下面这段包含冒号、逗号、单引号和若干空格的文本,我们希望拆分出每一个单词。面对这个需求python内置的split函数无法进行处理,因此我们可以直接使用re模块的split函数,re模块的split函数可以指定多个分隔符,如下:
说明:\s也是正则表达式,表示匹配任意空白。
9.3.3 大小写不敏感
我们在字符串查找或替换的时候?忽略字符的大小写,如下:
9.3.4非贪婪匹配
贪婪匹配:总是匹配最长的那个字符串,默认使用贪婪匹配;
非贪婪匹配:匹配到那个最小的字符串。
例子:
在下面的这个例子中,我么你要匹配以Beautiful开头并且以点号结尾的字符串。显然,存在两个符合条件的匹配。默认情况下使用贪婪匹配,如果要使用非贪婪匹配,只需要在匹配的字符串上加一个?,如下:
9.3.4 案例:获取HTML页面中的所有超链接
在这个例子中,我们使用开源的requests库获取Hack News的内容,然后使用正则表达式解析出所有的http或者https链接。
由于requests是一个开源项目,而不是标准库,所有,使用之前需要安装:
10 字符集编码
把unicode字符表示为二进制的方法有很多种,最常见的编码方式是UTF-8,但是读者需要注意的是,unicode是编写形式,utf-8是存储形式。UTF-8是使用最广泛的编码,但仅仅是unicode的一种存储形式。使用python处理unicde时,如想把unicode字符转换成二进制数据,可以使用encode(编码)方法;若想把二进制数据转换成unicode字符,可以使用decode(解码)方法。此外,也可以使用pyhon2中的codecs模块和python3中的open函数来指定编码类型。
在python 3中,字符串默认为Unicode,但如果是在python 2中需要使用unicode,则必须在字符串前面显示的加上一个"u"前缀,如下所示:
在python 2中,也可以使用默认的unicode字符串,只需要执行下面的导入即可:
python的字符串具有encode编码和decode解码方法,下面是一个在python 2中的例子:
如果我们想把中文写入文件里面,如果不保证字符集一致会报错,如下:
上面报错的原因是:
因为我们定义了一个unicode字符串'u陈志新'。随后,欧美想把这个字符串保存到文本文件中,由于我们没有指定文本文件的编码,所以默认是ASCII编码,显然unicode表示的汉子是无法使用ASCII编码进行存储的。所以python抛出了UnicodeEncodeError异常。
解决办法,手动编码的方式解决,这是因为一个ASCII文本本身也是一个UTF-8文本。
小知识:unicode是表现形式,utf8是存储形式。
如果需要写入的字符串比较多,而每次都需要进行编码,程序将会变得非常低效,在python2中可以使用codecs模块,在python3内置的open函数已经支持编码格式。指定编码格式后,当我们写入时会自动将unicode转换为特定的编码,读取文件时,会以特定的UTF进行解码:
在python2中,使用codecs模块进行编码(写入文件)和解码(读取文件):
在python3中,内置的open函数可以指定字符集编码:
在python编程中,应该把编码和解码操作放在程序的最外围处理,程序的核心部分都使用unicode,为了在程序核心部分使用unicode,可以在代码中使用下面的辅助函数,函数能接受str或者unicode类型并且返回需要的字符串类型:
python 2的字符集处理辅助函数:
python 3的字符集处理辅助函数:
11、jinja2模板
系统管理员和开发工程师应该经常使用模板来管理配置文件。
渲染:可以使用模板将业务逻辑与页面逻辑分隔开来。模板包含的是一个响应文本的文件,其中包含用占位符变量表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实的值替换变量,再返回最终得到的相应字符串,这一过程称为渲染。
一句话,渲染就是用真实的值替换变量。
python的标准库自带了一个简单的模板,下面的例子就是。不过,Python自带的模板功能非常有限,例如无法在模板中使用控制语句和表达式,不支持继承和重用等操作,这对于web开发来说远远不够,所以才出现了第三方的模板系统,最知名的就是jinja2和Mako。
11.1 jinja2模板语法入门
jinja2是Flask的一个依赖,如果已经安装了Flask,jinja2也会随之安装。当然也可以单独安装jinja2:
[root@localhost ~]# pip install jinja2
[root@localhost ~]# python -c "import jinja2"
11.2 语法块
在Jinja2中,存在三种语法:
a)控制结构{%%}
b)变量取值{}
c)注释{##}
下面是一个使用jinja控制结构和注释的一个例子:
{#note:disable temoate because we no longer use this
{% for user in users %}
....
{%endfor%}
#}
11.3 变量
jinja2模板中使用{{}}语法表示一个变量。jinja2识别所有的python数据类型,甚至是一个复杂的类型,如列表,字典和对象等,如下:
{{ mydict['key'] }}
{{ mylist[3] }}
{{ mylist[myintvar] }}
{{ myobj.somemethod{} }}
11.4 jinja2中的过滤器
过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。
|
jinja2的常用过滤器
|
|
过滤器名
|
说明
|
|
safc
|
渲染时不转译
|
|
capitalize
|
把值的首字母转换成大写,其他字母转换成小写
|
|
lower
|
把值转换为小写形式
|
|
upper
|
把值转换为大写形式
|
|
title
|
把值中的每个单词首字母转换成大写
|
|
trim
|
把值的首尾空格去掉
|
|
striptags
|
渲染之前把值中所有的html标签都删掉
|
|
join
|
拼接多个值为字符串
|
|
replace
|
替换字符串的值
|
|
round
|
默认对数字进行四舍五入,也可以用参数进行控制
|
|
int
|
把值转换为整型
|
在Jinja2中,变量可以通过过滤器进行修改,过滤器和变量之间用管道 | 进行分割。多个过滤器可以链式调用,前一个过滤器的输出会做为后一个过滤器的输入,如下:
{{ "Hello World" | replace("Hello","Goodbye") }}
-->Goodbye World
{{ "Hello World" | replace("Hello","Goodbye") |upper }}
-->GOODYE WORLD
{{ 42.55 | round }}
---> 43.0
{{ 42.55 | round | int }}
---> 43
11.5 jinja2的控制结构
jinja2中的if语句类似于python中的if语句,但是,需要使用endif语句做为条件判断的结束。我们可以使用if语句判断一个变量是否定义,是否为空,是否为真值。与python中的if语句一样,页可以使用elif和else构建分支,如下:
{% if kenny.sick %}
kenny is sick.
{% elif kenny.dead %}
You killed Kenny! You bastard!
{% else %}
Kenny looks ok
{% endif %}
11.6 jinja2的for循环
jinja2中的for语句可以迭代pytho的数据类型,包括列表、元组和字典。在jinja2中不存在while循环,这也符合了jinja2的“仅提供控制结构,不允许在模板中编写太多的业务逻辑,避免工程师乱用的行为”的设计原则。
在jinja2中迭代列表:
在jinja2中也可以遍历字典:
除了基本的for循环使用外,jinja2还提供了一些特殊的变量,我们不用定义就可以直接使用这些变量,如下:
|
变量
|
描述
|
|
loop.index
|
当前循环迭代的次数,从1开始
|
|
loop.index0
|
当前循环迭代的次数,从0开始
|
|
loop.revindex
|
到循环结束的次数,从1开始
|
|
loop.revindex0
|
到循环结束的次数,从0开始
|
|
loop.first
|
如果是第一次迭代,为True,否则为False
|
|
loop.last
|
如果是最后一次迭代,为True,否则为False
|
|
loop.length
|
序列中的项目数
|
|
loop.cycle
|
在一串序列间取值的辅助函数
|
需求:
假设你有一个保存了联系人信息的自动,字典的key是联系人的名字,字典的value是联系人的电话。你现在想把联系人的信息以表格的形式显示在HTML页面上。此时除了姓名和电话外,你希望表格的第一列是序号,这个需要在python的代码中是这样实现的:
jinja2为了防止工程师尽可能在模板中少写python代码处理业务逻辑,仅在模板处理显示工作问题,提供了一些特殊变量。对于上面的例子,在jinja2中正确的做法如下:
11.7 jinja2的宏
宏类似于编程语言宏的函数,它用于将行为抽象成可重复的代码块,与函数一样,宏分为定义和调用。
宏的定义的例子:
通过上面的例子可以看到,使用macro关键字定义一个宏,input是宏的名称。它有三个参数,分别是name,type和value,其中type和value蚕食有默认值。可以看到宏的定义和python中的函数定义非常相似。此外,他与jinja2宏的for循环和if语句一样,不需要使用复合语句的冒号,使用endmacro结束宏的定义。
宏的调用的例子:
11.8 jinja2的继承和Super函数
如果只是用jinja2进行配置文件的管理,将基本用不到jinja2的继承功能。如果是使用Jinja2进行web开发,那么,继承将是jinja2最吸引人的功能。
jinja2中最强大的部分就是模板的继承。模板继承允许你构建一个包含站点共同元素的基本模板“骨架”,并定义子模板可以覆盖的块。
假设我们有一个名为base.html的HTML文档,里面内容如下:
在base.html中,我们使用{% block name %}的方式定义了三个块,这些块可以在子模板中进行替换或调用。
下面是一个名为index,html的HTML文档,文档的内容如下:
在上面的Index.html中,我们使用{% extends "base.html" %}继承base.html,继承以后,base.html中的所有内容都会在index.html中展现。在Index.html中,我们重新定义了title和content这连个块的内容。
Super是继承的意思。
11.9 jinja2的其他运算
jinja2中可以定义变量,为了对变量进行操作,jinja2提供了算数操作、比较操作和逻辑操作。使用jinja2模板时,应该尽可能在python代码中进行逻辑处理,在jinja2中仅处理显示问题。因此,一般很少用到jinja2的变量和比那里的运算操作。
部分jinja2的运算操作:
a)算术运算:+-* % * **
b)比较操作:== != > >= < <=
c) 逻辑操作:not and or
12 jinja2实战
jinja2模块中有一个名为Environment的类,这个类的实例用于存储配置和全局对象,然后从文件系统其他位置加载模板。
大多数应用都在初始化时创建一个Environment对象并用它加载模板。配置jinja2为应用加载文档的最简单方式大概是这样的:
上面的代码会创建一个Environment对象和一个包加载器,该加载器会在yourapplication这个python包的templates目录下查找模板。接下来,只需要以模板的名字做为参数调用Environment.get_template方法即可。该方法会返回一个模板,最后使用模板的render方法进行渲染,如下:
除了使用包加载器外,还可以使用文件系统加载器。文件系统加载器不需要模板位于一个python包下,可以直接访问系统中的文件。为了便于功能演示,我们将在接下来请的例子中使用下面的辅助函数:
备注:
tpl_path:模板路径
**kwargs:需要渲染模板里面的参数
小知识:
如果是函数定义中参数前的*表示的是将调用时的多个参数放入元组中,**则表示将调用函数时的关键字参数放入一个字典中
如定义以下函数
def func(*args):print(args)
当用func(1,2,3)调用函数时,参数args就是元组(1,2,3)
定义以下函数
def func(**args):print(args)
当用func(a=1,b=2)调用函数时,参数args将会是字典{'a':1,'b':2}
12.1 基本功能演示
下面来看一个模板渲染的例子,假设我们存在一个名为simple.html的文本文件,它的内容如下:
在这个HTML模板中,我们使用for循环遍历一个列表,列表中每一个项是一个字典,字典中包含了文字和链接,我们将使用字典中的数据渲染成HTML的超链接。此外,我们还会使用jinja2提供的过滤器trim删掉titile里面的空格。
小知识
a)、locals返回当前作用域 的所有局部变量的变量名:变量值组成的字典。
例如:当前作用域有两个局部变量x=1,y='something'则locals()返回字典
{'x':1,'y':'something'}
b)、**locals()在format函数调用里的意思是将locals()返回的字典解包传递给format函数。如果locals返回的如上面的例子里说的 一样的话,解包就是将{'x':1,'y':'something'}变成x=1,y='something'
执行上面代码,渲染模板的结果为:
12.2 继承功能演示
为了演示继承的功能,我们需要使用两个html文件,分别是base.html和index.html。
base.html内容如下:
index.html的内容如下:
我们使用下面的python代码渲染jinja2模板:
渲染后的结果为:
从这个例子可以看到:
1)、我们渲染的是index.html,并没有直接渲染base.html,但是最后生成的模板中包含了完整的html框架,这也是继承广泛的使用场景;
2)、我们虽然在index.html中定义了title块。但是,因为我们使用了{{super}}引用了base.html中的HEAD块。因此,最后的渲染结果中包含了base.html中的head块和index.html中的head块。例如,最后渲染的结果中title标签的内容是“index -My Webpage”,这个字符串就来自index,html和base.html。
3)、我们在index.html中重新定义了content块的内容,因此,最后生成的文档中在正确的位置显示了content块的内容。
12.3 案例:使用jinja2生成HTML表格和XML配置文件
1、使用jinja2生成html表格
模板hzfc.html:
渲染模板的python代码:
运行结果:
2、使用jinja2生成xml配置文件
应用场景:部署应用可以自动化,但是生成配置文件就比较困难,这是因为配置文件的取值是动态取值的,如果使用shell脚本,只能通过sed进行动态的替换,shell脚本处理这种情况比较复杂,而且容易出错,可读性差。对于这里的需求,最好的方法是使用模板,通过模板渲染的方式,将底层的服务的Ip和端口号写入一个配置文件中,随后用Python代码读取配置文件,渲染配置文件生成新的配置文件。
在本例中,有一个名为base.cfg的配置文件,该文件保存了配置参数的取值:
此外,还有一个名为pass_service1_template.xml的配置模板,模板的内容如下:
在本例中,存在两个上层服务,另外一个上层服务的配置模板名称为pass_service2_template.xml,内容如下:
现在的需求是读入配置文件base.cfg,然后使用jinja2模板渲染技术,将两个上层服务的配置模板pass_service1_template.xml和pass_service2_template.xml渲染成配置文件。相关的python代码如下:
本文使用了一个小技巧,即通过给globals字典赋值的方式定义全局变量,然后将所有的全局变量传递给模板,模板渲染时只会使用到自己需要的变量,渲染完成后会在当前目录下生成两个配置文件,分别是pass_server1.xml和pass_server2.xml。
pass_server1.xml的内容如下:
pass_server2.xml的内容如下:
小知识:
locals 是只读的,不可修改, 而globals可以修改,原因是:
locals()实际上没有返回局部名字空间,它返回的是一个拷贝。所以对它进行修改,修改的是拷贝,而对实际的局部名字空间中的变量值并无影响。
globals()返回的是实际的全局名字空间,而不是一个拷贝: 与 locals 的行为完全相反。
所以对 globals 所返回的 dictionary 的任何的改动都会直接影响到全局变量的取值。
定义一个函数
结果:
{'x': 1, 'arg': 3}
x= 1
{'x': 1, 'arg': 3}
x= 1
{'foo': <function foo at 0x02A17CF0>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'E:\\workspace\\python day03\\main\\test.py', '__package__': None, '__name__': '__main__', 'z': 7, '__doc__': 'This is my first python program!'}
z= 7
{'foo': <function foo at 0x02A17CF0>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'E:\\workspace\\python day03\\main\\test.py', '__package__': None, '__name__': '__main__', 'z': 8, '__doc__': 'This is my first python program!'}
z= 8
(完)