Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 33.9k
Description
Feature or enhancement
__getattr__ in Python3.11.1 is much slower than @Property and visiting an object's attribute. It's even slower than Python3.10.4._PyObject_GenericGetAttrWithDict is the key reason. If Python3 fails finding an attribute in normal ways, it will return NULL and raise an exception. But raising an exception has performance cost. Python3.11.1 add set_attribute_error_context to support Fine Grained Error Locations in Tracebacks. It makes things worser.
Pitch
We can use this test code:
importtimeimportsysclassA: deffoo(self): print("Call A.foo!") def__getattr__(self, name): return2@propertydefppp(self): return3classB(A): deffoo(self): print("Call B.foo!") classC(B): def__init__(self) ->None: self.pps=1deffoo(self): print("Call C.foo!") defmain(): start=time.time() foriinrange(1, 1000000): passend=time.time() peer=end-startc=C() print(f"Python version of {sys.version}") start=time.time() foriinrange(1, 1000000): s=c.ppsend=time.time() print(f"Normal getattr spend time: {end-start-peer}") start=time.time() foriinrange(1, 1000000): s=c.ppaend=time.time() print(f"Call __getattr__ spend time: {end-start-peer}") start=time.time() foriinrange(1, 1000000): s=c.pppend=time.time() print(f"Call property spend time: {end-start-peer}") if__name__=="__main__": main()The result shows how slow __getattr__ is:
Python version of 3.11.1 (main, Dec 26 2022, 16:32:50) [GCC 8.3.0] Normal getattr spend time: 0.03204226493835449 Call __getattr__ spend time: 0.4767305850982666 Call property spend time: 0.06345891952514648 When we define __getattr__, failed to find an attribute is what we expected. If we can get this result and then call __getattr__ without exception handling, it will be faster.
I tried to modify Python3.11.1 like this:
- add a new function in
object.c:
PyObject*PyObject_GenericTryGetAttr(PyObject*obj, PyObject*name){return_PyObject_GenericGetAttrWithDict(obj, name, NULL, 1)}- change
typeobject.c:
if (getattribute==NULL|| (Py_IS_TYPE(getattribute, &PyWrapperDescr_Type) && ((PyWrapperDescrObject*)getattribute)->d_wrapped== (void*)PyObject_GenericGetAttr)) // res = PyObject_GenericGetAttr(self, name);res=PyObject_GenericTryGetAttr(self, name); else{Py_INCREF(getattribute); res=call_attribute(self, getattribute, name); Py_DECREF(getattribute)} if (res==NULL){if (PyErr_ExceptionMatches(PyExc_AttributeError)) PyErr_Clear(); res=call_attribute(self, getattr, name)} Py_DECREF(getattr); returnres;Rebuild python, it really become faster: spend time: 0.13772845268249512.
Previous discussion
getattr is much slower in Python3.11