isinstance() 意外返回 False

9
我将使用Kubernetes Python客户端进行翻译。如果在我的代码启动时Kubernetes不可用,我希望重试连接。

当客户端无法连接时,它会引发似乎是urllib3.exceptions.MaxRetryError异常,因此我从以下内容开始:

import time
import urllib3

import kubernetes

kubernetes.config.load_kube_config()
api = kubernetes.client.CoreV1Api()

while True:
    try:
        w = kubernetes.watch.Watch()
        for event in w.stream(api.list_pod_for_all_namespaces):
            print event
    except urllib3.exceptions.HTTPError:
        print('retrying in 1 second')
        time.sleep(1)

但这完全失败了;它好像没有except语句一样,然后就会停止运行并出现以下错误信息:

urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='192.168.122.140', port=8443): Max retries exceeded with url: /api/v1/pods?watch=True (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x2743110>: Failed to establish a new connection: [Errno 111] Connection refused',))

我觉得也许我对继承的理解不如我想象的那么好,所以我将上述内容替换为:

except urllib3.exceptions.MaxRetryError:
    print('retrying in 1 second')
    time.sleep(1)

同样的失败。为了弄清楚发生了什么,我添加了一个万能的 except 并调用了 pdb:

except Exception as err:
    import pdb; pdb.set_trace()

pdb 提示符中,我们可以看到:

(Pdb) type(err)
<class 'urllib3.exceptions.MaxRetryError'>

这看起来很好,MRO也是如此:

(Pdb) import inspect
(Pdb) inspect.getmro(err.__class__)
(<class 'urllib3.exceptions.MaxRetryError'>, <class 'urllib3.exceptions.RequestError'>, <class 'urllib3.exceptions.PoolError'>, <class 'urllib3.exceptions.HTTPError'>, <type 'exceptions.Exception'>, <type 'exceptions.BaseException'>, <type 'object'>)

但尽管如此:
(Pdb) isinstance(err, urllib3.exceptions.MaxRetryError)
False

所有路径看起来都很合理:

(Pdb) urllib3.__file__
'/usr/lib/python2.7/site-packages/urllib3/__init__.pyc'
(Pdb) kubernetes.client.rest.urllib3.__file__
'/usr/lib/python2.7/site-packages/urllib3/__init__.pyc'

那么...这里到底发生了什么?

更新

以下是完整的堆栈跟踪:

Traceback (most recent call last):
  File "testkube.py", line 13, in <module>
    for event in w.stream(api.list_pod_for_all_namespaces):
  File "/usr/lib/python2.7/site-packages/kubernetes/watch/watch.py", line 116, in stream
    resp = func(*args, **kwargs)
  File "/usr/lib/python2.7/site-packages/kubernetes/client/apis/core_v1_api.py", line 14368, in list_pod_for_all_namespaces
    (data) = self.list_pod_for_all_namespaces_with_http_info(**kwargs)
  File "/usr/lib/python2.7/site-packages/kubernetes/client/apis/core_v1_api.py", line 14464, in list_pod_for_all_namespaces_with_http_info
    collection_formats=collection_formats)
  File "/usr/lib/python2.7/site-packages/kubernetes/client/api_client.py", line 335, in call_api
    _preload_content, _request_timeout)
  File "/usr/lib/python2.7/site-packages/kubernetes/client/api_client.py", line 148, in __call_api
    _request_timeout=_request_timeout)
  File "/usr/lib/python2.7/site-packages/kubernetes/client/api_client.py", line 371, in request
    headers=headers)
  File "/usr/lib/python2.7/site-packages/kubernetes/client/rest.py", line 250, in GET
    query_params=query_params)
  File "/usr/lib/python2.7/site-packages/kubernetes/client/rest.py", line 223, in request
    headers=headers)
  File "/usr/lib/python2.7/site-packages/urllib3/request.py", line 66, in request
    **urlopen_kw)
  File "/usr/lib/python2.7/site-packages/urllib3/request.py", line 87, in request_encode_url
    return self.urlopen(method, url, **extra_kw)
  File "/usr/lib/python2.7/site-packages/urllib3/poolmanager.py", line 321, in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
  File "/usr/lib/python2.7/site-packages/urllib3/connectionpool.py", line 668, in urlopen
    **response_kw)
  File "/usr/lib/python2.7/site-packages/urllib3/connectionpool.py", line 668, in urlopen
    **response_kw)
  File "/usr/lib/python2.7/site-packages/urllib3/connectionpool.py", line 668, in urlopen
    **response_kw)
  File "/usr/lib/python2.7/site-packages/urllib3/connectionpool.py", line 639, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/usr/lib/python2.7/site-packages/urllib3/util/retry.py", line 388, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='192.168.122.140', port=8443): Max retries exceeded with url: /api/v1/pods?watch=True (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x3d16110>: Failed to establish a new connection: [Errno 111] Connection refused',))

请展示堆栈跟踪。 - user2357112
尝试运行 python -v testkube.py 来打开 import logging,看看是否有任何模块被初始化两次。 - user2357112
@user2357112 产生了一些有趣但冗长的结果。我现在把它们放在这里(http://chunk.io/larsks/a514bf26a4af46e7985610b2cc0caa65),虽然我很乐意在问题中放一个摘录。我看到混合了 requests.packages.urllib3,我认为这是问题的根源,尽管我不清楚这是发生在哪里。 - larsks
@larsks: 看起来chunk.io在这里被禁止了,据说是因为“潜在的恶意活动或其他安全原因”。也许可以试试Pastebin? - user2357112
1
旧版本的 requests 初始化 requests.packages 的方式不同,而在这些版本中,requests.packages 确实会导致 urllib3.exceptions 的重新加载。requests.packages 就是罪魁祸首! - user2357112
显示剩余10条评论
1个回答

7
您的代码间接依赖于requests包,而requests包有一个奇怪的子模块叫做requests.packages。这个子模块曾经包含了一些依赖项的源代码副本,包括urllib3,但他们停止了这样做。他们想保留requests.packages以保持向后兼容性,所以现在他们正在做一些奇怪的事情。
现在,requests.packages不再包含完整的urllib3源代码副本,而是导入urllib3并设置sys.modules['requests.packages.urllib3'] = urllib3。根据requests的版本,它可能还会设置其他一些sys.modules条目;例如,截至requests 2.18.4的源代码就是这样做的。
for package in ('urllib3', 'idna', 'chardet'):
    locals()[package] = __import__(package)
    # This traversal is apparently necessary such that the identities are
    # preserved (requests.packages.urllib3.* is urllib3.*)
    for mod in list(sys.modules):
        if mod == package or mod.startswith(package + '.'):
            sys.modules['requests.packages.' + mod] = sys.modules[mod]

但在 2.17.0 中,它确实

import urllib3
sys.modules['requests.packages.urllib3'] = urllib3

import idna
sys.modules['requests.packages.idna'] = idna

import chardet
sys.modules['requests.packages.chardet'] = chardet

该代码与导入包的子模块交互存在问题。如果某些代码尝试执行import requests.packages.urllib3.exceptions,并且Python找不到sys.modules['requests.packages.urllib3.exceptions']条目,则Python将重新创建urllib3.exceptions模块,并将urllib3.exceptionssys.modules['requests.packages.urllib3.exceptions']设置为新模块(但它不会触及sys.modules['urllib3.exceptions'])。这将生成涉及的类的新副本,从而导致错误。

同样原因的相关问题在5月份被报告,导致2.18.4中显示了新代码。 2.18.4不应导致您所看到的具体问题,但仍然很脆弱,因为如果在requests.packages搞乱sys.modules时,urllib3的任何子模块尚未加载,则这些子模块将表现出您今天看到的相同问题。


谢谢你帮我解决了这个问题!我之前很困惑。 - larsks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接