最新消息:20210917 已从crifan.com换到crifan.org

【已解决】Python代码logging打印报错:TypeError not all arguments converted during string formatting

Python crifan 910浏览 0评论
折腾:
【未解决】iOS自动化操作设置出错:启动设置后找不到无线局域网
期间,代码:
        launchResult = self.wdaClient.app_launch(iOS_AppId_Settings)
        logging.debug("launchResult=%s" % launchResult)
调试又遇到之前的错误:
        logging.debug("launchResult=%s" % launchResult)
    TypeError: not all arguments converted during string formatting
重新调试,可以发现是:
和之前另外的代码:
    def iOSGetAppState(self, appBundleId):
        """get iOS app state"""
        curAppState = self.wdaClient.app_state(appBundleId)
        logging.debug("curAppState=%s", curAppState)
        # -> 发生异常: TypeError, not all arguments converted during string formatting
        # curAppStateValue = curAppState["value"]
        # -> 发生异常: TypeError, tuple indices must be integers or slices, not str
        # curAppStateValue = curAppState["value"]
        """
        {
            "value" : 4,
            "sessionId" : "5BBD460B-F420-461D-A5E3-244A74CDF5CE"
        }
        """
        # <GenericDict, len() = 3>
        # curAppStateValue = curAppState[0]
        curAppStatus = curAppState.status
        curAppSessionId = curAppState.sessionId
        logging.debug("curAppStatus=%s, curAppSessionId=%s", curAppStatus, curAppSessionId)
        curAppStateValue = curAppState.value
        logging.debug("curAppStateValue=%s", curAppStateValue)
        curStateEnum = ApplicationState(curAppStateValue)
        logging.debug("curStateEnum=%s", curStateEnum)
        return curStateEnum
遇到的情况是一模一样:
即,直接对于返回的值去打印,就会报错
而如果是:
curAppState[0]
获取第一个值,就可以。
或者是,注意到其有属性value,所以写成:
curAppState.value
也是可以获取到值的。
类似的还有:
curAppState.sessionId
curAppState.status
应该是对应的:
curAppState[1]
curAppState[2]
以及看到变量类型是:
<GenericDict, len() = 3>
即:
GenericDict
感觉是:
此处的facebook-wda的一些函数
/Users/xxx/.pyenv/versions/3.8.0/Python.framework/Versions/3.8/lib/python3.8/site-packages/wda/__init__.py
    def app_launch(self,
                   bundle_id,
                   arguments=[],
                   environment={},
                   wait_for_quiescence=False):
。。。
        return self._session_http.post(
            "/wda/apps/launch", {
                "bundleId": bundle_id,
                "arguments": arguments,
                "environment": environment,
                "shouldWaitForQuiescence": wait_for_quiescence,
            })

    def app_activate(self, bundle_id):
        return self._session_http.post("/wda/apps/launch", {
            "bundleId": bundle_id,
        })


    def app_terminate(self, bundle_id):
        return self._session_http.post("/wda/apps/terminate", {
            "bundleId": bundle_id,
        })


    def app_state(self, bundle_id):
        """
        Returns example:
            {
                "value": 4,
                "sessionId": "0363BDC5-4335-47ED-A54E-F7CCB65C6A65"
            }
        
            value: enum ApplicationState
        """
        return self._session_http.post("/wda/apps/state", {
            "bundleId": bundle_id,
        })
中的
_session_http.post
所返回的,变量类型是GenericDict
且都无法直接打印,否则会报错
所以:要去搞清楚:
什么是GenericDict
为何不能打印值
python GenericDict
AttributeError: ‘GenericDict’ object has no attribute ‘status’ · Issue #74 · openatx/facebook-wda
此处返回的json
Shell: curl -X POST -d '{"desiredCapabilities": {"bundleId": "com.apple.mobilesafari", "arguments": ["-u", "www.baidu.com"], "shouldWaitForQuiescence": true, "defaultAlertAction": "accept"}}' 'http://localhost:8100/session'
Return (20ms): {
  "value" : {
    "error" : "session not created",
    "message" : "'capabilities' is mandatory to create a new session"
  },
  "sessionId" : "7A89D03F-EED8-473B-B0CB-2962CF7A19C3"
}
对应着:
value和sessionId
但是status从哪里来的?
去看看代码
搜:
.status
找到
class WDARequestError(WDAError):
    def __init__(self, status, value):
        self.status = status
        self.value = value


    def __str__(self):
        return 'WDARequestError(status=%d, value=%s)' % (self.status,
                                                         self.value)
-》对于WDARequestError是有额外的status的
但是此处不是error的,而是普通的response啊
def httpdo(url, method="GET", data=None):
    """
    thread safe http request
    """
    p = urlparse(url)
    with namedlock(p.scheme + "://" + p.netloc):
        return _unsafe_httpdo(url, method, data)


def _unsafe_httpdo(url, method='GET', data=None):
    """
    Do HTTP Request
    """
    start = time.time()
    if DEBUG:
        body = json.dumps(data) if data else ''
        print("Shell: curl -X {method} -d '{body}' '{url}'".format(
            method=method.upper(), body=body or '', url=url))


    try:
        response = requests.request(method,
                                    url,
                                    json=data,
                                    timeout=HTTP_TIMEOUT)
    except (requests.exceptions.ConnectionError,
            requests.exceptions.ReadTimeout) as e:
        raise


    if DEBUG:
        ms = (time.time() - start) * 1000
        print('Return ({:.0f}ms): {}'.format(ms, response.text))


    try:
        retjson = response.json()
        retjson['status'] = retjson.get('status', 0)
        r = convert(retjson)
        if r.status != 0:
            raise WDARequestError(r.status, r.value)
        if isinstance(r.value, dict) and r.value.get("error"):
            raise WDARequestError(100, r.value['error']) # status:100 for new WebDriverAgent error
        return r
    except JSONDecodeError:
        if response.text == "":
            raise WDAEmptyResponseError(method, url, data)
        raise WDAError(method, url, response.text)
->看到了,是用:
        retjson['status'] = retjson.get('status', 0)
        r = convert(retjson)
加了额外的status字段的。
然后看到了:
def convert(dictionary):
    """
    Convert dict to namedtuple
    """
    return namedtuple('GenericDict', list(dictionary.keys()))(**dictionary)
此处是:
nametuple,名字是:GenericDict
即:
带名字的tuple元祖,名字叫GenericDict
-》真变态,一个元祖的名字,起了个dict。。。
然后注意到,想起来了,之前的wda的代码中,http的response处理中,也都是:
直接引用response即r的status和value(偶尔用到sessionId)的
所以:
后续正确用法是:
r.status
r.value
r.sessionId
但是如何确保:
logging等直接打印这个GenericDict 不报错呢?
Convert any dictionary to a named tuple
/Users/xxx/.pyenv/versions/3.8.0/Python.framework/Versions/3.8/lib/python3.8/site-packages/wda/__init__.py
from collections import defaultdict, namedtuple
看到了:
from collections import namedtuple
def convert(dictionary):
    return namedtuple('GenericDict', dictionary.keys())(**dictionary)


"""
>>> d = dictionary(a=1, b='b', c=[3])
>>> named = convert(d)
>>> named.a == d.a
True
>>> named.b == d.b
True
>>> named.c == d.c
True
"""
->终于明白了:
此处是目标是把之前的dict即json,转换后,方便直接引用字段值
对于:
{
    “status”: 0
}
的dict,之前要写
someDict[“status”]
而变成namedtuple后,直接写:
someDict.status
即可-》更方便了。
如此而已。
接着继续去看看,如何能打印这个变量的值
难道要写成:
logging.info("launchResult: value=%s, status=%s, sessionId=%s", launchResult.value, launchResult.status, launchResult.sessionId)
比较麻烦,虽然可用。
先调试看看
[200611 14:49:50][DevicesMethods.py 2602] launchResult: value=None, status=0, sessionId=79A39B72-F5F9-4A01-8E58-DD380452350A
另外试试
print(dict(launchResult))
print(str(launchResult))
经过测试,可以用:
str(launchResult)
转换成 字符串,即可打印。
logging.info("launchResult=%s", str(launchResult))
调试
        logging.info("launchResult=%s", str(launchResult))
        # launchResult=GenericDict(value=None, sessionId='79A39B72-F5F9-4A01-8E58-DD380452350A', status=0)
即可。
【总结】
此处facebook-wda的代码
/Users/xxx/.pyenv/versions/3.8.0/Python.framework/Versions/3.8/lib/python3.8/site-packages/wda/__init__.py
中有很多函数,比如:
    def app_launch(self,
。。。
        return self._session_http.post(
            "/wda/apps/launch", {
。。。
            })
所返回的值,去logging打印:
        launchResult = self.wdaClient.app_launch(iOS_AppId_Settings)
        logging.debug("launchResult=%s" % launchResult)
会报错:
    TypeError: not all arguments converted during string formatting
原因:
此处是一个特殊的变量:
一个有名字的元祖,名字叫做GenericDict
具体实现是:
from collections import defaultdict, namedtuple

def convert(dictionary):
    """
    Convert dict to namedtuple
    """
    return namedtuple('GenericDict', list(dictionary.keys()))(**dictionary)
对此,想要获取(此处的的返回的)response的值,则可以用:
response.status
response.value
response.sessionId
即可
想要打印其值,可以:
logging.info("launchResult=%s", str(launchResult))
输出:
launchResult=GenericDict(value=None, sessionId='79A39B72-F5F9-4A01-8E58-DD380452350A', status=0)
或:
logging.info("launchResult: value=%s, status=%s, sessionId=%s", launchResult.value, launchResult.status, launchResult.sessionId)
输出:
launchResult: value=None, status=0, sessionId=79A39B72-F5F9-4A01-8E58-DD380452350A
即可。
【后记20200611】
    def iOSGetAppState(self, appBundleId):
        """get iOS app state"""
        curAppState = self.wdaClient.app_state(appBundleId)
        logging.info("curAppState=%s", curAppState)
才注意到,之前此处已经在打印值了,一直没报错?
去调试,真的没报错:
[200611 14:58:12][DevicesMethods.py 1727] curAppState=GenericDict(value=4, sessionId='79A39B72-F5F9-4A01-8E58-DD380452350A', status=0)
-》说明直接logging打印是没问题的。
但是为何后续打印,偶尔又报错了呢?
很是奇怪,再回去测试:
        launchResult = self.wdaClient.app_launch(iOS_AppId_Settings)
        logging.debug("launchResult=%s" % launchResult)
结果:
前面的info打印,是没问题的
[200611 15:00:52][DevicesMethods.py 1727] curAppState=GenericDict(value=2, sessionId='79A39B72-F5F9-4A01-8E58-DD380452350A', status=0)
怀疑:难道是debug输出到file,有问题?
去看看
也没问题
[200611 15:00:52][DevicesMethods.py 1727] curAppState=GenericDict(value=2, sessionId='79A39B72-F5F9-4A01-8E58-DD380452350A', status=0)
然后才注意到是:
logging.debug("launchResult=%s" % launchResult)
是 % ,即字符串的format,格式化,不支持此处的GenericDict
而logging本身是支持的
logging.debug("launchResult=%s”, launchResult)
即:
此处对于打印GenericDict的值,直接
logging.debug("launchResult=%s”, launchResult)
即可。
无需额外的str(xxx) ,或 获取每个字段 再去打印值了。
另外:
之前代码
wdaTest/wdaTest.py
    curAppState = curSession.app_state(gCurAppId)
    # logging.debug("curAppState=%s", curAppState)
    # -> 发生异常: TypeError, not all arguments converted during string formatting
感觉像是:
之前就是正常的写法
logging.debug("curAppState=%s", curAppState)
但是也报错?
不确定之前是什么情况。有空再去深究。
 

转载请注明:在路上 » 【已解决】Python代码logging打印报错:TypeError not all arguments converted during string formatting

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
92 queries in 0.185 seconds, using 23.44MB memory