您现在的位置: 主页 > 上位机技术 > python > 下篇
本文所属标签:
为本文创立个标签吧:

下篇

来源:网络整理 网络用户发布,如有版权联系网管删除 2018-08-13 


十四、字符串格式化(String Formatting)


在许多编程语言中都包含有格式化字符串的功能,比如C语言中的格式化输入输出。Python中内置有对字符串进行格式化的操作符 "%" 以及str.format()方法。


1、操作符 "%"

Python中的 "%" 操作符和C语言中的sprintf类似。简单来说,使用 "%" 来格式化字符串的时候,你需要提供一个字符串模板和用来插入的值。模板中有格式符,这些格式符为真实值预留位置,并说明真实数值应该呈现的格式。Python用一个tuple将多个值传递给模板,每个值对应一个格式符。注意:给定的值一定要和模板中的格式符一一对应!

name = "xianglong"
messages = 3
text = ("Hello %s, you have %i messages" % (name, messages))
print text
# Output: Hello xianglong, you have 3 messages

在上面的例子中,"Hello %s, you have %i messages" 是字符串模板。%s为第一个格式符,表示一个字符串。%i为第二个格式符,表示一个十进制整数。(name, messages)的两个元素为替换%s和%i的真实值。在模板和tuple之间,有一个%号分隔,它代表了格式化操作。

常用的格式符如下:

格式描述
%%百分号 % 标记
%s字符串 (采用str()的显示)
%r字符串 (采用repr()的显示)
%c字符及其ASCII码
%b二进制整数
%d十进制整数 (有符号整数)
%u十进制整数 (无符号整数)
%i十进制整数 (有符号整数)
%o八进制整数 (无符号整数)
%x十六进制整数 (无符号整数)
%X十六进制整数 (无符号整数)
%e指数 (基底写为e)
%E指数 (基底写为E)
%f浮点数
%F浮点数,与上相同
%g指数(e)或浮点数 (根据显示长度)
%G指数(E)或浮点数 (根据显示长度)
%p指针(用十六进制打印值的内存地址)
%n存储输出字符的数量放进参数列表的下一个变量中


使用操作符 "%" 也可以通过字典格式化字符串:

values = {"name": name, "messages": messages}
print ("Hello %(name)s, you have %(messages)i messages" % values)
# Output: Hello xianglong, you have 3 messages

上面的代码中,我们指定了用来格式化的值的名字,然后可以根据name在字典中查找相应的value。其实,上面的"name"和"messages"已经在local命名空间中定义,所以,我们可以利用这一点:

print ("Hello %(name)s, you have %(messages)i messages" % locals())

locals()方法返回一个包含所有本地变量的字典。这个功能非常强大,你可以不必担心提供的values是否和模板匹配;但是同时这个也是非常危险的:你将会暴露整个本地命名空间给调用者,这一点需要你注意。

在Python中,对象有一个__dict__属性,你可以在格式化字符串的时候使用;

print ("We found %(error_count)d errors" % self.__dict__)
# 等同于
print ("We found %d errors" % self.error_count)


另外,我们还可以用如下的方式,对字符串格式化进一步的控制:%[(name)][flags][width].[precision]typecode,其中:

(name)为命名

flags可以有+,-," "或0。+表示右对齐。-表示左对齐。" "为一个空格,表示在正数的左侧填充一个空格,从而与负数对齐。0表示使用0填充。

width表示显示宽度

precision表示小数点后精度

比如:

print("%+10x" % 10) # +a
print("%04d" % 5) # 0005
print("%6.3f" % 2.3) # 2.300

上面的width, precision为两个整数。我们可以利用*,来动态代入这两个量。比如:

print("%.*f" % (4, 1.2)) # 1.2000

Python实际上用4来替换*。所以实际的模板为"%.4f"。


2、str.format()方法

str.format()方法是在Python 2.6中引入的,它通过 {} 和 : 来代替 % ,功能非常强大。具体的用法见下面的例子:

In [1]: name = "xianglong"
In [2]: messages = 4
# 通过位置
In [3]: "Hello {0}, you have {1} messages".format(name, messages)
Out[3]: "Hello xianglong, you have 4 messages"
# 通过关键字参数
In [4]: "Hello {name}, you have {messages} messages".format(name=name, messages=messages)
Out[4]: "Hello xianglong, you have 4 messages"
# 通过下标
In [5]: "Hello {0[0]}, you have {0[1]} messages".format([name, messages])
Out[5]: "Hello xianglong, you have 4 messages"
# 格式限定符:填充与对齐
# ^、<、>分别是居中、左对齐、右对齐,后面带宽度
# :号后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充
In [6]: "Hello {0:>14}, you have {1:>14} messages".format(name, messages)
Out[6]: "Hello xianglong, you have 4 messages"
# 格式限定符:精度与类型f
In [7]: "{:.2f}".format(321.33345)
Out[7]: "321.33"
# 格式限定符:b、d、o、x分别是二进制、十进制、八进制、十六进制
In [8]: "{:b}".format(14)
Out[8]: "1110"
In [9]: "{:d}".format(14)
Out[9]: "14"
In [10]: "{:o}".format(14)
Out[10]: "16"
In [11]: "{:x}".format(14)
Out[11]: "e"
# 格式限定符:千位分隔符
In [12]: "{:,}".format(1234567890)
Out[12]: "1,234,567,890"

更多关于Python字符串格式化的介绍,可以参看:PEP 3101 -- Advanced String Formatting


十五、迭代器(List comprehensions)


List Comprehensions即迭代器(列表生成式),是Python内置的非常简单却强大的可以用来创建list的生成式。在不使用迭代器的时候,创建一个新列表可以使用for和if来实现:

new_list = []
for item in a_list:
if condition(item):
new_list.append(fn(item))

使用迭代器的话:

new_list = [fn(item) for item in a_list if condition(item)]

列表生成式非常简洁的,不过是在某种程度上。你可以在列表生成式中使用多个for循环和多个if语句,但是两个以上的for和if语句会让列表生成式非常复杂,这时候建议直接用for循环。根据Zen of Python,选择更容易读的方式。下面是一些例子:

>>> [n ** 2 for n in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> [n ** 2 for n in range(10) if n % 2]
[1, 9, 25, 49, 81]


十六、生成器(Generator & Generator expressions)


先出一个题:计算1 ~ 100的平方和。最简单的方法就是使用一个for循环:

total = 0
for num in range(1, 101):
total += num * num

其实,我们可以使用Python内置的sum方法计算:

# 迭代器(列表生成式)
total = sum([num * num for num in range(1, 101)])
# 生成器
total = sum(num * num for num in xrange(1, 101))

生成器和上面提到的迭代器差不多,可以说:生成器是一种特殊的迭代器;但是它们之间有一个很大的区别:迭代器是贪婪的,而生成器是懒惰的,具体来说:迭代 器会一次性的计算出整个结果列表,而生成器只在需要的时候计算一个值。这个特性在列表非常大,或者需要一步一步计算的时候非常有用。

在上面的例子中,我们只需要平方和,不需要平方的list,所以我们使用生成器xrange。如果我们计算1 ~ 1000000000的平方和,使用迭代器的话会内存溢出,但是生成器却不会:

total = sum(num * num for num in xrange(1, 1000000000))

在语法上,迭代器会有一个"[]",但是生成器没有;不过有时候,生成器需要"()",所以,最好每次都带上。一些自定义的生成器例子:

# 过滤CSV文件中的空行
def filter_rows(row_iterator):
for row in row_iterator:
if row:
yield row
data_file = open(path, "rb")
irows = filter_rows(csv.reader(data_file))
# 文件读取:open
datafile = open("datafile")
for line in datafile:
do_something(line)


PS:原文中作者举了一些工作中的例子,这里不再赘述,想了解的可以到原文链接中查看。


十七、排序(Sorting)


在Python中对列表排序非常简单,比如:

In [1]: a_list = ["Tommy", "Jack", "Smith", "Paul"]
In [2]: a_list.sort()
In [3]: a_list
Out[3]: ["Jack", "Paul", "Smith", "Tommy"]

需要注意的是:list的sort()方法会直接在原list变量上排序,改变原本的list对象,并且该方法不会返回一个list对象。如果你需要不改变原list,并且返回新的list对象的话,可以使用Python的orted方法:

In [1]: a_list = ["Tommy", "Jack", "Smith", "Paul"]
In [2]: b_list = sorted(a_list)
In [3]: b_list
Out[3]: ["Jack", "Paul", "Smith", "Tommy"]
In [4]: a_list
Out[4]: ["Tom", "Jack", "Smith", "Paul"]

但是,如果你想对一个list进行排序,不过不想使用默认的排序方式,比如你可能需要先根据第二行排序,再根据第四行排序。这时候,我们也可以使用sort()方法,但是得提供一个自定义的排序方法:

In [1]: def custom_cmp(item1, item2):
...: return cmp((item1[1], item1[3]), (item2[1], item2[3]))
...:
In [2]: a_list = ["Tommy", "Jack", "Smith", "Paul"]
In [3]: a_list.sort(custom_cmp)
In [4]: a_list
Out[4]: ["Jack", "Paul", "Smith", "Tommy"]

这种方法可以实现,但是在list比较大的情况下效率很低。下面介绍两种其他的方法。

1、DSU排序方法

DSU即Decorate-Sort-Undecorate,中文就是"封装-排序-解封"。DSU方法不会创建自定义的排序方法,而是创建一个辅助的排 序列表,然后对这个列表进行默认排序。需要说明的是:DSU方法是一种比较老的方法,现在已经基本上不使用了,不过这里还是给出一个简单的例子说明一下:

# Decorate:
to_sort = [(item[1], item[3], item) for item in a_list]
# Sort:
to_sort.sort()
# Undecorate:
a_list = [item[-1] for item in to_sort]

上述代码第一行创建了一个tuple的list,tuple中的前两项是用来排序的字段,最后一项是原数据;第二行使用sort()方法对辅助的list进行默认的排序;最后一行是从已经排序的辅助list中获取原数据,重新组成list。

这种方法是使用复杂度和内存空间来减少计算时间,比较简单,也比较快,但是我们得复制原列表的数据。

2、KEY方法

自从Python 2.4之后,list.sort()和sorted()都添加了一个key参数用来指定一个函数,这个函数作用于每个list元素,在做cmp之前调用。key参数是一个函数,这个函数有一个参数,返回一个用来排序的关键字。这种排序方法很快,因为key方法在每个输入的record上只执行一次。你可以使用Python内置的函数(len, str.lower)或者自定义函数作为key参数,下面是一个简单的例子:

In [1]: a_list = ["Tommy", "Jack", "Smith", "Paul"]
In [2]: def my_key(item):
...: return (item[1], item[3])
...:
In [3]: a_list.sort(key=my_key)
In [4]: a_list
Out[4]: ["Jack", "Paul", "Smith", "Tommy"]


十八、EAFP vs LBYL


检查数据可以让程序更健壮,用术语来说就是防御性编程。检查数据的时候,有EAFP和LBYL两种不同的编程风格,具体的意思如下:

LBYL: Look Before You Leap,即事先检查;

EAFP: It"s Easier to Ask Forgiveness than Permission,即不检查,出了问题由异常处理来处理。

异常处理总是比事先检查容易,因为你很难提前想到所有可能的问题。所以,一般情况下编码时会倾向使用EAFP风格,但它也不是适应所有的情况。两个风格的优缺点如下:

d = {}
words = ["a", "d", "a", "c", "b", "z", "d"]
# LBYL
for w in words:
if w not in d:
d[w] = 0
d[w] += 1
# EAFP
for w in words:
try:
d[w] += 1
except KeyError:
d[w] = 1

对于LBYL,容易打乱思维,本来业务逻辑用一行代码就可以搞定的。却多出来了很多行用于检查的代码。防御性的代码跟业务逻辑混在一块降低了可读性。而EAFP,业务逻辑代码跟防御代码隔离的比较清晰,更容易让开发者专注于业务逻辑。不过,异常处理会影响一点性能。因为在发生异常的时候,需要进行保留现场、回溯traceback等操作。但其实性能相差不大,尤其是异常发生的频率比较低的时候。

另外,需要注意的是,如果涉及到原子操作,强烈推荐用EAFP风格。比如我某段程序逻辑是根据redis的key是否存在进行操作。如果先if exists(key),然后do something。这样就变成2步操作,在多线程并发的时候,可能key的状态已经被其他线程改变了。而用EAFP风格则可以确保原子性。

PS:在使用EAFP风格捕获异常时,尽量指明具体的异常,不要直接捕获Exception。否则会捕获到其他未知的异常,如果有问题,你会很难去定位(debug)。


十九、引用(Importing)


Python中的引用:

from module import *

你可能在其他地方见过这种使用通配符*的引用方式,可能你也比较喜欢这种方式。但是,这里要说的是:请不要使用这种引用方式!

通配符引用的方式属于Python中的阴暗面,这种方式会导致命名空间污染的问题。你可能会在本地命名空间中得到意想不到的东西,而且这种方式引入的变量 可能将你在本地定义的变量覆盖,在这种情况下,你很难弄清楚变量来自哪里。所以,通配符引用的方式虽然方便,但可能会导致各种各样奇怪的问题,在 Python项目中尽量不要用这种方式。

在Python中,大家比较认同的import方式有以下几个规则:

1、通过模块引用变量(Reference names through their module)

这种方式直接import的是模块,然后通过模块访问其中的变量,Class和方法。使用这种方式可以很清晰的知道变量来自哪里:

import module
module.name

2、模块名比较长时使用短名字(alias)

import long_module_name as mod
mod.name

3、直接引用你需要的变量名

from module import name
name


二十、模块与脚本(Modules & Scripts)


为了使一个Python文件既可以被引用,又可以直接执行,你可以在Python文件中加上这样的代码:

if __name__ == "__main__":
# script code here

当被引用时,一个模块(module)的__name__属性会被设置为该文件的文件名(不包括.py后缀)。所以,上面代码片段中if语句中的脚本在被 引用的时候不会执行。当把Python文件作为一个脚本执行的时候,__name__属性则被设置为"__main__",这时候if语句中的脚本才会被 执行。

最好不要在Python文件中直接写可执行的语句,应该将这些代码放在方法或类里面,必要的时候放在"if __name__ == "__main__":"中。一个Python文件的结构可以参考下面的:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" 文档 module docstring """
# 引用 imports
# 常量 constants
# 异常 exception classes
# 方法 interface functions
# 类 classes
# 内部方法和类 internal functions & classes
def main(...):
...
if __name__ == "__main__":
status = main()
sys.exit(status)


二十一、命令行解析(Commend-Line Processing)


Python是一种脚本语言,有时候我们会直接在命令行运行Python文件,这时候可能需要解析命令行传入的参数,下面是一个例子:cmdline.py

#!/usr/bin/env python
"""
Module docstring.
"""
import sys
import optparse
def process_command_line(argv):
"""
Return a 2-tuple: (settings object, args list).
`argv` is a list of arguments, or `None` for ``sys.argv[1:]``.
"""
if argv is None:
argv = sys.argv[1:]
# initialize the parser object:
parser = optparse.OptionParser(
formatter=optparse.TitledHelpFormatter(width=78),
add_help_option=None)
# define options here:
parser.add_option( # customized description; put --help last
"-h", "--help", action="help",
help="Show this help message and exit.")
settings, args = parser.parse_args(argv)
# check number of arguments, verify values, etc.:
if args:
parser.error("program takes no command-line arguments; "
""%s" ignored." % (args,))
# further process settings & args if necessary
return settings, args
def main(argv=None):
settings, args = process_command_line(argv)
# application code here, like:
# run(settings, args)
return 0 # success
if __name__ == "__main__":
status = main()
sys.exit(status)


二十二、包(Packages)


Python中包的设计与引用规则,包的设计例子:

package/
__init__.py
module1.py
subpackage/
__init__.py
module2.py

建议使用上面的方式来组织你的项目,尽量减小引用路径,明确引用对象,避免引用冲突。引用示例:

import package.module1
from package.subpackage import module2
from package.subpackage.module2 import name

我们可以通过__future__模块使用Python 3.0的功能:absolute_import。方法如下:

from __future__ import absolute_import

简单介绍一下相对引入和绝对引入的概念:

相对导入:在不指明 package 名的情况下导入自己这个 package 的模块,比如一个 package 下有 a.py 和 b.py 两个文件,在 a.py 里 from . import b 即是相对导入 b.py。

绝对导入:指明顶层 package 名。比如 import a,Python 会在 sys.path 里寻找所有名为 a 的顶层模块。

引入absolute_import之后不是支持了绝对引入,而是拒绝相对引入。


简单比复杂好


调试程序的难度是写代码的两倍。因此,只要你的代码写的尽可能的清楚,那么你在调试代码时就不需要那么地有技巧。(Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. ) -- Brian Kernighan。所以,尽量保持你的程序足够简单。


不要重复造轮子


在你写代码之前,你需要先看一下有没有其他人已经实现了类似的功能。你可以从下面的几个地方寻找:

1、Python标准库

2、Python第三方LIB, PYPI(Python Package Index),地址:PYPI

3、搜索引擎,Google,百度等。。


Over!

本文地址:http://xianglong.me/article/how-to-code-like-a-pythonista-idiomatic-python/, 点击阅读原文可查看地址

特别声明:本站文章,如非注明,皆为降龙原创。转载需注明本文链接并保证链接可用。




              查看评论 回复

游客   2018-09-02 12:02:20
python威武
1楼 回复本楼


 使用 一个 方法

网站地图

围观()