你用对 hasattr 了嘛?
/ / / 阅读数:6090前天帮一个同学 DEBUG 一个很奇怪的问题,发现了一个 Python 2 的 hasattr 一个不适用场景,和大家分享一下。
在日常开发中,判断对象有没有一个属性,你可能这么写:
if hasattr(self, 'redirect_url'): return request.redirect(self.redirect_url) |
如果 self 有 redirect_url 这个属性,那么请求直接重定向到 redirect_url 这个 URL 上。看起来没有问题对吧,我们写的更完整一些:
In [1]: class VideoSubject(object): ...: @property ...: def redirect_url(self): ...: raise IndexError() # 就是让它抛错 ...: In [2]: In [2]: vs = VideoSubject() In [3]: hasattr(vs, 'redirect_url**) Out[3**: False |
看到了吧 hassattr 的结果是 False,也就是说 Python 2 认为 vs 这个实例就没有 redirect_url 属性。但是看代码是有这个 propery 的,只是在计算时由于某些原因抛错误了。
我 debug 了好久才发现这个原来是这个问题,🤦
感觉网上搜了下,原来 attrs 的作者,Python 核心开发在 16 年就写过一篇文章介绍这个事情:
https://hynek.me/articles/hasattr/
也就是说在 Python2,如果你想要判断一个类里面是否有某个 property,需要确认它会不会有机会抛错
如果它有可能会异常,那么应该用 getattr(让异常发生):
In [4]: getattr(vs, 'redirect_url', None) --------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-4-08e45e416015> in <module>() ----> 1 getattr(vs, 'redirect_url', None) /Users/dongweiming/workspace/test.py in redirect_url(self) 4 @property 5 def redirect_url(self): ----> 6 raise IndexError() # 就是让它抛错 7 8 IndexError: |
另外在 CPython 实现,hasattr 其实内部还是用了 getattr 的:
/* 2.7/Python/bltinmodule.c#L828-L858 */ static PyObject * builtin_getattr(PyObject *self, PyObject *args) { PyObject *v, *result, *dflt = NULL; PyObject *name; if (!PyArg_UnpackTuple(args, "getattr", 2, 3, &v, &name, &dflt)) return NULL; #ifdef Py_USING_UNICODE if (PyUnicode_Check(name)) { name = _PyUnicode_AsDefaultEncodedString(name, NULL); if (name == NULL) return NULL; } #endif if (!PyString_Check(name)) { PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string"); return NULL; } result = PyObject_GetAttr(v, name); if (result == NULL && dflt != NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); Py_INCREF(dflt); result = dflt; } return result; } |
注意result = PyObject_GetAttr(v, name)
这步,所以 getattr 要比 hasattr 更快更直接。
而在 Python 3 没有这个问题,他会捕获 AttributeError 返回 False,而其他错误直接抛出来:
/* 3.7/Objects/object.c#L1231-L1242 阅读路线: builtin_hasattr_impl -> _PyObject_LookupAttr -> PyObject_GenericGetAttr -> _PyObject_GenericGetAttrWithDict * / if (descr != NULL) { Py_INCREF(descr); f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { res = f(descr, obj, (PyObject *)obj->ob_type); if (res == NULL && suppress && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); } goto done; } } |
也就抛出了 PyExc_AttributeError 会被清除(其他的错误照常 raise)