一、问题
Python中标准异常集包含的内容已经相当广泛,但有时开发中还须创建自己的异常,比如在特定的标准异常和模块异常中添加额外的信息。
本例中两个异常都与IOError有关,IOError是一个用于输入/输出的通用异常,可能在无效的文件访问或其他形式的通信中触发。
二、解决
1、创建自定义异常代码
#!/usr/bin/env python
'''
$Id$
myexc.py -- "my exceptions" demo which highlights user-created
exceptions. NOTE: this example does not currently work with
JPython as neither the errno nor tempfile modules have been
implemented, and also, the socket module is incomplete.
'''
# import all our needed modules
import os, socket, errno, types, tempfile
# create our a new NetworkError exception, derived from IOError
class NetworkError(IOError):
pass
# create our a new FileError exception, derived from IOError
class FileError(IOError):
pass
# updArgs --> tuple
def updArgs(args, newarg=None):
'''updArgs(args, newarg=None) -- if instance, grab each exception
instance argument and place them in a list; otherwise, just
convert given args sequence to a list for mutability; add
newarg if necessary; then convert the whole thing to a tuple.'''
if isinstance(args, IOError):
myargs = []
myargs.extend([arg for arg in args])
else:
myargs = list(args)
if newarg:
myargs.append(newarg)
return tuple(myargs)
# fileArgs --> tuple
def fileArgs(fn, mode, args):
'''fileArgs(fn, mode, args) -- similar to updArgs() except made
specifically for files; creates small permission string and
formats error to be similar to other IOError exceptions.'''
if args[0] == errno.EACCES and \
'access' in dir(os):
perms = ''
permd = { 'r': os.R_OK, 'w': os.W_OK, \
'x': os.X_OK }
pkeys = permd.keys()
pkeys.sort()
pkeys.reverse()
for eachPerm in 'rwx':
if os.access(fn, permd[eachPerm]):
perms = perms + eachPerm
else:
perms = perms + '-'
if isinstance(args, IOError):
myargs = []
myargs.extend([arg for arg in args])
else:
myargs = list(args)
myargs[1] = "'%s' %s (perms: '%s')" % \
(mode, myargs[1], perms)
myargs.append(args.filename)
else:
myargs = args
return tuple(myargs)
# myconnect() --> None (raises exception on error)
def myconnect(sock, host, port):
'''myconnect(sock, host, port) -- attempt to make a network connection
with the given socket and host-port pair; raises our new NetworkError
exception and collates error number and reason.'''
try:
sock.connect((host, port))
except socket.error, args:
myargs = updArgs(args) # convert inst to tuple
if len(myargs) == 1: # no #s on some errors
myargs = (errno.ENXIO, myargs[0])
raise NetworkError, \
updArgs(myargs, host + ':' + str(port))
# myopen() --> file object
def myopen(fn, mode='r'):
'''myopen(fn, mode) -- wrapper around the open() built-in function
such that we raise our new FileError exception on an error situation
and collate a set of FileError exception arguments to pass to the user'''
try:
fo = open(fn, mode)
except IOError, args:
raise FileError, fileArgs(fn, mode, args)
return fo
# testfile() --> None
def testfile():
'''testfile() -- runs the file tester, setting a variety of test files
which should generate FileError exceptions'''
fn = tempfile.mktemp() #make temp file and path
f = open(fn, 'w')
f.close()
for eachTest in ((0, 'r'), (0100, 'r'), (0400, 'w'), (0500, 'w')):
try:
os.chmod(fn, eachTest[0])
f = myopen(fn, eachTest[1])
except FileError, args:
print "%s: %s" % \
(args.__class__.__name__, args)
else:
print fn, "opened ok... perms ignored"
f.close()
os.chmod(fn, 0777)
os.unlink(fn)
# testnet() --> None
def testnet():
'''testfile() -- runs the network tester, making various connections
which should generate NetworkError exceptions'''
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
for eachHost in ('127.0.0.1', 'www'):
try:
myconnect(s, eachHost, 80)
except NetworkError, args:
print "%s: %s" % (args.__class__.__name__, args)
else:
print "network connection successful to", `eachHost`
s.close()
# run tests if invoked as a script
if __name__ == '__main__':
testfile()
testnet()
程序自定义了两个新的异常类FileError与NetworkError,基类都是IOError,也重新实现了两个诊断版的函数open()[myopen()]和socket.connect([myconnect()],同时包含了一个在直接运行文件时执行的测试函数[test()]。
2、运行结果图
(centos6.5下运行结果:)
myconnect()仅仅是简单的对套接字的函数conect()进行包装,当网络连接失败时提供一个IOError类型的异常,和一般的socket.error不一样,还提供给程序员主机名和端口号。当失败发生时,错误号和错误字符很有帮助,但是如果结合更精确的主机-端口会更有帮助,因为这一对可能是由某个数据库或名称服务动态生成或重新获得。这些值由connect()加入。另一种情形是无法找到主机,socket.error异常没有直接提供的错误号;为了遵循IOError协议,提供了一个错误号-错误字符串对;查找最接近的错误号,选用的是ENXIO。
myopen()封装了已经存在的一些代码,这里用的是open()函数,仅仅捕捉IOError异常。所有的其他都忽略并传给下一层(因为没有与它们相关的处理器)。一旦捕捉到IOError就引发自定义的异常并通过 fileArgs()返回值来定制参数。
上述分别是linux下使用不同的用户运行的结果,root用户拥有操作文件的所有权限。
三、总结
(1)可以总结一个通用的异常模块,包括常见的异常信息提示,这样在项目开发中可以直接导入,重复利用。
(2)raise传递异常,对定位错误信息有很大的帮助,并且可以加入自己喜欢的样式便于调试。
(3)若有更好的设计或思路,请留言,在此先感谢!