建议7.将常量集中到一个文件
Python的内建命名空间是支持一小部分常量的,如True,False,None.只是Python没有提供定义常量的直接方式而已.那么在Python中应该如何使用常量呢?一般来说有以下两种方法:
1.通过命名风格来提醒使用者该变量代表的意义为常量.如常量名所有字母大写,用下划线连接各个单词,如MAX_OVERFLOW,TOTAL.而这种方式并没有实现真正的常量,其对应的值仍然可以改变,这只是一种约定俗成的风格.
2.通过自定义的类实现常量功能.这要求符合”命名全部为大写”和”值一旦绑定便不可再修改”这两个条件.下面是一种较为常见的解决方法.它通过对常量对应的值进行修改时或者命名不符合规范时抛出异常来满足以上常量的两个条件.
class _const:
class ConstError(TypeError):pass
class ConstCaseError(ConstError):pass
def __setattr__(self, name, value):
if self.__dict__.has_key(name):
raise self.ConstError,"Can't change const.%s" % name
if not name.isupper():
raise self.ConstCaseError, \
'const name "%s" is not all uppercase' % name
self.__dict__[name] = value
import sys
sys.modules[__name__]=_const()
如果上面的代码对应的模块名为const,使用的时候只需要import const,便可以直接定义常量了.如一下代码:
import const
const.COMPANY = "IBM"
上面的代码中常量一旦赋值便不可再更改,因此const.COMPANY=”SAP”会抛出const.ConstError:异常,而常量名称如果小写,如const.name=”Python”,也会抛出const.ConstCaseError异常.
无论采用哪一种方式来实现常量,都提倡将常量集中到一个文件中,因为这样有利于维护,一旦需要修改常量的值,可以集中统一进行而不是逐个文件去检查.采用第二种方式实现的常量可以这么做:将存放常量的文件命名为constant.py,并在其中定义一系列常量.
class _const:
class ConstError(TypeError):pass
class ConstCaseError(ConstError):pass
def __setattr__(self, name, value):
if self.__dict__.has_key(name):
raise self.ConstError, "Can't change const.%s" %name
if not name.isupper():
raise self.ConstCaseError, \
"const name "%s" is not all uppercase" %name
self.__dict__[name] = value
import sys
sys.modules[__name__] = _const()
import const
const.MY_CONSTANT = 1
const.MY_SECOND_CONSTANT = 2
const.MY_THIRD_CONSTANT = "a"
const.MY_FORTH_CONSTANT = "b"
from constant import const
print const.MY_SECOND_CONSTANT
print const.MY_THIRD_CONSTANT*2
print const.MY_FORTH_CONSTANT+"5"
建议8. 利用assert语句来发现问题.
断言(assert)在很多语言中都存在,它主要为调试程序服务,能够快速方便地检查程序的异常或者发现不恰当的输入等.可以防止意想不到的情况出现.
基本语法如下:
assert expression1 [",", expression2]
其中计算expression 1的值会返回True 或者 False,当值为False的时候会引发AssertionError, 而expression2是可选的,常用来传递具体的异常信息.
来看一个简单的使用例子.
x = 1
y = 2
assert x == y ,”not equals”
Traceback (most recent call last):
File “”,line 1, in
AssertionError: not equals
在执行过程中它实际相当于如下代码:
x = 1
y = 2
if debug and not x == y:
… raise AssertionError(“not equals”)
…
Traceback (most recent call last):
File “”,line2,in
AssertionError:not equals
对python使用断言需要说明如下:
1) debug的值默认设置为True,且是只读的,且在python2.7中还无法修改该值.
2) 断言是有代价的,它会对性能产生一定的影响,对于编译型的语言,如c/c++,这也许并不那么重要.因为断言只在调试模式下启用.但Python并没有严格定义调试和发布模式之间的区别,通常禁用断言的方法是在运行脚本的时候加上-O标志,这种方式带来的影响是它并不优化字节码,而是忽略与断言相关的语句.如:
def foo(x):
assert x
foo(0)
运行python asserttest.py如下:
Traceback (most recent call last):
File "asserttest.py",line 4,in <module>
foo(0)
File "asserttest.py",line 2,in foo
assert x
AssertionError
加上 -O的参数: python -O asserttest.py便可以禁用断言.
断言实际上是被设计用来捕获用户所定义的约束的,而不是用于捕获程序本身错误的,因此使用断言需要注意一下几点:
1) 不要滥用,这是使用断言最基本的原则.若是由于断言引发了异常,通常代表程序中存在bug,因此断言应该使用在正常逻辑不可到达的地方或正常情况下总是为真的场合.
2) 如果Python本身的异常能够处理就不要再使用断言.如对于类似于数组越界,类型不匹配,除数为0之类的错误,不建议使用断言来进行处理.下面的例子中使用断言就显得多余,因为如果传入的参数一个为字符串,另一个为数字或者列表,本身就会抛出TypeError.
def stradd(x,y):
… assert isinstance(x,basestring)
… assert isinstance(y,basestring)
… return x+y
3) 不要使用断言来检查用户的输入.最好用条件判断.并在不符合条件的时候输出错误提示信息.
4) 在函数调用后,当需要确认返回值是否合理时,可以使用断言.
5) 当条件是业务逻辑继续下去的先决条件时可以使用断言.如list1和其副本list2,业务继续下去的条件是这两个list必须是一样的,但是由于某些不可控因素,如使用了浅拷贝而list1中含有可变对象等,就可以使用断言来判断这两者的关系,如果不相等,则继续运行后面的程序意义不大.
建议9: 数值交换值的时候不推荐使用中间变量.
x,y = y,x
这种方法更优一点,一般情况下,Python表达式的计算顺序是从左到右的,表达式赋值的时候表达式右边的操作数先于左边的操作数计算.因此表达式expr3,expr4 = expr1,expr2的计算顺序是expr1,pxpr2->expr3, expr4.因此对于表达式x,y = y,x,其在内存中执行的顺序如下:
1)先计算右边的表达式y,x,因此先在内存中创建元祖(y,x),其标识符和值分别是y,x及其对应的值.其中y和x是在初始化时已经存在与内存中的对象.
2)计算表达式左边的值并进行赋值.元祖被依次分配给左边的标识符,通过解压缩,元祖第一个标识符(为y)分配给左边第一个元素(此时为x),元祖第二个标识符(为x)分配给第二个元素(此时为y),从而达到x,y值交换的目的.