Issue 1294232: Error in metaclass search order (original) (raw)
Created on 2005-09-18 01:07 by pwerneck, last changed 2022-04-11 14:56 by admin. This issue is now closed.
Messages (31)
Author: Pedro Werneck (pwerneck)
Date: 2005-09-18 01:07
In a simple class hierarchy, I have class A with metaclass M_A and class B, subclass of A, with metaclass M_B, subclass of M_A, as required.
A new class C, subclass of B, must have M_B or a subclass of it as subclass, or a TypeError, metaclass conflict exception is raised. The exception is raised in a multiple class hierarchy (diamond, trees, etc) or in a single class hierarchy when using a metaclass with no relation to M_A and M_B.
If M_A or type are used as C metaclass, the interpreter is ignoring dict['metaclass'], which has priority over B.class and using M_B, when it was supposed to raise TypeError, with the "metaclass conflict" error message.
More details in attached file.
Author: Rodrigo Dias Arruda Senra (rodsenra)
Date: 2005-09-18 23:04
Logged In: YES user_id=9057
I have discussed this at length with Pedro Werneck by email. I personally believe the best path to follow is to document that the entity specified in metaclass inside C class body, can be automagically replaced by the most specialized metaclass among the metaclasses associated to C ancestors. I think that will suffice for the meta-adventurous.
Author: Pedro Werneck (pwerneck)
Date: 2005-09-19 00:42
Logged In: YES user_id=696687
Yes. I think this confusion was caused because of the lack of documentation on this topic, especially on the case described here, which seems to break some rules.
Since the "Unifying types and classes" essay seems to be the most used Python document about this topic and, I suggest the first rule on determining a metaclass be changed from:
"If dict['metaclass'] exists, it is used."
To something like:
"If dict['metaclass'] exists and is equal to, or a subclass of, each of the metaclasses of the bases, it is used; if it exists and is a base class of any metaclass of the bases, the most specialized metaclass in the hierarchy is used; if it exists and doesn't satisfies any of these constraints, TypeError is raised."
Author: Terry J. Reedy (terry.reedy) *
Date: 2010-08-07 21:24
Is the 3.1/2 doc still lacking in this area?
Author: Daniel Urban (daniel.urban) *
Date: 2010-08-08 07:56
I think the situation is a bit more complicated. Here is the example described by Pedro Werneck (this is py3k, but its essentially the same in 2.x):
class M_A(type): ... def new(mcls, name, bases, ns): ... print('M_A.new') ... return super().new(mcls, name, bases, ns) ... class A(metaclass=M_A): pass ... M_A.new
class M_B(M_A): ... def new(mcls, name, bases, ns): ... print('M_B.new') ... return super().new(mcls, name, bases, ns) ... class B(A, metaclass=M_B): pass ... M_B.new M_A.new
class C(B, metaclass=M_A): pass ... M_A.new M_B.new M_A.new
As it is clear from the last three lines, the given metaclass (M_A) to C is not ignored. It is actually called (and produces the 'M_A.new' output line). Then M_A.new calls type.new (with super()). type.new then searches the bases of C, find the metaclass of B, M_B, and calls its new. M_B.new then prints the line 'M_B.new', then calls M_A.new again (with super()). This produces the last line, 'M_A.new'. So the trick is in type.new.
Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) *
Date: 2010-09-13 15:35
What also worries me is the difference between the "class" statement and the type() function.
class M_A(type): def new(mcls, name, bases, ns): print('M_A.new', mcls, name, bases) return super().new(mcls, name, bases, ns)
class M_B(M_A): def new(mcls, name, bases, ns): print('M_B.new', mcls, name, bases) return super().new(mcls, name, bases, ns)
class A(metaclass=M_A): pass class B(metaclass=M_B): pass
class C(A, B): pass D = type('D', (A, B), {})
The construction of C and D won't print the same messages.
Author: Daniel Urban (daniel.urban) *
Date: 2010-12-05 12:15
What also worries me is the difference between the "class" statement and the type() function.
I think the reason of this is that the class statement uses the build_class builtin function. This function determines the metaclass to use (by getting the metaclass of the first base class), and calls it. When one directly calls type, one doesn't call the metaclass (though type.new will later call the "real" metaclass).
An example:
class M_A(type): ... def new(mcls, name, bases, ns): ... print('M_A.new', mcls, name, bases) ... return super().new(mcls, name, bases, ns) ... class M_B(M_A): ... def new(mcls, name, bases, ns): ... print('M_B.new', mcls, name, bases) ... return super().new(mcls, name, bases, ns) ... class A(metaclass=M_A): pass ... M_A.new <class '__main__.M_A'> A ()
class B(metaclass=M_B): pass ... M_B.new <class '__main__.M_B'> B () M_A.new <class '__main__.M_B'> B ()
class C(A, B): pass ... M_A.new <class '__main__.M_A'> C (<class '__main__.A'>, <class '__main__.B'>) M_B.new <class '__main__.M_B'> C (<class '__main__.A'>, <class '__main__.B'>) M_A.new <class '__main__.M_B'> C (<class '__main__.A'>, <class '__main__.B'>)
Above build_class calls M_A (because that is the metaclass of the first base class, A). Then M_A calls type.new with super(), then type.new searches the "real" metaclass, M_B, and calls its new. Then M_B.new calls again M_A.new.
D = type('D', (A, B), {}) M_B.new <class '__main__.M_B'> D (<class '__main__.A'>, <class '__main__.B'>) M_A.new <class '__main__.M_B'> D (<class '__main__.A'>, <class '__main__.B'>)
Above type.call directly calls type.new, which determines the "real" metaclass, M_B, and calls it (which then class M_A):
class C2(B, A): pass ... M_B.new <class '__main__.M_B'> C2 (<class '__main__.B'>, <class '__main__.A'>) M_A.new <class '__main__.M_B'> C2 (<class '__main__.B'>, <class '__main__.A'>)
If we reverse the order of the base classes of C (as above for C2), build_class will use M_B as the metaclass.
D2 = M_B('D', (A, B), {}) M_B.new <class '__main__.M_B'> D (<class '__main__.A'>, <class '__main__.B'>) M_A.new <class '__main__.M_B'> D (<class '__main__.A'>, <class '__main__.B'>)
And of course, if we call directly the "real" metaclass, M_B (as above), we get the same result.
I used the expression "real metaclass" with the meaning "the class of the class we are currently creating":
C.class <class '__main__.M_B'> C2.class <class '__main__.M_B'> D.class <class '__main__.M_B'> D2.class <class '__main__.M_B'>
Summary: the problem seems to be, that build_class doesn't call the "real" metaclass, but the metaclass of the first base. (Note: I think this is approximately consistent with the documentation: "Otherwise, if there is at least one base class, its metaclass is used." But I don't know, if this is the desired behaviour.)
This behaviour of build_class can result in problems. For example, if the two metaclasses define prepare. In some cases build_class won't call the "real" metaclass' prepare, but the other's:
class M_A(type): ... def new(mcls, name, bases, ns): ... print('M_A.new', mcls, name, bases) ... return super().new(mcls, name, bases, ns) ... @classmethod ... def prepare(mcls, name, bases): ... print('M_A.prepare', mcls, name, bases) ... return {} ... class M_B(M_A): ... def new(mcls, name, bases, ns): ... print('M_B.new', mcls, name, bases, ns) ... return super().new(mcls, name, bases, ns) ... @classmethod ... def prepare(mcls, name, bases): ... print('M_B.prepare', mcls, name, bases) ... return {'M_B_was_here': True} ...
The prepare method of the two metaclass differs, M_B leaves a 'M_B_was_here' name in the namespace.
class A(metaclass=M_A): pass ... M_A.prepare <class '__main__.M_A'> A () M_A.new <class '__main__.M_A'> A ()
class B(metaclass=M_B): pass ... M_B.prepare <class '__main__.M_B'> B () M_B.new <class '__main__.M_B'> B () {'M_B_was_here': True, 'module': 'main'} M_A.new <class '__main__.M_B'> B ()
class C(A, B): pass ... M_A.prepare <class '__main__.M_A'> C (<class '__main__.A'>, <class '__main__.B'>) M_A.new <class '__main__.M_A'> C (<class '__main__.A'>, <class '__main__.B'>) M_B.new <class '__main__.M_B'> C (<class '__main__.A'>, <class '__main__.B'>) {'module': 'main'} M_A.new <class '__main__.M_B'> C (<class '__main__.A'>, <class '__main__.B'>)
'M_B_was_here' in C.dict False
build_class calls M_A.prepare, so the new class won't have a 'M_B_was_here' attribute (though its class is M_B).
class C2(B, A): pass ... M_B.prepare <class '__main__.M_B'> C2 (<class '__main__.B'>, <class '__main__.A'>) M_B.new <class '__main__.M_B'> C2 (<class '__main__.B'>, <class '__main__.A'>) {'M_B_was_here': True, 'module': 'main'} M_A.new <class '__main__.M_B'> C2 (<class '__main__.B'>, <class '__main__.A'>)
'M_B_was_here' in C2.dict True
I we reverse the order of the bases, M_B.prepare is called.
C.class <class '__main__.M_B'> C2.class <class '__main__.M_B'>
But the "real" metaclass of both classes is M_B.
(Sorry for the long post.)
Author: Daniel Urban (daniel.urban) *
Date: 2011-01-28 16:30
It seems, that this possible problem already came up when build_class got implemented, see . The second and third version of the patch at this issue ( and ) contains a comment: "XXX Should we do the "winner" calculation here?". But the next version, which contains the C implementation of build_class still uses simply the first base. The "winner calculation" probably refers to the algorithm determining the proper metaclass in lines 1950-1976 of typeobject.c (in type_new).
Author: Terry J. Reedy (terry.reedy) *
Date: 2011-04-03 19:58
I would make the same guess about 'winner calculation'. I am surprised that the class statement does not result in the same calculation (why else would type_new do it). Perhaps build_class (which I have not read) should either call type_new or a new function with the winner calculation factored out of type_new.
I suggest you repost your simplified example from the list here and use it as the basis of a test. Guido agreed that it shows a bug that might be backported to 3.2 (after discussion). If not also backported to 2.7, a separate 2.7 doc patch might be needed.
Author: Daniel Urban (daniel.urban) *
Date: 2011-04-03 20:44
The attached test case currently fails. I'll try to make a patch soon.
Perhaps build_class (which I have not read) should either call type_new or a new function with the winner calculation factored out of type_new.
Yeah, that's exactly what I'm planning to do (the factoring out).
Author: Daniel Urban (daniel.urban) *
Date: 2011-04-04 09:50
The attached patch seems to correct this issue. It contains the test attached yesterday, and it passes now.
I factored out the winner calculation from type_new to a new _PyType_CalculateWinner function, and type_new calls this. I've put the declaration of this function into object.h, so build_class can also call it, instead of using the metaclass of the first base. (Am I correct in thinking that the underscore prefix keeps it out of the public API?)
A slight problem may be, that in some cases this function will be called twice. But it is quite simple, so I don't think it matters much:
Without patch: $ ./python -m timeit -- "class A(type): pass class B: pass class C(metaclass=A): pass class D(B, C): pass " 10000 loops, best of 3: 371 usec per loop
With patch: $ ./python -m timeit -- "class A(type): pass class B: pass class C(metaclass=A): pass class D(B, C): pass " 10000 loops, best of 3: 381 usec per loop
(Note, that I generated the patch with hg extdiff, because the output of hg qdiff was much more unreadable than simple diff. I can provide an equivalent patch generated by hg if needed.)
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-04-19 13:26
Yep, the leading underscore and the fact you added it to a block of code that is skipped when Py_LIMITED_API is defined makes it OK to include the prototype in object.h.
However, I would suggest _PyType_CalculateMetaclass as the name - CalculateWinner is a bit vague without the specific context of calculating the metaclass.
On a broader point, I think there is an issue that needs to be brought up on python-dev: in light of PEP 3115, "type(name, bases, ns)" is no longer an entirely safe way to create dynamic types, as it bypasses prepare methods. There should be an official way to access the full class building process, including correct invocation of prepare methods (likely by making build_class an official part of the language spec rather than a CPython implementation detail).
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-04-19 13:37
Removed 2.7 from the affected versions - with neither build_class nor prepare in 2.x, there's no alternate metaclass calculation to go wrong.
This should definitely be fixed for the final 3.1 release and for 3.2 though. An updated patch against current 3.1 would be most convenient (hg should take care of the forward porting from that point).
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-04-19 14:03
Sorry, my last review wasn't right and the patch is incorrect as it stands. After digging a little deeper, there is still a case that isn't covered correctly in build_class.
Specifically, the case where the most derived class has a declared metatype that is the parent of the metaclass that should be used.
This can be seen in type_new, where it does the "if (winner != metatype)" check. There is no equivalent check in build_class - instead, the explicitly declared metaclass always wins.
What needs to happen is that the call to _PyType_CalculateMetaclass in build_class should be moved out so that it is unconditional. The passed in metatype will either by the explicitly declared one (if it exists) or else PyType_Type. An additional test to pick up this case is also needed.
Author: Daniel Urban (daniel.urban) *
Date: 2011-04-19 20:43
Thanks for the review!
I've updated my patch:
- renamed it to _PyType_CalculateMetaclass
- in build_class call it even when a metaclass is declared
- added a test for this case (which fails with my previous patch)
However I noticed another problem: the declared metaclass (the object passed with the metaclass keyword in the class definition) according to PEP 3115 can be any callable object, not only a PyTypeObject. Problems:
In this case, PyType_IsSubtype will be called on something that is not a PyTypeObject (I don't know if that's a big problem, currently it seems to work).
The bigger problem: a simple construct, like:
class X(object, metaclass=func): pass
(where func is for example a function) won't work, because in _PyType_CalculateMetaclass it will detect, that func isn't a super- or subtype of object.class, and will raise an exception: "metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases".
My first idea to solve this problem is to ignore this case in build_class (check for a returned NULL, and call PyErr_Clear), and use the declared metaclass. (I don't know, if this can cause other problems, I haven't thought much about it yet.)
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-04-20 01:07
This last point sounds like an error in PEP 3115 rather than an error in the implementation - as the exception says, the metaclass of a derived class must be the same as or a subclass of the metaclasses of all of its bases. Since that rule applies recursively, and all classes in 3.x implicitly inherit from object, it follows that all metaclasses must directly or indirectly inherit from type.
Author: Daniel Urban (daniel.urban) *
Date: 2011-04-20 19:26
That may be, but with my latest patch, this works (func is a function):
class X(metaclass=func): pass
But this blows up with a TypeError:
class X(object, metaclass=func): pass
Is this the desired behaviour? Or should we disallow non-class metaclasses in every case? (And what about backwards-compatibility?)
Author: Guido van Rossum (gvanrossum) *
Date: 2011-04-20 20:22
class X(metaclass=func) should definitely continue to work; that is a long-standing (if relatively unknown, and very advanced) feature. In fact, metaclasses have their origins in this, via the "Don Beaudry hook" -- see http://python-history.blogspot.com/2009/04/metaclasses-and-extension-classes-aka.html .
IMO we should also keep class X(object, metaclass=func) working; it should just construct a tuple of bases (object,) and pass it to func.
I realize there is now a circular dependency: you need the metaclass computation in order to find the metaclass and you need the metaclass in order to find the prepare hook, but you need to call the prepare hook before you call the metaclass and hence before the metaclass computation is carried out. I'm not sure how to reconcile that but I think we should look harder rather than give up.
As to how PEP 3115 seems to imply that all classes derive from object, that only applies as long as their metaclass is (derived from) type. And that is in a sense a tautology since (dynamically) we only call something a class if its metaclass is (derived from) type. However the class statement does not necessarily create a class object! It creates whatever the metaclass creates, with suitable defaults: if there's no explicit metaclass, look at the class of the first given base; if no bases are given either, use object. But an explicit metaclass should win.
Maybe the metaclass computation (which should come up with the "most derived" metaclass if there are multiple bases or bases and a metaclass) is something that we can give a name as another attribute on the "initial" metaclass, and a suitable default? Let's say compute_metaclass(tuple_of_bases). The default could be the initial metaclass, and type could override it to do the correct computation (which computes the most derived metaclass, and raises an error if any of the bases has a metaclass that is not in the answer's ancestor). Then this computation could run before we get prepare (from the answer). When metaclass=func is given one could have prepare as a function attribute and even the compute_metacass could be overridden as a function attribute, so everything is still fully general.
[Sorry for blathering on. Not feeling great.]
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-04-21 14:11
I think PEP 3115 is OK - the error about all metaclasses inheriting from type was a mistake on my part (I thought the ability to create non-type metaclasses went away along with old-style classes, but I was simply wrong on that point).
That got me curious as to how the explicit inheritance from object + explicit non-type metaclass case was working in 2.7, and it turns out it does share the same initial metaclass determination error as 3.x - it is just that build_class() is embedded in ceval.c rather than being published as a builtin.
So I have two conclusions:
to match existing behaviour when metaclass is not an instance of type(), build_class still needs one special case where it bypasses the normal metaclass calculation: when an explicit metaclass exists and is not an instance of type.
after we get 3.x sorted, we will want to backport this to the ceval version of build_class() in 2.7
Author: Guido van Rossum (gvanrossum) *
Date: 2011-04-21 15:48
Thanks for diving deep! How much of this can we claim as a bug and how much as a feature?
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-04-21 16:36
As near as I can tell, the only visible behavioural change with Daniel's patch (updated as per my last comment) will be that the two error cases noted above (i.e. when an explicit metaclass or the first parent type's metaclass is not the most derived metaclass) will now correctly invoke the real metaclass immediately, instead of first traversing up the chain to type(), which then jumps all the way back down to the most derived metaclass.
The "new" special case is actually just a matter of preserving the current behaviour in the one situation where that is the right thing to do.
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-04-21 16:46
Commenting on the latest patch here, since the Rietveld integration isn't coping with the "hg extdiff" output (I would guess that the revision numbers in the pathnames are confusing the script that attempts to determine the base revision).
Aside from the note above about needing to restore the special case handling for non-type metaclasses, the only other recommendation I have is for the new metaclass invocation tests to add a second metaclass to the hierarchy and explicitly check the order of the new calls, as in the original examples above that invoked the wrong M_A/M_B/M_A sequence. Those tests will then also be applicable to Python 2.7 (which doesn't have prepare_).
Author: Daniel Urban (daniel.urban) *
Date: 2011-04-21 22:00
I'm attaching the updated patch. Changes:
- Special casing objects we can't do the metaclass computation for.
- Tests for these.
- Adding tests for the order of new calls.
The special case isn't just checking if the object is a class, but instead checking if "isinstance(obj, type) and issubclass(obj, type)", because I think only these are the objects we can do the metaclass calculation for. All other objects (including classes which are not "real" metaclasses (they do not derive from type) and functions) are used "as is", without any metaclass calculation. (It's late here, so it is quite possible that I've overlooked someting in this part.)
(I'm using "hg extdiff" because the normal hg diff results in a longer and harder to read patch, but if it's needed I will send a normal diff.)
Author: Daniel Urban (daniel.urban) *
Date: 2011-04-21 22:35
I've just realized, that my patch still breaks a case, that previously worked: when the bases are not classes.
This works in 3.2, but not with my patch:
class Foo: # not a subclass of type! ... def new(mcls, name='foo', bases=(), namespace={}): ... self = super().new(mcls) ... self.name = name ... return self ... foo1 = Foo('foo1') foo1.name 'foo1'
foo2 = Foo('foo2') foo2.name 'foo2'
class foo3(foo1, foo2):pass ... foo3 <__main__.Foo object at 0xb74aa96c> foo3.name 'foo3'
This raises a TypeError: "metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases". In this case the type of all of its bases is the same (Foo), but that type is not a metaclass, but a regular class.
Right now I don't know if this is a real problem, or how to solve it.
Author: Daniel Urban (daniel.urban) *
Date: 2011-04-22 05:41
Okay, probably the check in my previous patch was too strict (sorry for the noise). I'm attaching an updated patch again. Now the algorithm in build_class is this:
If an object is explicitly given with the metaclass keyword, that is the "starting metaclass".
Else if there are bases, the type of the first base is the starting metaclass (note, that this possibly will be replaced later, but before the prepare call).
Else the starting metaclass is type.
If the starting metaclass is a class, do the metaclass calculation, so the starting metaclass may be replaced with its most derived descendant.
Else we cannot do the metaclass calculation, so use the starting metaclass as is.
There are also tests for these cases, and the example in now works too.
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-09-18 12:27
Looking at Daniel's updated patch is still on my to-do list, but I won't object if anyone else wants to take this forward (it will be at least a few weeks before I get to it).
Author: Roundup Robot (python-dev)
Date: 2011-10-23 12:37
New changeset c2a89b509be4 by Nick Coghlan in branch '3.2': Issue 1294232: Fix errors in metaclass calculation affecting some cases of metaclass inheritance. Patch by Daniel Urban. http://hg.python.org/cpython/rev/c2a89b509be4
New changeset c72063032a7a by Nick Coghlan in branch 'default': Merge issue 1294232 patch from 3.2 http://hg.python.org/cpython/rev/c72063032a7a
Author: Alyssa Coghlan (ncoghlan) *
Date: 2011-10-23 12:41
Fix has been applied to 3.x and hence will be in 3.3 and the next 3.2 release.
I have adjusted the issue metadata to reflect the fact 2,7 still exhibits the problem, but the patch requires significant work to account for the 3.x vs 2.x changes in class creation before it can be backported.
Author: Florent Xicluna (flox) *
Date: 2011-10-28 10:00
After changeset c72063032a7a I get this complain:
Python/bltinmodule.c: In function ‘builtin___build_class__’: Python/bltinmodule.c:43: warning: unused variable ‘nbases’
Author: Roundup Robot (python-dev)
Date: 2011-10-28 13:07
New changeset b9bb9340eb0c by Florent Xicluna in branch 'default': Merge 3.2 (linked to issue #1294232) http://hg.python.org/cpython/rev/b9bb9340eb0c
Author: Serhiy Storchaka (serhiy.storchaka) *
Date: 2020-05-31 12:39
Python 2.7 is no longer supported.
History
Date
User
Action
Args
2022-04-11 14:56:13
admin
set
github: 42380
2020-05-31 12:39:15
serhiy.storchaka
set
status: open -> closed
nosy: + serhiy.storchaka
messages: +
resolution: fixed
stage: needs patch -> resolved
2012-12-06 02:07:04
bruno.dupuis
set
nosy: + bruno.dupuis
2012-05-07 12:05:37
ncoghlan
set
assignee: ncoghlan ->
2011-12-17 02:00:20
barry
set
nosy: + barry
2011-10-28 13:07:07
python-dev
set
messages: +
2011-10-28 10:00:10
flox
set
nosy: + flox
messages: +
2011-10-23 12:41:32
ncoghlan
set
stage: patch review -> needs patch
messages: +
components: - Documentation
versions: - Python 3.1, Python 3.2, Python 3.3
2011-10-23 12:37:17
python-dev
set
nosy: + python-dev
messages: +
2011-09-18 22:49:14
meador.inge
set
nosy: + meador.inge
stage: test needed -> patch review
2011-09-18 12:27:17
ncoghlan
set
messages: +
2011-07-09 20:51:58
eric.snow
set
nosy: + eric.snow
2011-06-10 07:04:35
ncoghlan
set
assignee: ncoghlan
2011-04-22 05:43:21
daniel.urban
set
files: + issue_1294232_4.patch
2011-04-22 05:42:57
daniel.urban
set
files: - issue11256_4.patch
2011-04-22 05:41:24
daniel.urban
set
files: + issue11256_4.patch
messages: +
2011-04-21 22:35:34
daniel.urban
set
messages: +
2011-04-21 22:00:36
daniel.urban
set
files: + issue_1294232_3.patch
messages: +
2011-04-21 16:46:51
ncoghlan
set
messages: +
2011-04-21 16:36:46
ncoghlan
set
messages: +
2011-04-21 15:48:21
gvanrossum
set
messages: +
2011-04-21 14:11:30
ncoghlan
set
messages: +
versions: + Python 2.7
2011-04-20 20:22:32
gvanrossum
set
messages: +
2011-04-20 19:26:38
daniel.urban
set
messages: +
2011-04-20 01:07:31
ncoghlan
set
messages: +
2011-04-19 20:43:30
daniel.urban
set
files: + issue_1294232_2.patch
messages: +
2011-04-19 14:03:33
ncoghlan
set
messages: +
2011-04-19 13:37:07
ncoghlan
set
messages: +
versions: - Python 2.7
2011-04-19 13:26:48
ncoghlan
set
assignee: docs@python -> (no value)
messages: +
nosy: + ncoghlan
2011-04-04 09:50:46
daniel.urban
set
files: + issue_1294232.patch
messages: +
2011-04-03 20:44:03
daniel.urban
set
files: + test_issue1294232.patch
type: enhancement -> behavior
components: + Interpreter Core
keywords: + patch
nosy: + gvanrossum, benjamin.peterson
messages: +
2011-04-03 20:07:25
terry.reedy
set
versions: + Python 3.3
2011-04-03 19:58:13
terry.reedy
set
messages: +
2011-01-28 16:30:52
daniel.urban
set
nosy:georg.brandl, terry.reedy, amaury.forgeotdarc, rodsenra, pwerneck, daniel.urban
messages: +
2010-12-05 12:15:56
daniel.urban
set
messages: +
2010-10-29 10:07:21
admin
set
assignee: georg.brandl -> docs@python
2010-09-13 15:35:11
amaury.forgeotdarc
set
nosy: + amaury.forgeotdarc
messages: +
2010-08-08 07:56:35
daniel.urban
set
nosy: + daniel.urban
messages: +
2010-08-07 21:24:34
terry.reedy
set
nosy: + terry.reedy
messages: +
versions: + Python 3.1, Python 2.7, Python 3.2, - Python 2.6
2009-10-22 16:08:38
georg.brandl
link
2009-09-16 23:24:00
terry.reedy
set
assignee: georg.brandl
nosy: + georg.brandl
2009-03-20 22:30:47
ajaksu2
set
priority: normal -> low
stage: test needed
type: enhancement
versions: + Python 2.6, - Python 2.4
2005-09-18 01:07:12
pwerneck
create