This issue tracker has been migrated to GitHub ,
and is currently read-only.
For more information,
see the GitHub FAQs in the Python's Developer Guide.
Created on 2007年12月13日 20:13 by gangesmaster, last changed 2022年04月11日 14:56 by admin. This issue is now closed.
| Files | ||||
|---|---|---|---|---|
| File name | Uploaded | Description | Edit | |
| demo.txt | gangesmaster, 2007年12月13日 20:13 | |||
| getattr-descriptors.diff | eric.snow, 2013年02月23日 06:17 | review | ||
| issue1615.stoneleaf.01.patch | ethan.furman, 2013年10月17日 04:12 | review | ||
| issue1615.stoneleaf.02.patch | ethan.furman, 2014年03月28日 23:22 | review | ||
| Messages (16) | |||
|---|---|---|---|
| msg58581 - (view) | Author: ganges master (gangesmaster) | Date: 2007年12月13日 20:13 | |
it seems the code of PyObject_GenericGetAttr, which invokes the descriptor protocol, silences any AttributeErrors raised by the descriptor, for classes that also define __getattr__. it should propagate up rather than being silently ignored. the attached example is quite artificial, but it's a simplification of real world code i had hard time debugging. turned out i misspelled an attribute name inside the property getter function, which raised an AttributeError as expected -- but the exception i got was quite misleading, saying the instance has no attribute named so. this bug only happens when the class defines a custom __getattr__. see attached demo file for details. |
|||
| msg61312 - (view) | Author: Antoine Pitrou (pitrou) * (Python committer) | Date: 2008年01月20日 17:29 | |
I can confirm that with SVN trunk, and it's actually even worse because it can return unexpected results without raising an exception at all: >>> class Foo(object): ... def __getattr__(self, name): return 42 ... @property ... def bacon(self): return int.lalala ... >>> f = Foo() >>> f.bacon 42 >>> Foo.bacon.__get__(f) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in bacon AttributeError: type object 'int' has no attribute 'lalala' |
|||
| msg61321 - (view) | Author: Antoine Pitrou (pitrou) * (Python committer) | Date: 2008年01月20日 18:09 | |
PyObject_GenericGetAttr is invoked from slot_tp_getattr_hook in typeobject.c via tp_getattro. The problem is that, when tp_getattro returns with an AttributeError, there is no way for slot_tp_getattr_hook to know whether the error was raised by PyObject_GenericGetAttr itself or by subsequent invocation of user code. Perhaps by adding an attribute to the raised AttributeError? |
|||
| msg76825 - (view) | Author: ganges master (gangesmaster) | Date: 2008年12月03日 12:20 | |
here's a short example of the bug >>> class Foo(object): ... def __getattr__(self, name): ... return 42 ... @property ... def bacon(self): ... return int.lalala ... @property ... def eggs(self): ... return 17 ... >>> f = Foo() >>> f.bacon # raises an AttributeError, and silently ignores it 42 >>> f.eggs 17 >>> are there any news in this front? |
|||
| msg77424 - (view) | Author: Thomas Lee (thomaslee) (Python committer) | Date: 2008年12月09日 15:20 | |
Related reading from a few years back: http://coding.derkeiler.com/Archive/Python/comp.lang.python/2005-05/msg03829.html |
|||
| msg116804 - (view) | Author: Mark Lawrence (BreamoreBoy) * | Date: 2010年09月18日 15:53 | |
This is still an issue with the latest trunk. |
|||
| msg116805 - (view) | Author: Benjamin Peterson (benjamin.peterson) * (Python committer) | Date: 2010年09月18日 15:57 | |
I consider this to be a feature. Properties can raise AttributeError to defer to __getattr__. |
|||
| msg158556 - (view) | Author: Micah Friesen (Micah.Friesen) | Date: 2012年04月17日 16:16 | |
I ran into this recently, as well, and have lost probably a day's worth of time debugging it. I submit that this is not a feature - I can't imagine a real-world scenario where you actually want to write debuggable code where a descriptor defers to __getattr__ (except perhaps for exception handling, in which case some re-factoring is in order), particularly because descriptors are effectively mix-ins and can be used on multiple classes. I worked around this by writing an ancestor descriptor that catches AttributeErrors and re-raises them as a user-defined exception. |
|||
| msg182210 - (view) | Author: Eric Snow (eric.snow) * (Python committer) | Date: 2013年02月16日 04:20 | |
Got bit by a variation of this today in 2.7:
class Spam(object):
def __getattr__(self, attr):
raise AttributeError(attr)
@property
def eggs(self):
return self.ham
s = Spam()
s.eggs
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __getattr__
AttributeError: eggs
It took me a little while to figure out what was going on. A real head-scratcher. This is because the AttributeError was attributed to the property, but was actually caused by the __getattr__ call triggered by the property's code. I would expect the AttributeError to reference "ham", not "eggs". As already noted, if __getattr__() is not there, that's what happens.
Regardless, I'm just not seeing where the hurdle is to improving this behavior. I certainly agree that this is not a feature. It is the source of very mysterious failures.
I was surprised that AttributeError does not have an attribute to which the name would be bound. If it did, then slot_tp_getattr_hook() could check against that:
if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyObject *tp, *exc, *tb, *exc_attr;
PyErr_Fetch(&tp, &exc, &tb);
exc_attr = PyObject_GetAttrString(exc, "attribute");
PyErr_Restore(tp, exc, tb);
if (!exc_attr || exc_attr == name) {
PyErr_Clear();
res = call_attribute(self, getattr, name);
}
Py_XDECREF(exc_attr);
}
Alternatively, when an AttributeError comes out of a getter in _PyObject_GenericSetAttrWithDict() (in either spot they're called), another exception (not AttributeError) could be raised with the original chained onto it. Then slot_tp_getattr_hook() won't silently ignore it. It would be something like this:
if (f != NULL && PyDescr_IsData(descr)) {
res = f(descr, obj, value);
if (!res && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyObject *msg = PyUnicode_FromFormat("getter failed for '%U'", name);
/* implicit chaining here */
PyErr_SetObject(PyExc_???Error, msg);
}
goto done;
}
Conceptually, it's as though locating the attribute and extracting the value are lumped together here. Distinguishing the two would help make this failure situation much less confusing.
Additionally, it would be really helpful to have a brief mention of this behavior (AttributeErrors in getters falling back to __getattr__) in the language reference entry for __getattr__ and/or descriptors.
|
|||
| msg182717 - (view) | Author: Eric Snow (eric.snow) * (Python committer) | Date: 2013年02月23日 06:17 | |
The whole AttributeError gaining an attribute seems impractical, but the second approach still seems reasonable to me. I've attached a rough patch to demonstrate. If that looks okay, I can flesh it out. |
|||
| msg197991 - (view) | Author: Ethan Furman (ethan.furman) * (Python committer) | Date: 2013年09月17日 14:28 | |
Benjamin Peterson added the comment:
>
> I consider this to be a feature. Properties can raise AttributeError to defer to
> __getattr__.
Micah Friesen added the comment:
>
>I submit that this is not a feature - I can't imagine a real-world scenario [...]
No need to imagine. The new Enum class uses this ability to support having both protected properties on enum members and enum members of the same name:
--> from enum import Enum
--> class Field(Enum):
--> name = 1
... age = 2
... location = 3
...
--> Field.name
<Field.name: 1>
--> Field.name.name
'name'
Enum's custom __getattr__ is located in the metaclass, however, not in the class. For future reference, here is a short test script and it's output in 3.4.0a2+:
=====================================================================================
class WithOut:
@property
def huh(self):
return self.not_here
class With:
@property
def huh(self):
return self.not_here
def __getattr__(self, name):
print('looking up %s' % name)
raise AttributeError('%s not in class %s' % (name, type(self)))
try:
WithOut().huh
except AttributeError as exc:
print(exc)
print()
try:
With().huh
except AttributeError as exc:
print(exc)
print()
import enum # broken value property tries to access self.not_here
class TestEnum(enum.Enum):
one = 1
print(TestEnum.one.value)
=====================================================================================
'WithOut' object has no attribute 'not_here'
looking up not_here
looking up huh
huh not in class <class '__main__.With'>
meta getattr with __new_member__
meta getattr with __new_member__
meta getattr with one
'TestEnum' object has no attribute 'not_here'
=====================================================================================
|
|||
| msg198158 - (view) | Author: Raymond Hettinger (rhettinger) * (Python committer) | Date: 2013年09月20日 18:41 | |
Marking this for Python 3.4. It isn't a bug in the descriptor protocol; rather, it is an implementation detail that is sometimes helpful but is mostly annoying. |
|||
| msg200108 - (view) | Author: Ethan Furman (ethan.furman) * (Python committer) | Date: 2013年10月17日 04:12 | |
Well, attached patch doesn't segfault in debug mode, but the errors aren't any better; in fact, I'd say their worse. Here's the current output from my test script: =============================================================== getter failed for descriptor 'huh' looking up not_here looking up huh huh not in class <class '__main__.With'> Traceback (most recent call last): File "break_getattr.py", line 30, in <module> print(TestEnum.one.missing) AttributeError: getter failed for descriptor 'missing' =============================================================== As you can see, we have even less information when a class level __getattr__ is /absent/, and when we do have one, there is no change (which is exactly where we really wanted the change). :( |
|||
| msg200109 - (view) | Author: Ethan Furman (ethan.furman) * (Python committer) | Date: 2013年10月17日 04:15 | |
If anyone with more experience wants ownership, feel free to take it from me. ;) Otherwise I'll do my best to get this figured out in time for the beta. |
|||
| msg215018 - (view) | Author: Ethan Furman (ethan.furman) * (Python committer) | Date: 2014年03月28日 05:21 | |
Results from the first two tests in my test script: -------------------------------------------------- 'WithOut' object has no attribute 'not_here' looking up not_here looking up huh 'With' object has no attribute 'not_here' |
|||
| msg215092 - (view) | Author: Ethan Furman (ethan.furman) * (Python committer) | Date: 2014年03月28日 23:22 | |
Downside to this patch (stoneleaf.02) is that custom AttributeErrors raised in __getattr__ are overridden, which is a pretty severe regression. (Removed, renamed, and reloaded patch.) |
|||
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2022年04月11日 14:56:28 | admin | set | github: 45956 |
| 2015年07月21日 08:15:01 | ethan.furman | set | status: open -> closed resolution: not a bug stage: patch review -> |
| 2014年03月28日 23:22:21 | ethan.furman | set | files:
+ issue1615.stoneleaf.02.patch messages: + msg215092 |
| 2014年03月28日 23:20:45 | ethan.furman | set | files: - issue1615.stoneleaf.01.patch |
| 2014年03月28日 23:20:19 | ethan.furman | set | messages: - msg215091 |
| 2014年03月28日 23:19:18 | ethan.furman | set | messages: + msg215091 |
| 2014年03月28日 05:23:27 | ethan.furman | set | files: + issue1615.stoneleaf.01.patch |
| 2014年03月28日 05:22:49 | ethan.furman | set | files: - issue1615.stoneleaf.01.patch |
| 2014年03月28日 05:22:00 | ethan.furman | set | files:
+ issue1615.stoneleaf.01.patch messages: + msg215018 stage: test needed -> patch review |
| 2014年03月22日 03:17:58 | ethan.furman | set | stage: needs patch -> test needed versions: + Python 3.5, - Python 3.4 |
| 2014年02月03日 15:51:04 | BreamoreBoy | set | nosy:
- BreamoreBoy |
| 2013年10月17日 04:15:06 | ethan.furman | set | assignee: ethan.furman messages: + msg200109 stage: needs patch |
| 2013年10月17日 04:12:27 | ethan.furman | set | files:
+ issue1615.stoneleaf.01.patch messages: + msg200108 |
| 2013年10月17日 03:34:40 | rhettinger | set | assignee: rhettinger -> (no value) |
| 2013年09月21日 03:53:30 | Arfrever | set | nosy:
+ Arfrever |
| 2013年09月20日 18:41:31 | rhettinger | set | messages:
+ msg198158 title: descriptor protocol bug -> PyObject_GenericGetAttr suppresses AttributeErrors in descriptors |
| 2013年09月20日 18:34:48 | rhettinger | set | priority: high -> normal assignee: rhettinger versions: - Python 2.7, Python 3.2, Python 3.3 |
| 2013年09月17日 14:28:10 | ethan.furman | set | messages: + msg197991 |
| 2013年09月16日 22:50:26 | ethan.furman | set | nosy:
+ ethan.furman |
| 2013年02月23日 06:17:04 | eric.snow | set | files:
+ getattr-descriptors.diff keywords: + patch messages: + msg182717 |
| 2013年02月16日 04:20:22 | eric.snow | set | nosy:
+ eric.snow messages: + msg182210 versions: + Python 3.4 |
| 2012年04月18日 22:07:08 | eric.araujo | set | nosy:
+ eric.araujo versions: + Python 3.2, Python 3.3, - Python 2.6 |
| 2012年04月17日 23:38:11 | pitrou | set | nosy:
+ rhettinger |
| 2012年04月17日 16:16:30 | Micah.Friesen | set | nosy:
+ Micah.Friesen messages: + msg158556 |
| 2010年09月18日 15:57:02 | benjamin.peterson | set | nosy:
+ benjamin.peterson messages: + msg116805 |
| 2010年09月18日 15:53:48 | BreamoreBoy | set | nosy:
+ BreamoreBoy messages: + msg116804 |
| 2010年08月16日 17:28:35 | daniel.urban | set | nosy:
+ daniel.urban |
| 2010年05月11日 20:41:41 | terry.reedy | set | versions: + Python 2.7, - Python 2.5 |
| 2008年12月09日 15:20:54 | thomaslee | set | nosy:
+ thomaslee messages: + msg77424 |
| 2008年12月03日 12:20:40 | gangesmaster | set | messages: + msg76825 |
| 2008年01月20日 20:01:53 | christian.heimes | set | priority: high |
| 2008年01月20日 18:09:29 | pitrou | set | messages: + msg61321 |
| 2008年01月20日 17:29:07 | pitrou | set | nosy:
+ pitrou messages: + msg61312 severity: normal -> major |
| 2007年12月13日 20:13:37 | gangesmaster | create | |