Issue 32513: dataclasses: make it easier to use user-supplied special methods (original) (raw)
Created on 2018-01-07 17:06 by eric.smith, last changed 2022-04-11 14:58 by admin. This issue is now closed.
Messages (25)
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-07 17:06
Modify dataclasses to make it easier to specify special methods.
For example: currently, if you want to override repr, you need to specify @dataclass(repr=False), and also provide your own repr. Also, it's current an error to not specify repr=False and to also provide your own repr. @dataclass should be able to determine that if you provide repr, you don't want it to provide it's own repr.
The methods that @dataclass will provide for you are: init repr eq ne lt le gt ge hash_ and if using frozen=True, also: setattr delattr
Per the discussion that started at https://mail.python.org/pipermail/python-dev/2017-December/151487.html, the change will be to override these methods only if:
- the method would have been generated based on @dataclass params, and
- the method is not present in the class's dict.
The first item is to allow the user to continue to suppress method generation, and the second item is so that base-class methods are not considered in the decision.
I'm still thinking through how eq and ne should be handled (as well as the ordering comparisons, too). I'll raise an issue on python-dev and point it here.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-15 13:13
Another case to consider is that if a class defines eq, Python will automatically set hash=False before the decorator gets involved. NOte to self: be sure to add a test case for this.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-15 13:19
See #32546 for the hash=None problem.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-22 02:41
Here is my proposal for making it easier for the user to supply dunder methods that dataclasses would otherwise automatically add to the class.
For all of these cases, when I talk about init=, repr=, eq=, order=, hash=, or frozen=, I'm referring to the parameters to the dataclass decorator.
When checking if a dunder method already exists, I mean check for an entry in the class's dict. I never check to see if a member is defined in a base class.
Let's get the easy ones out of the way first.
init If init exists or init=False, don't generate init.
repr If repr exists or repr=False, don't generate repr.
setattr delattr If frozen=True and either of these methods exist, raise a TypeError. These methods are needed for the "frozen-ness" of the class to be implemented, and if they're already set then that behavior can't be enforced.
eq If eq exists or eq=False, don't generate eq.
And now the harder ones:
ne I propose to never generate a ne method. Python will call eq and negate the result for you. If you feel you really need a non-matching ne, then you can write it yourself without interference from dataclasses.
lt le gt ge I propose to treat each of these individually, but for each of them using the value of the order= parameter. So: If lt exists or order=False, don't generate lt. If le exists or order=False, don't generate le. If gt exists or order=False, don't generate gt. If ge exists or order=False, don't generate ge. If for some crazy reason you want to define some of these but not others, then set order=False and write your desired methods.
hash Whether dataclasses might generate hash depends on the values of hash=, eq=, and frozen=. Note that the default value of hash= is None. See below for an explanation.
If hash=False, never generate hash. If hash=True, generate hash unless it already exists.
If hash=None (the default), then use this table to decide whether and how to generate hash: eq=? frozen=? hash False False do not generate hash False True do not generate hash True False set hash to None unless it already exists True True generate hash unless it already exists and is None
Note that it's almost always a bad idea to specify hash=True or hash=False. The action based on the above table (where hash=None, the default), is usually the correct behavior.
One special case to recognize is if the class defines a eq. In this case, Python will assign hash=None before the dataclass decorator is called. The decorator cannot distinguish between these two cases (except possibly by using the order of dict keys, but that seems overly fragile):
@dataclass class A: def eq(self, other): pass
@dataclass class B: def eq(self, other): pass hash = None
This is the source of the last line in the above table: for a dataclass where eq=True, frozen=True, and hash=None, if hash is None it will still be overwritten. The assumption is that this is what the user wants, but it's a tricky corner case. It also occurs if setting hash=True and defining eq. Again, it's not expected to come up in normal usage.
Author: Larry Hastings (larry) *
Date: 2018-01-22 03:08
Do all the parameters to the decorator take a default value of "None", so that you can differentiate between explicit True, explicit False, and did-not-specify?
Is this the complete list of these dunder-method-generation-control parameters? init repr eq order hash
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-22 07:37
Only hash has the tri-state True/False/None behavior, defaulting to None. It's this way because None is the "do what's rational, based on eq and frozen" behavior. None of the other parameters work this way.
There's a long issue in the attrs repo that describes how they came to this conclusion: https://github.com/python-attrs/attrs/issues/136. I think it makes sense.
None of the dataclass parameters have a sentinel that would let me detect if the user provided a value or not. In the case of hash, I can't detect if they explicitly passed hash=None or just didn't provide a value. I've given this some thought, and couldn't come up with a use case for knowing this. For example, if init had a sentinel value of MISSING, what would the code ever do except start with: if init is sentinel: init = True ?
In addition to your list of dunder-control-parameters, there's frozen. It determines if instances are immutable (to the extent that they can be made immutable).
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-22 07:55
Oops, that should have been:
if init is MISSING: init = True
Author: Larry Hastings (larry) *
Date: 2018-01-22 08:58
So if I understand correctly: the default value of the "repr" parameter is True.
Decision matrix: does dataclass insert a repr into the class?
+-- row: argument passed in to decorator's repr parameter | | v | yes | no | <--- columns: does the class have a repr? -------+---------+---------+ True | ??? | yes | -------+---------+---------+ False | no | no | -------+---------+---------+
If the user specifies "repr=True", and also specifies their own repr in the class, does dataclasses stomp on their repr with its own? Does it throw an exception?
I still prefer the tri-state value here. In this version, the default value of the "repr" parameter would be None, and the decision matrix for inserting a repr looks like this:
+-- row: argument passed in to decorator's repr parameter | | v | yes | no | <--- columns: does the class have a repr? -------+---------+---------+ True | raise | yes | -------+---------+---------+ None | no | yes | -------+---------+---------+ False | no | no | -------+---------+---------+
But we've talked about using the tri-state default for all of these parameters before, and clearly you weren't swayed then, so I guess I've said my peace and I'll stop suggesting it.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-22 11:02
The discussion on python-dev was that your ??? box would be "no": if the user supplied repr, they obviously meant for dataclass() to not provide one.
I can't see why the user would say repr=True ("I want dataclass() to add repr"), but then provide a repr and get an exception. That looks like the only functionality added by your repr=True row over the proposal. Where your proposal uses repr=None for the "no", "yes" row, mine uses repr=True.
It's not like there's action at a distance here: the user is writing the class. Especially since base classes are ignored.
I'm ignoring make_dataclasses(), where the user is dynamically creating a class and maybe a repr snuck in. But I don't care so much about that case.
I do think your ascii tables are a good way of explaining this. Thanks! (Now I need a 3D version for eq, frozen, hash!)
Author: Alyssa Coghlan (ncoghlan) *
Date: 2018-01-23 02:36
For the ordering operators, my only question would be whether or not I can rely on them to act like functools.total_ordering: if I supply eq and one of the ordering operators (e.g. lt), will dataclasses make sure the other three ordering operators are consistent with those base methods? Or will it bypass them and act directly on the underlying fields?
My suggestion would be to say that if any of lt, le, gt or ge are defined, then data classes will implicitly generate the other methods based on functools.total_ordering semantics, and will only reference the underlying fields directly if none of them are defined. Otherwise I can see folks defining a single method like "lt", and being surprised when they end up with inconsistent comparison behaviour.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-23 02:51
Rather than re-implementing (and maintaining) functools.total_ordering semantics, I'd rather advise them to specify order=False and just use functools.total_ordering. It's an odd use case for dataclasses, anyway.
Author: Alyssa Coghlan (ncoghlan) *
Date: 2018-01-23 02:57
I'd be fine with that recommendation (since @dataclass(order=False)
and @total_ordering
will compose without any problems), but in that case I'd suggest having "order=True" + any of the ordering methods result in an exception (as you've proposed for frozen=True and the methods that it needs to override).
Author: Guido van Rossum (gvanrossum) *
Date: 2018-01-24 03:27
Are we clear on the proposal now? It would be good to have this implemented in beta 1. Eric's long message looks good to me (though I have to admit the logic around hash confuses me, but I've spent two long days at PyCascades, and I trust Eric's judgment).
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-24 09:12
I'm working on a patch before beta 1. The only issue is whether to raise in the case that order=True and the user specifies one of the 4 ordering dunders. I think raising is okay, especially since order=False is the default: you'd have to go out of your way to cause the exception. I'll mention functools.total_ordering in the exception message.
I do think that the discussion in my long message about hash is correct, although I'm not done thinking it through. I think it's already pretty well tested, but if not I'll make sure there are enough test cases for objects that I think should or shouldn't be hashable that I'll have all of the kinks worked about before beta 1.
Author: Guido van Rossum (gvanrossum) *
Date: 2018-01-25 01:33
Raising for order=True if one of the ordering dunders exists sounds fine.
I am confused by the corner case for hash. Your table:
""" eq=? frozen=? hash False False do not generate hash False True do not generate hash True False set hash to None unless it already exists True True generate hash unless it already exists and is None """
Then you write at the end of that message:
""" One special case to recognize is if the class defines a eq. In this case, Python will assign hash=None before the dataclass decorator is called. The decorator cannot distinguish between these two cases (except possibly by using the order of dict keys, but that seems overly fragile):
@dataclass class A: def eq(self, other): pass
@dataclass class B: def eq(self, other): pass hash = None
This is the source of the last line in the above table: for a dataclass where eq=True, frozen=True, and hash=None, if hash is None it will still be overwritten. The assumption is that this is what the user wants, but it's a tricky corner case. It also occurs if setting hash=True and defining eq. Again, it's not expected to come up in normal usage. """
I think I understand what you are saying there -- the two cases are treated the same, and a hash is created (assuming the decorator is really "@dataclass(eq=True, frozen=True)"), overwriting the "hash = None" for class B.
However the table's last line says "generate hash unless it already exists and is None". Perhaps that was a typo and you meant to write "and is not None"?
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-25 09:40
I'll look at the hash question later today. It's sufficiently complex that I really want to sit down and work through all of the cases.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-26 13:24
Here's where I am on what methods are added under which circumstances. Hopefully these tables don't get mangled.
The more I think about what to do with hash and raising exceptions, the more I think that the right answer is to not raise any exceptions, and just follow the rule of "don't overwrite an attribute that the user specified". The uses of hash=True or hash=None are so specialized that users can be expected to understand the implications. The interaction among hash=, eq=, and frozen= are sufficiently complicated (see the last table below) that we don't need to add any more rules.
There are only 2 places below where exceptions are raised:
frozen=True and the user supplied setattr or delattr. These methods are crucial for frozen-ness, so we must be able to add them.
order=True and the user supplied at least one of lt, le, ge, or gt. This is at Nick's request (see in this issue). I'm not so sure about this one: if the user wants to explicitly ask that these methods be added (by specifying order=True, which is not the default), and they also specify some of the ordering function, then why not let them, and have dataclass add the missing ones as we normally would? But I'm not heavily invested in the outcome, as I don't see any reason to write such code, anyway. The only reason I'd suggest leaving it out is to avoid having to explain it in the documentation.
I have this all coded up and ready to create a PR, modulo a few more tests. I'll get it committed before Beta 1. Hopefully these tables answers the questions about the new proposal.
Conditions for adding methods:
added = method is added
= no action: method is not added
raise = TypeError is raised
None = attribute is set to None
init
+--- init= parameter
|
v | yes | no | <--- class has init?
-------+---------+---------+
False | | |
-------+---------+---------+
True | | added | <- the default
-------+---------+---------+
repr
+--- repr= parameter
|
v | yes | no | <--- class has repr?
-------+---------+---------+
False | | |
-------+---------+---------+
True | | added | <- the default
-------+---------+---------+
setattr
delattr
+--- frozen= parameter
|
v | yes | no | <--- class has setattr or delattr?
-------+---------+---------+
False | | | <- the default
-------+---------+---------+
True | raise * | added |
-------+---------+---------+
* Raise because not adding these methods
would break the "frozen-ness" of the class.
eq
+--- eq= parameter
|
v | yes | no | <--- class has eq?
-------+---------+---------+
False | | |
-------+---------+---------+
True | | added | <- the default
-------+---------+---------+
lt
le
gt
ge
+--- order= parameter
|
v | yes | no | <--- class has any comparison method?
-------+---------+---------+
False | | | <- the default
-------+---------+---------+
True | raise * | added |
-------+---------+---------+
* Raise because to allow this case would interfere with using
functools.total_ordering.
hash
+------------------- hash= parameter
| +----------- eq= parameter
| | +--- frozen= parameter
| | |
v | v | v | yes | no | <--- class has hash?
-------+-------+-------+-------+-------+
False | False | False | | |
-------+-------+-------+-------+-------+
False | False | True | | |
-------+-------+-------+-------+-------+
False | True | False | | |
-------+-------+-------+-------+-------+
False | True | True | | |
-------+-------+-------+-------+-------+
True | False | False | | added | Has no eq, but hashable
-------+-------+-------+-------+-------+
True | False | True | | added | Has no eq, but hashable
-------+-------+-------+-------+-------+
True | True | False | | added | Not frozen, but hashable
-------+-------+-------+-------+-------+
True | True | True | | added | Frozen, so hashable
-------+-------+-------+-------+-------+
None | False | False | | | No eq, use the base class hash
-------+-------+-------+-------+-------+
None | False | True | | | No eq, use the base class hash
-------+-------+-------+-------+-------+
None | True | False | | None | <-- the default, not hashable
-------+-------+-------+-------+-------+
None | True | True | | added | Frozen, so hashable
-------+-------+-------+-------+-------+
For boxes that are blank, hash is untouched and therefore
inherited from the base class. If the base is object, then
id-based hashing is used.
Note that a class may have already hash=None if it specified an
eq method in the class body (not one that was created by
dataclass).
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-26 16:24
I'm working on a better formulation of the hash issue, so don't invest much time in this until I post my next message. I'm going to re-organize the various tables to make them easier to read.
Author: Guido van Rossum (gvanrossum) *
Date: 2018-01-26 16:30
Can you also clarify that this never looks at whether the method is implemented in a superclass?
And clarify what happens with the hash = None that's automatically created when you define eq.
Please also repeat the default value of the flag in each case.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-27 01:52
I apologize for the length of this, but I want to be as precise as possible. I've no doubt made some mistakes, so corrections and discussion are welcomed.
I'm adding the commented text at the end of this message to dataclasses.py. I'm repeating it here for discussion. These tables are slightly different from previous versions on this issue, and I've added line numbers to the hash table to make it easier to discuss. So any further comments and discussion should reference the tables in this message.
I think the init, repr, set/delattr, eq, and ordering tables are not controversial.
** hash
For hash, the 99.9% use case is that the default value of hash=None is sufficient. This is rows 1-4 of that table. It's unfortunate that I have to spend so much of the following text describing cases that I think will rarely be used, but I think it's important that in these special cases we don't do anything surprising.
**** hash=None
First, let's discuss hash=None, which is the default. This is lines 1-4 of the hash table.
Here's an example of line 1:
@dataclass(hash=None, eq=False, frozen=False) class A: i: int
The user doesn't want an eq, and the class is not frozen. Whether or not the user supplied a hash, no hash will be added by @dataclass. In the absense of hash, the base class (in this case, object) hash. object.hash and object.eq are based on object identity.
Here's an example of line 2:
@dataclass(hash=None, eq=False, frozen=True) class A: i: int
This is a frozen class, where the user doesn't want an eq. The same logic is used as for line 1: no hash is added.
Here's an example of line 3, "no" column (no hash). Note that this line shows the default values for hash=, eq=, frozen=:
@dataclass(hash=None, eq=True, frozen=False) class A: i: int
In this case, if the user doesn't provide hash (the "no" column), we'll set hash=None. That's because it's a non-frozen class with an eq, it should not be hashable.
Here's an example of the line 3, "yes" column (hash defined):
@dataclass(hash=None, eq=True, frozen=False) class A: i: int def hash(self): pass
Since a hash exists, it will not be overwritten. Note that this is also true if hash were set to None. We're just checking that hash exists, not it's value.
Here's an example of line 4, "no" column:
@dataclass(hash=None, eq=True, frozen=True) class A: i: int
In this case the action is "add", and a hash method will be created.
And finally, consider line 4, "yes" column. This case is hash=None, eq=True, frozen=True. Here we also apply the auto-hash test, just like we do in the hash=True case (line 12, "yes" column). We want to make the frozen instances hashable, so we add our hash method, unless the user has directly implemented hash.
**** hash=False
Next, consider hash=False (rows 5-8). In this case, @dataclass will never add a hash method, and if one exists it is not modified.
**** hash=True
Lastly, consider hash=True. This is rows 9-12 of the hash table. The user is saying they always want a hash method on the class. If hash does not already exist (the "no" column), then @dataclass will add a hash method. If hash does already exist in the class's dict, @dataclass will overwrite it if and only if the current value of hash is None, and if the class has an eq in the class definition. Let's call this condition the "auto-hash test". The assumption is that the only reason a class has a hash of None and an eq method is that the hash was automatically added by Python's class creation machinery. And if the hash was auto-added, then by the user specifying hash=True, they're saying they want the hash = None overridden by a generated hash method.
I'll give examples from line 9 of the hash table, but this behavior is the same for rows 9-12. Consider:
@dataclass(hash=True, eq=False, frozen=False) class A: i: int hash = None
This is line 9, "yes" column, action="add*", which says to add a hash method if the class passes the auto-hash test. In this case the class fails the test because it doesn't have an eq in the class definition. hash will not be overwritten.
Now consider:
@dataclass(hash=True, eq=False, frozen=False) class A: i: int def eq(self, other): ...
This again is line 9, "yes" column, action="add*". In this case, the user is saying to add a hash, but it already exists because Python automatically added hash=None when it created the class. So, the class passes the auto-hash test. So even though there is a hash, we'll overwrite it with a generated method.
Now consider:
@dataclass(hash=True, eq=False, frozen=False) class A: i: int def eq(self, other): ... def hash(self): ...
Again, this is line 9, "yes" column, action="add*". The existing hash is not None, so we don't overwrite the user's hash method.
Note that a class can pass the auto-hash test but not have an auto-generated hash=None. There's no way for @dataclass to actually know that hash=False was auto-generated, it just assumes that that's the case. For example, this class passes the auth-hash test and hash will be overwritten:
@dataclass(hash=True, eq=False, frozen=False) class A: i: int def eq(self, other): ... hash=None
A special case to consider is lines 11 and 12 from the table. Here's an example of line 11, but line 12 is the same for the purposes of this discussion:
@dataclass(hash=True, eq=True, frozen=False) class A: i: int hash=None
The class will have a generated eq, because eq=True. However, the class still fails the auto-hash test, because the class's dict did not have an eq that was added by the class definition. Instead, it was added by @dataclass. So this class fails the auto-hash test and the hash value will not be overwritten.
Tables follow:
Conditions for adding methods. The boxes indicate what action the
dataclass decorator takes. For all of these tables, when I talk
about init=, repr=, eq=, order=, hash=, or frozen=, I'm referring
to the arguments to the @dataclass decorator. When checking if a
dunder method already exists, I mean check for an entry in the
class's dict. I never check to see if an attribute is defined
in a base class.
Key:
+=========+=========================================+
+ Value | Meaning |
+=========+=========================================+
| | No action: no method is added. |
+---------+-----------------------------------------+
| add | Generated method is added. |
+---------+-----------------------------------------+
| add* | Generated method is added only if the |
| | existing attribute is None and if the |
| | user supplied a eq method in the |
| | class definition. |
+---------+-----------------------------------------+
| raise | TypeError is raised. |
+---------+-----------------------------------------+
| None | Attribute is set to None. |
+=========+=========================================+
init
+--- init= parameter
|
v | | |
| no | yes | <--- class has init in dict?
+=======+=======+=======+
| False | | |
+-------+-------+-------+
| True | add | | <- the default
+=======+=======+=======+
repr
+--- repr= parameter
|
v | | |
| no | yes | <--- class has repr in dict?
+=======+=======+=======+
| False | | |
+-------+-------+-------+
| True | add | | <- the default
+=======+=======+=======+
setattr
delattr
+--- frozen= parameter
|
v | | |
| no | yes | <--- class has setattr or delattr in dict?
+=======+=======+=======+
| False | | | <- the default
+-------+-------+-------+
| True | add | raise |
+=======+=======+=======+
Raise because not adding these methods would break the "frozen-ness"
of the class.
eq
+--- eq= parameter
|
v | | |
| no | yes | <--- class has eq in dict?
+=======+=======+=======+
| False | | |
+-------+-------+-------+
| True | add | | <- the default
+=======+=======+=======+
lt
le
gt
ge
+--- order= parameter
|
v | | |
| no | yes | <--- class has any comparison method in dict?
+=======+=======+=======+
| False | | | <- the default
+-------+-------+-------+
| True | add | raise |
+=======+=======+=======+
Raise because to allow this case would interfere with using
functools.total_ordering.
hash
+------------------- hash= parameter
| +----------- eq= parameter
| | +--- frozen= parameter
| | |
v v v | | |
| no | yes | <--- class has hash in dict?
+=========+=======+=======+========+========+
| 1 None | False | False | | | No eq, use the base class hash
+---------+-------+-------+--------+--------+
| 2 None | False | True | | | No eq, use the base class hash
+---------+-------+-------+--------+--------+
| 3 None | True | False | None | | <-- the default, not hashable
+---------+-------+-------+--------+--------+
| 4 None | True | True | add | add* | Frozen, so hashable
+---------+-------+-------+--------+--------+
| 5 False | False | False | | |
+---------+-------+-------+--------+--------+
| 6 False | False | True | | |
+---------+-------+-------+--------+--------+
| 7 False | True | False | | |
+---------+-------+-------+--------+--------+
| 8 False | True | True | | |
+---------+-------+-------+--------+--------+
| 9 True | False | False | add | add* | Has no eq, but hashable
+---------+-------+-------+--------+--------+
|10 True | False | True | add | add* | Has no eq, but hashable
+---------+-------+-------+--------+--------+
|11 True | True | False | add | add* | Not frozen, but hashable
+---------+-------+-------+--------+--------+
|12 True | True | True | add | add* | Frozen, so hashable
+=========+=======+=======+========+========+
For boxes that are blank, hash is untouched and therefore
inherited from the base class. If the base is object, then
id-based hashing is used.
Note that a class may have already hash=None if it specified an
eq method in the class body (not one that was created by
@dataclass).
Author: Alyssa Coghlan (ncoghlan) *
Date: 2018-01-27 02:58
Eric's current proposal sounds sensible to me, but I'll note that if we deem it necessary, the code that implicitly sets __hash__ = None
to override object.hash when eq is defined could also set some other marker to make it more explicit that that happened.
I think Eric's proposed heuristic will be sufficient, though - for subclasses that inherit both hash and eq from a base class the dataclass decorator won't pay any attention to either of them, and if a base class has set hash to something, then the type creation machinery won't inject "hash = None" into the class being defined.
Author: Guido van Rossum (gvanrossum) *
Date: 2018-01-27 16:32
If Nick says it's okay it is okay. I think it's okay too but my brain doesn't want to start up today -- I suggest not waiting for me.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-27 16:36
I'm completely burned out on it, too. I'll get the code checked in today, then update the PEP later. I don't really want to add all of this reasoning to the PEP, but I guess I should.
I think the gist of it is correct, in any event. We can fix some corner cases during the betas, if need be.
Thanks Guido and Nick for looking at it.
Author: Eric V. Smith (eric.smith) *
Date: 2018-01-28 00:07
New changeset ea8fc52e75363276db23c6a8d7a689f79efce4f9 by Eric V. Smith in branch 'master': bpo-32513: Make it easier to override dunders in dataclasses. (GH-5366) https://github.com/python/cpython/commit/ea8fc52e75363276db23c6a8d7a689f79efce4f9
Author: Guido van Rossum (gvanrossum) *
Date: 2018-01-29 16:49
Nit: I think the presentation of the 12-row table in can be improved, e.g. by putting the legend for add* closer and/or giving add* a clearer name, and by splitting it into three 4-row tables. Currently the legend comes before all the other tables, and those aren't very useful (they're all the same, except for 'order', and the exception there is better explained in text).
But that's a matter for whoever writes the docs. As a spec it is unambiguous. On python-dev I've just posted that I support this version.
History
Date
User
Action
Args
2022-04-11 14:58:56
admin
set
github: 76694
2018-11-09 02:30:23
xtreak
set
pull_requests: + <pull%5Frequest9702>
2018-11-07 19:33:29
xtreak
set
pull_requests: - <pull%5Frequest9682>
2018-11-07 19:32:31
xtreak
set
pull_requests: + <pull%5Frequest9682>
2018-01-29 16:49:14
gvanrossum
set
messages: +
2018-01-28 00:09:26
eric.smith
set
status: open -> closed
resolution: fixed
stage: patch review -> resolved
2018-01-28 00:07:42
eric.smith
set
messages: +
2018-01-27 20:59:22
eric.smith
set
keywords: + patch
pull_requests: + <pull%5Frequest5209>
2018-01-27 16:36:15
eric.smith
set
messages: +
2018-01-27 16:32:34
gvanrossum
set
messages: +
2018-01-27 02:58:34
ncoghlan
set
messages: +
2018-01-27 01:52:18
eric.smith
set
messages: +
2018-01-26 16:30:13
gvanrossum
set
messages: +
2018-01-26 16:24:32
eric.smith
set
messages: +
2018-01-26 13:24:50
eric.smith
set
messages: +
2018-01-25 09:40:05
eric.smith
set
messages: +
2018-01-25 01:33:14
gvanrossum
set
messages: +
2018-01-24 09:12:12
eric.smith
set
messages: +
2018-01-24 03:27:32
gvanrossum
set
nosy: + gvanrossum
messages: +
2018-01-23 02:57:51
ncoghlan
set
messages: +
2018-01-23 02:51:08
eric.smith
set
messages: +
2018-01-23 02:36:18
ncoghlan
set
nosy: + ncoghlan
messages: +
2018-01-22 11:02:11
eric.smith
set
messages: +
2018-01-22 08:58:14
larry
set
messages: +
2018-01-22 07:55:52
eric.smith
set
messages: +
2018-01-22 07:37:56
eric.smith
set
messages: +
2018-01-22 03:08:41
larry
set
nosy: + larry
messages: +
2018-01-22 02:41:21
eric.smith
set
keywords: - patch
messages: +
2018-01-21 20:34:14
rhettinger
set
keywords: + patch
stage: patch review
pull_requests: + <pull%5Frequest5108>
2018-01-15 13:19:37
eric.smith
set
messages: +
2018-01-15 13:13:56
eric.smith
set
messages: +
2018-01-08 00:57:35
levkivskyi
set
nosy: + levkivskyi
2018-01-07 17:06:05
eric.smith
create