Python24中使用urllib时遇到IOError的正确打开方式


【问题背景描述】

前段时间上线项目遇到很多“未知原因导致的登录失败“,其实所谓”未知原因“不过是前后台各种捕获后无进一步处理的异常的一个提示信息,一般没有后续处理的异常告诉用户具体的信息感觉意义不大,也就全部归结为未知异常了。

不过在运营商的墙裂要求下,开始对客户端和服务器端登录异常处理进行更细化的处理。

之前一直注意到过的,但一直没抽时间去认真去分析的一个后台异常。

这个异常是在使用urllib进行一个远程HTTP服务API调用时偶尔会抛出的,从日志上看到的大概是这样的情况:

Traceback (most recent call last):
  File "C:\Users\constant\bending\error.py", line 6, in ?
    a = urllib.urlopen("<a target=_blank href="http://xxx.yyy.zz/">http://xxx.yyy.zz/</a>")
  File "C:\Python24\lib\urllib.py", line 77, in urlopen
    return opener.open(url)
  File "C:\Python24\lib\urllib.py", line 180, in open
    return getattr(self, name)(url)
  File "C:\Python24\lib\urllib.py", line 296, in open_http
    h.endheaders()
  File "C:\Python24\lib\httplib.py", line 794, in endheaders
    self._send_output()
  File "C:\Python24\lib\httplib.py", line 675, in _send_output
    self.send(msg)
  File "C:\Python24\lib\httplib.py", line 642, in send
    self.connect()
  File "C:\Python24\lib\httplib.py", line 626, in connect
    raise socket.error, msg
IOError: [Errno socket error] (10061, 'Connection refused')

作为一名python出血者,这样的堆栈信息一开始让我萌萌哒了很久偷笑,我天然呆地一直不敢确定该异常的类型。

而每次等我企图去试着捕获它的时候,它又再也不会出现了,于是它也义无反顾的成为了”未知异常“的一员。

 

【研究问题】

我研究过程中走的弯路,总结起来主要有一下几点:

  • 不能确定异常的准确类型
  • 不知道捕获到异常对象有些啥属性

这次有时间便仔细研究了一下这个异常,并想了个办法来重现它。

Sokect的连接拒绝异常一般是由于端口存在,但是协议不支持导致的,也就是TCP连接握手的时候发现服务器不搭理你。

最简单的模拟当然是用不正确的协议访问一个存在的接口,我依然的访问我本地的80端口,这个接口很常用,一般稍不注意都会有”流氓“监听着它。

看测试代码:

#!/usr/bin/python

import sys
import urllib

try:
	a = urllib.urlopen("http://127.0.0.1")
	b = a.read()
except Exception, e:
	info = sys.exc_info()
	print e    #打印捕获的异常对象
	print info #打印异常的完整数据

这个异常完美重现了,由于我并没有搭建本地的web服务器,urllib的open函数直接抛出了异常。

以下是输出:

[Errno socket error] (10061, 'Connection refused')
(<class exceptions.IOError at 0x015C4750>, <exceptions.IOError instance at 0x0170AF30>, <traceback object at 0x0170AF08>)

 这两行输出可以看出以下2点:

第一:捕获到的异常对象”e“它要么就是个字符串,要么就是个对象实例;

第二:sys.exc_info返回的是个tuple。(之前一直抄的代码,看到前辈使用了这个函数,却没有仔细看过它到底放回了些啥,萌新就是萌啊偷笑)

我不太清楚python异常处理的方式,根据之前各种其他语言的经验,我80%确定"e"应该是异常对象,那么它的类型就是决定是否能准确捕获它的关键了,我当时是想单独捕获它然后再抛一个客户端认识的异常使得客户端能够显示正确的错误提示,而此时此刻我还不清楚exc_info函数返回的东东和e有啥关系。

于是我修改了代码来打印e的类型以及exc_info这个tupe里面三个物体的类型信息。

#!/usr/bin/python

import sys
import urllib

try:
	a = urllib.urlopen("http://127.0.0.1")
	b = a.read()
except Exception, e:
	info = sys.exc_info()
	print "type(e)=%s" % type(e)             #打印e的类型
	print "type(info[0])=%s" % type(info[0]) #打印info[0]的类型
	print "type(info[1])=%s" % type(info[1]) #打印info[1]的类型
	print "type(info[2])=%s" % type(info[2]) #打印info[2]的类型

输出如下:

type(e)=<type 'instance'>
type(info[0])=<type 'classobj'>
type(info[1])=<type 'instance'>
type(info[2])=<type 'traceback'>

看到这样的结果真是太TM给力了惊恐,啥玩意儿啊,“instance”和“classobj”是要闹哪样啊?!坑爹喃?好吧我至少知道你特么不是str了。

【支线任务1】

于是我随便学习了一下python的对象结构和异常抛出机制,网上文档很多,并结合我对JavaScript的理解心领神会如下:

python的类本身是个对象,type出来就是classobj,而除了个别内置类型的类对象的实例被type出来就叫instance。


 此处省略1w字(我根据堆栈去查阅了C:\Python24\lib\httplib.py的源码),说说最终我得出的几点结论吧:

  1. e和info[1]是同一个东西,而且它是python内置异常IOError的实例(instance);
  2. info[0]是IOError类对象(classobj);
  3. info[2]这是个内置的堆栈类型,可以用来打印堆栈信息。

证据如下:

#!/usr/bin/python

import sys
import urllib

try:
	a = urllib.urlopen("http://127.0.0.1")
	b = a.read()
except Exception, e:
	info = sys.exc_info()
	print "str(e)=%s" % str(e)              #字符串打印e  
	print "str(info[0])=%s" % str(info[0])  #字符串打印info[0] 
	print "str(info[1])=%s" % str(info[1])  #字符串打印info[1] 
 	print "str(info[2])=%s" % str(info[2])  #字符串打印info[2] 
	print "e == info[1] is %s" % (e == info[1]) #比较e和info[0]
	print "e is a IOError is %s" % (isinstance(e, IOError)) #判定e是一个IOError的实例

输出如下:

str(e)=[Errno socket error] (10061, 'Connection refused')
str(info[0])=exceptions.IOError
str(info[1])=[Errno socket error] (10061, 'Connection refused')
str(info[2])=<traceback object at 0x0169AF58>
e == info[1] is True
e is a IOError is True

铁证如山啊!大人明鉴!偷笑

可以看到IOError对象e一定是从在过__str__函数,字符串化后的结果是:

[Errno socket error] (10061, 'Connection refused')

肿么看着是个socket异常啊,说好的IOError喃?

 

【支线任务2】

研究了python内置异常对象的数据结构:

这个异常对象字符串化是这样的结构 [Errno <errno>] <strerror>

这里我不得不提的一个我犯的错误理解,我一度以为异常对象存在假tuple的现象,因为它可以用数组下标的方式来访问,但是却不能用len判断长度,同时我一度以为它没有其他访问其属性方式。

 

参考以下这段代码:

#!/usr/bin/python

import sys
import urllib

try:
	a = urllib.urlopen("http://127.0.0.1")
	b = a.read()
except Exception, e:
	print "e[0] = %s" % e[0]  #使用数组方式获取e中第一个元素
	print "e[1] = %s" % e[1]  #使用数组方式获取e中第二个元素
	print "e is a tuple is %s" % isinstance(e, tuple) #判断e是不是一个tuple
	print "len(e) = %s" % len(e) #试图判断e的长度

输出:

e[0] = socket error
e[1] = (10061, 'Connection refused')
e is a tuple is False
Traceback (most recent call last):
  File "C:\Users\constant\bending\error.py", line 13, in ?
    print "len(e) = %s" % len(e)
AttributeError: IOError instance has no attribute '__len__'

从输出可见,这个e可以通过数组下标来访问它的属性,好吧,我姑且认定这个IOError对象实现了数组访问的读取接口吧。

不过至少对象应该有属性吧,光是用数组下标访问是不是太二了,于是我翻阅了python的文档。

引用:https://docs.python.org/2.6/library/exceptions.html#exceptions.IOError
exception IOError

Raised when an I/O operation (such as a printstatement, the built-inopen()function or a method of a file object) fails for an I/O-relatedreason, e.g., “file not found” or “disk full”.

This class is derived from EnvironmentError. See the discussion above for more information on exception instanceattributes.

Changedin version 2.6: Changed socket.errorto use this as a base class.

 

好吧,这里说这个类是继承至EnvironmentError,它自己没属性,那我看看它爹有属性没:

引用:https://docs.python.org/2.6/library/exceptions.html#exceptions.EnvironmentError
exception EnvironmentError

The base class for exceptions that can occur outside thePython system:IOError,OSError. When exceptions of this type are created with a 2-tuple, the firstitem is available on the instance’serrnoattribute(it is assumed to be an error number), and the second item isavailable on thestrerrorattribute (it is usually the associated error message). The tuple itself is also available ontheargsattribute.

New in version 1.5.2.

When an EnvironmentError exception is instantiated with a3-tuple, the first two items are available as above, while the thirditem is available on thefilename attribute.  However, for backwardscompatibility, theargs attribute contains only a 2-tuple of thefirst two constructor arguments.

The filenameattribute isNonewhen this exception is created with other than 3 arguments.  Theerrnoandstrerrorattributes are alsoNone when the instance was created with otherthan 2 or 3 arguments. In this last case,argscontains the verbatim constructor arguments as a tuple.

 

可以看到它爹有两个属性:errno和strerror,请先原谅我的“文档白目程度”(我故意吧我白目掉的文档用灰色表示了)。

我第一次看这文档的时候完全忽略了args属性可怜,这直接导致了我后来的对socket.error对象的各种纠结。

通过以上观察,我修改了代码通过errno和strerror来访问e的属性,然后我开始判断这两个属性的类型,注意我的目标是通过属性取到那个socket错误代码10061。

因为目前看,我通过数组下标的方式来获取(e[1][0])显得比较不安全啊,万一有个异常的错误代码不在这个位置或则干脆不存在喃?

 

修改后的代码:

#!/usr/bin/python

import sys
import urllib

try:
	a = urllib.urlopen("http://127.0.0.1")
	b = a.read()
except IOError, e:
	print "e[0] = %s" % e[0]                          #打印e[0]的值
	print "e.errno = %s" % e.errno                    #打印e.errno的值 
	print "e[0] == e.errno is %s" % (e[0] == e.errno) #判定e[0]就是e.errno
	print "type(e.errno) = %s" % type(e.errno)        #打印e.errno的类型
	print "------------------------------"
	print "e[1] = %s" % e[1]                                #打印e[1]的值
	print "e.strerror = %s" % e.strerror                    #打印e.strerror 
	print "e[1] == e.strerror is %s" % (e[1] == e.strerror) #判定e[1]就是e.strerror
	print "type(e.strerror) = %s" % type(e.strerror)        #打印e.strerror

输出如下:

e[0] = socket error
e.errno = socket error
e[0] == e.errno is True
type(e.errno) = <type 'str'>
------------------------------
e[1] = (10061, 'Connection refused')
e.strerror = (10061, 'Connection refused')
e[1] == e.strerror is True
type(e.strerror) = <type 'instance'>

通过输出可得到下列判断:

  • e.errno和e[0]是一个属性,它的类型是字符串;
  • e.strerror和e[1]是一个属性 ,它的类型是个对象实例。(为啥不是str喃?说好的strerror啊。。。)

从e.strerror的字符串化输出可以看出,它内容上有是一个2元素的tuple,那么10061应该就是这个tuple的第一个元素。

但是从类型上看e.strerror应该是一个对象,那么它到底是神马对象喃?

为此我翻阅了urllib的源文件,找到抛这个异常的地方,其源代码如下(异常由绿色部分抛出):

    def connect(self):
        """Connect to the host and port specified in __init__."""
        msg = "getaddrinfo returns an empty list"
        for res in socket.getaddrinfo(self.host, self.port, 0,
                                      socket.SOCK_STREAM):
            af, socktype, proto, canonname, sa = res
            try:
                self.sock = socket.socket(af, socktype, proto)
                if self.debuglevel > 0:
                    print "connect: (%s, %s)" % (self.host, self.port)
                self.sock.connect(sa)
            except socket.error, msg:
                if self.debuglevel > 0:
                    print 'connect fail:', (self.host, self.port)
                if self.sock:
                    self.sock.close()
                self.sock = None
                continue
            break
        if not self.sock:
            <span style="background-color: rgb(51, 255, 51);">raise socket.error, msg</span>

可以看到最早抛出的是一个socket.error对象的异常,再看urllib的捕获异常后封装成IOError的逻辑:

    # External interface
    def open(self, fullurl, data=None):
        """Use URLopener().open(file) instead of open(file, 'r')."""
        fullurl = unwrap(toBytes(fullurl))
        if self.tempcache and fullurl in self.tempcache:
            filename, headers = self.tempcache[fullurl]
            fp = open(filename, 'rb')
            return addinfourl(fp, headers, fullurl)
        urltype, url = splittype(fullurl)
        if not urltype:
            urltype = 'file'
        if urltype in self.proxies:
            proxy = self.proxies[urltype]
            urltype, proxyhost = splittype(proxy)
            host, selector = splithost(proxyhost)
            url = (host, fullurl) # Signal special case to open_*()
        else:
            proxy = None
        name = 'open_' + urltype
        self.type = urltype
        name = name.replace('-', '_')
        if not hasattr(self, name):
            if proxy:
                return self.open_unknown_proxy(proxy, fullurl, data)
            else:
                return self.open_unknown(fullurl, data)
        try:
            if data is None:
                return getattr(self, name)(url)
            else:
                return getattr(self, name)(url, data)
        except socket.error, msg:
            <span style="background-color: rgb(51, 255, 51);">raise IOError, ('socket error', msg), sys.exc_info()[2]</span>
可以看到msg应该是一个socket.error异常对象,作为IOError的第二个参数也就是strerror属性对应的值。

从而可以认定e.strerror属性对应的应该是一个socket.error对象的实例。

修改代码已确认其类型:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import urllib
import socket #导入socket

try:
	a = urllib.urlopen("http://127.0.0.1")
	b = a.read()
except IOError, e:
	print "e[1] = %s" % e[1]                                #打印e[1]的值
	print "e.strerror = %s" % e.strerror                    #打印e.strerror 
	print "e[1] == e.strerror is %s" % (e[1] == e.strerror) #判定e[1]就是e.strerror
	print "type(e.strerror) = %s" % type(e.strerror)        #打印e.strerror
	# 判定e.strerror是一个socket.error对象
	print "e.strerror is a socket.error is %s" % (isinstance(e.strerror, socket.error))
输出证实这一点:

e[1] = (10061, 'Connection refused')
e.strerror = (10061, 'Connection refused')
e[1] == e.strerror is True
type(e.strerror) = <type 'instance'>
<span style="background-color: rgb(102, 255, 153);">e.strerror is a socket.error is True</span>

好啦,现在我只需要取得这个socket.error的error number就好了,也就是那个我心爱的10061。

从直接打印e.strerror来看,socket.error被表现成一个包含2元素的tuple了。

10061这个值应该就可以通过e.strerror[1]来获得,但是我还是希望能够通过属性名的方式来获得,而不是数组下标,原因就是我担心它的顺序和存在性会改变。

从这里开始,我的困惑便开始了。

socket.error看上去没有任何属性可以直接拿到这个10061,难道它就是个挂着socket.error旗号的tuple?挂羊头卖狗肉?!

我修改代码企图判断这个socket.error是不是一个tuple,结果很显然它不是,它就是一个socket.error的实例,而且它也没有__len__的方法来取得它的长度。

我就开始纠结我在代码里面写一个e.strerror[0]会不会导致“索引不存在”的惨剧啊,这不活生生把捕获的异常又变成未知异常了么。

至少要让我获得这个tuple的长度信息我才能大胆的去[0]它啊。。。。

过了许久,我才想起重新阅读python的文档,我的白目问题才得到解决:(这次我把之前阅读过的部分标灰色了,把关键的这句话给放大了鄙视

引用:https://docs.python.org/2.6/library/exceptions.html#exceptions.EnvironmentError
exception  EnvironmentError

The base class for exceptions that can occur outside thePython system:IOError,OSError. When exceptions of this type are created with a 2-tuple, the firstitem is available on the instance’serrnoattribute(it is assumed to be an error number), and the second item isavailable on thestrerrorattribute (it is usually the associated error message).  The tuple itself is also available ontheargsattribute.

New in version 1.5.2.

When an EnvironmentError exception is instantiated with a3-tuple, the first two items are available as above, while the thirditem is available on thefilename attribute.  However, for backwardscompatibility, theargsattribute contains only a 2-tuple of thefirst two constructor arguments.

The filenameattribute isNonewhen this exception is created with other than 3 arguments.  Theerrnoandstrerrorattributes are alsoNone when the instance was created with otherthan 2 or 3 arguments. In this last case,argscontains the verbatim constructor arguments as a tuple.

原来python君早就知道我会困惑了,其实一直存在着一个属性叫“args”来存储来汇总这些属性,而我用数组下标访问的时候,正是访问的这个args属性。

而且这个args属性是一个tuple,妥妥的有长度,请看代码:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import urllib
import socket #导入socket

try:
	a = urllib.urlopen("http://127.0.0.1")
	b = a.read()
except IOError, e:
	print "e[1] = %s" % e[1]                                #打印e[1]的值
	print "e.strerror = %s" % e.strerror                    #打印e.strerror 
	print "e[1] == e.strerror is %s" % (e[1] == e.strerror) #判定e[1]就是e.strerror
	print "type(e.strerror) = %s" % type(e.strerror)        #打印e.strerror
	# 判定e.strerror是一个socket.error对象
	print "e.strerror is a socket.error is %s" % (isinstance(e.strerror, socket.error))
	print "type(e.strerror.args) = %s" % (type(e.strerror.args)) #你一定是一个tuple。
	print "len(e.strerror.args) = %s" % (len(e.strerror.args)) #打印e.strerror.args的长度是
结果妥妥的:

e[1] = (10061, 'Connection refused')
e.strerror = (10061, 'Connection refused')
e[1] == e.strerror is True
type(e.strerror) = <type 'instance'>
e.strerror is a socket.error is True
<span style="background-color: rgb(51, 255, 51);">type(e.strerror.args) = <type 'tuple'>
len(e.strerror.args) = 2</span>
可以看到e.strerror.args是一个tuple而且可以获得它的长度是2。

关于socket.error这个异常也没有其他属性,只有这个args属性,参考python文档:

https://docs.python.org/2.6/library/socket.html#socket.error
exception socket.error
This exception is raised for socket-related errors. The accompanying value is either a string telling what went wrong or a pair (errno, string) representing an error returned by a system call, similar to the value accompanying os.error. See the module errno, which contains names for the error codes defined by the underlying operating system.Changed in version 2.6: socket.error is now a child class of IOError.
也就是说这个socket.error的args属性要么是一个一元的包含一个字符串的tupe,要么是一个二元tuple。

这下整个问题就清晰了。

那么要准确的捕获这个异常需要最后在判定一下socket.error的长度就可以了,而之前认为IOError和socket.error是一个tuple的错误理解也烟消云散了。

其实它们具备args属性,这个属性是来自于所有异常的基类的。


【问题解决】

修改代码完成异常的准确捕获如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import urllib
import socket #导入socket

CONNECTION_REFUSE_ERRNO = 10061

try:
	a = urllib.urlopen("http://127.0.0.1")
	b = a.read()
except IOError, e:
	#限定IOError的实际类型是socket.error
	if e.strerror is not None and isinstance(e.strerror, socket.error):
		#限定socket.error的参数长度是2并且错误代码是10061
		if 2 == len(e.strerror.args) and CONNECTION_REFUSE_ERRNO == e.strerror.args[0]:
			# raise FoundConnectionRefuseError 抛出自定义异常通知客户端显示准确错误信息
			print "Catch me if you can!!!!"
输出:

Catch me if you can!!!!
异常处理得“快”,“稳”,“准”,亲们都给我点了个赞!哈哈~

纠结了这么久,终于守得云开见月明鸟。


【一点点拓展】

稍微又研究了下关于IOError的构造方式,以及不同构造方式后,它不同的属性组成,其实文档里面都有写啦。

引用:https://docs.python.org/2.6/library/exceptions.html#exceptions.EnvironmentError
exception  EnvironmentError

The base class for exceptions that can occur outside thePython system:IOError,OSError. When exceptions of this type are created with a 2-tuple, the firstitem is available on the instance’serrnoattribute(it is assumed to be an error number), and the second item isavailable on thestrerrorattribute (it is usually the associated error message).  The tuple itself is also available ontheargsattribute.

New in version 1.5.2.

When an EnvironmentError exception is instantiated with a3-tuple, the first two items are available as above, while the thirditem is available on thefilename attribute.  However, for backwardscompatibility, theargsattribute contains only a 2-tuple of thefirst two constructor arguments.

The filenameattribute isNonewhen this exception is created with other than 3 arguments.  Theerrnoandstrerrorattributes are alsoNone when the instance was created with otherthan 2 or 3 arguments. In this last case,argscontains the verbatim constructor arguments as a tuple.



看看IOError这个异常类的父类的文档的下半段,其实写了这个对象奇葩的构造方式,总结如下:

  • 正常情况下,初始化IOError需要3个参数,形如: IOError(errno, strerror, fileName),第三个参数可以通过fileName来获得;
  • fileName参数是可选的,所以你也可以这样构造IOError异常,形如:IOError(errno, strerror) 那么这个时候fileName属性就是None;
  • 为了向下兼容,由小于等于三个参数所构造的IOError异常对象,其args属性永远是2元tuple,其内容是(errorno, strerror);
  • 然后迫于“淫威”,有些开发者硬要多传参数,IOError异常也是可以构造的,当用多余3个参数构造IOError的时候,形如:IOError(xxx, yyy, zzz, www, eee, etc.) 这样玩的,那么构造出来的实例它的正常属性(errno, strerror, fileName)将全部是None,而所有构造的参数将被保留到args属性中。

实验代码如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import urllib

print '''IOError()'''
a = IOError() # 木有参数
print "args length: %s" % len(a.args)
print a.__dict__
print "---------------------------------------------------------------"

print '''IOError("fdsafds")'''
a1 = IOError("fdsafds") #1个参数
print "args length: %s" % len(a1.args)
print a1.__dict__
print "---------------------------------------------------------------"

print '''IOError("fdsafds", "xx")'''
b = IOError("fdsafds", "xx") #2个参数
print "args length: %s" % len(b.args)
print b.__dict__
print "---------------------------------------------------------------"

print '''IOError("fdsafds", "xx", "zzzz")'''
c = IOError("fdsafds", "xx", "zzzz") #3个参数
print "args length: %s" % len(c.args)
print c.__dict__
print "---------------------------------------------------------------"

print '''IOError("fdsafds", "xx", "zzzz", "yyyyy")'''
d= IOError("fdsafds", "xx", "zzzz", "yyyyy") #4个参数
print "args length: %s" % len(d.args)
print "---------------------------------------------------------------"

print '''IOError(("fdsafds", "xx"), "zzzz", "yyyyy")'''
e= IOError(("fdsafds", "xx"), "zzzz", "yyyyy") #211参数
print "args length: %s" % len(e.args)
print e.__dict__
print "---------------------------------------------------------------"

print '''IOError("fdsafds", ("xx", "zzzz", "yyyyy"), "www")'''
f= IOError("fdsafds", ("xx", "zzzz", "yyyyy"), "www")#131参数
print "args length: %s" % len(f.args)
print f.__dict__
print "---------------------------------------------------------------"

# 最后的疯狂
print '''IOError("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")'''
g= IOError("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
print "args length: %s" % len(g.args)
print g.__dict__

输出结果如下,值得注意的是,当只给一个参数和给多余3个参数的时候情况是一样的,这一点略出乎意料。我以为它会只初始化errno属性喃,结果全都给放args里面去了。

IOError()
args length: 0
{'errno': None, 'args': (), 'strerror': None, 'filename': None}
---------------------------------------------------------------
<span style="background-color: rgb(255, 204, 102);">IOError("fdsafds")
args length: 1
{'errno': None, 'args': ('fdsafds',), 'strerror': None, 'filename': None}</span>
---------------------------------------------------------------
IOError("fdsafds", "xx")
args length: 2
{'errno': 'fdsafds', 'args': ('fdsafds', 'xx'), 'strerror': 'xx', 'filename': None}
---------------------------------------------------------------
IOError("fdsafds", "xx", "zzzz")
args length: 2
{'errno': 'fdsafds', 'args': ('fdsafds', 'xx'), 'strerror': 'xx', 'filename': 'zzzz'}
---------------------------------------------------------------
IOError("fdsafds", "xx", "zzzz", "yyyyy")
args length: 4
---------------------------------------------------------------
IOError(("fdsafds", "xx"), "zzzz", "yyyyy")
args length: 2
{'errno': ('fdsafds', 'xx'), 'args': (('fdsafds', 'xx'), 'zzzz'), 'strerror': 'zzzz', 'filename': 'yyyyy'}
---------------------------------------------------------------
IOError("fdsafds", ("xx", "zzzz", "yyyyy"), "www")
args length: 2
{'errno': 'fdsafds', 'args': ('fdsafds', ('xx', 'zzzz', 'yyyyy')), 'strerror': ('xx', 'zzzz', 'yyyyy'), 'filename': 'www'}
---------------------------------------------------------------
IOError("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
args length: 10
{'errno': None, 'args': ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10'), 'strerror': None, 'filename': None}

码完收工!


- Constant

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值