
I know this has come up in the past. Could the consensus have changed regarding bool's inheritance from int? This is not intuitive (to me), and recently bit me as a bug: Python 3.9.1 (default, Dec 13 2020, 11:55:53) [GCC 10.2.0] on linux Type "help", "copyright", "credits" or "license" for more information.
{1,True} {1}
I've worked around it by storing tuple(type, value) in the set, which is fugly. Yes, I need mixed types in the set, and I'm using the set for efficient lookup. A contrived example I dreamed-up, which I also find non-intuitive:
Maybe a wish list item for Python 4.0?

On Mon, Dec 21, 2020 at 8:26 AM Paul Bryan <pbryan@anode.ca> wrote:
Wouldn't that still work if bool's __int__ returns 1?
Yes, there are ways you could accomplish the `sum(trues)` pattern other than making bool a subclass of int. But as Chris points out, the value of `1 == True == 1.0` is pretty fundamental to many other patterns also. And likewise, things being in sets by equality rather than identity is likewise fundamental. The change you suggest to make `True != 1` "breaks the world".
-- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

21.12.20 10:11, Paul Bryan пише:
Wouldn't that still work if bool's __int__ returns 1?
Implementing `__index__` would solve 90% of all problems with non-int booleans. But not the example provided by David (it requires `__add__`). And there may be more specific cases when you need to make arithmetic or bit operations with booleans or sort them. In result you would need to implement all method inherited from int in new bool. And what is the difference?

On Mon, Dec 21, 2020 at 7:15 PM Paul Bryan <pbryan@anode.ca> wrote:
Hmm, I see ints, floats and decimal.Decimal all produce the same hashes for integer values, so they also suffer the same fate in sets and dicts.
Yes, because they compare equal. (The hash is secondary to that.) Since 1 == 1.0, they must be coalesced in a set. ChrisA

Surely what you're looking for is some kind of typed hash table? Maybe you should be suggesting adding a TypedMapping to collections or something like that? But it seems to be your solution is fine, but maybe could abstract away the types in the keys in the class dunders? ``` class TypedMapping(dict): def __getitem__(self, item): return super().__getitem__((item, type(item))) ... ``` And so on

Surely what you're looking for is some kind of typed hash table?
Maybe, maybe not. My impression is that the Typed hash table is a kluge to get around this one issue. As others have pointed out, 1 and 1.0 are considered equal, and thus hash the same — a typed hash table would not consider them the same. And while maybe useful, it would violate the spirit of duck typing. If you really want that kind of static typing, maybe Python is not the language for the job. (And since booleans are subclasses of Ints, they might still class in a typed has table, depending on the implementation) What I think the OP wants, and I happen to agree, is for Booleans to not only not BE integers in the subclassing sense, but also to not behave as numbers in the. Duck Typing sense. That is: True == 1 # false True * 4. # Exception Etc. However, that ship has sailed. I think it would have been minimally disruptive when True and False were first introduced, but it’s way too late now. There is far too much code out there that expects the bools to behave like integers. To the OP: perhaps you could create your own non-integer Bool type, and then put a wrapper around set and/or dict, that substitutes it on insertion. Whether that is more or less kludgy than your solution depends on your use case. -CHB Maybe you should be suggesting adding a TypedMapping to collections or
-- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Mon, Dec 21, 2020 at 5:27 PM Christopher Barker <pythonchb@gmail.com> wrote:
I've occasionally wanted an "identity set". Except, after I want it, I pretty quickly always realize I don't actually want it. Exactly what strings and integers will actually be interned, etc? There's a lot of implementation dependent stuff there that isn't a clear semantics (I mean, it's knowable, but too complex). In any case, I know how to write that in a few lines if I feel like having it. Basically just use pairs `(id(x), x)` in a set. That's more specific than `(type(x), x)`, although both have their own gotchas. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

On Mon, Dec 21, 2020 at 5:56 PM Paul Bryan <pbryan@anode.ca> wrote:
I'm interested in knowing when (id(x), x) would be preferable over (type(x), x).
Something along these lines:
-- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

On Tue, Dec 22, 2020 at 5:20 AM David Mertz <mertz@gnosis.cx> wrote:
If equality is based on self.x and self.y, the easiest way to do the hash is the same way: def __hash__(self): return hash((self.x, self.y)) But the real question is: Why do points compare equal based on their locations, if you need them to be independently stored in a set? Logically, if they are equal, the set either contains that one thing or it doesn't. ChrisA

On Mon, Dec 21, 2020 at 6:32 PM Chris Angelico <rosuav@gmail.com> wrote:
This is a 3 minute example, not a fleshed out application design. The intuition I was going for was that various places might be located at e.g. lat/lon coordinates. But some are in the same building, hence equal address. Using `==` as a way of comparing being in the same place could be useful. Yes, I can also think of other ways of designing this (e.g. `p1.sameAddress(p2)`). My goal here was showing plausibility, not proposing a specific software design for a given need. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

Christopher Barker wrote:
The OP states that in their case True and 1 should _not_ hash the same, whether that gels with Python was not my point, I was saying that this use case, to me, is asking for some kind of typed hash table not a change to the typing of Booleans themselves.
(And since booleans are subclasses of Ints, they might still class in a typed has table, depending on the implementation)
Hence my suggestion that the OP's solution of a bespoke key mapping to tuples is not so bad (i.e. not making it part of the stdlib), but that it may be nicer to deal with if it is abstracted away into the getitem, setitem ... dunder methods.
Apologies, but that is not what the user is saying, they are saying arguably the opposite. I am not saying I agree with the OP on this, but I don't think the behaviour of Bools in mathematical operations is their point.
I think this is a similar idea to what I was suggesting but limited to only handle Booleans.

On Mon, Dec 21, 2020 at 9:25 AM Christopher Barker <pythonchb@gmail.com> wrote:
For what it's worth, functools.lru_cache has a "typed" option that effectively adds the type()s of the arguments to the cache key. It was added by https://bugs.python.org/issue13227 . The only application of it in that patch was to re's compiled pattern cache, which needs to be typed to avoid spurious warnings or errors with -b or -bb, I suppose.

On Mon, Dec 21, 2020 at 3:37 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I know why, but I'm not so sure -- no one was using a built in True or False as an integer, because they didn't exist. I suppose folks were using the results of, e.g. `a == b` as an integer, but how often? Where else is an explicit True or False returned by Python itself? But anyway, it would certainly be even more disruptive now. - CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Tue, Dec 22, 2020 at 11:52 AM Christopher Barker <pythonchb@gmail.com> wrote:
No, but AIUI people were creating their own globals to do that job. And in Python 2, True and False weren't keywords, just built-ins, so you could keep on writing "True = 1" and everything would be fine.
I suppose folks were using the results of, e.g. `a == b` as an integer, but how often? Where else is an explicit True or False returned by Python itself?
Actually, I do that sort of thing periodically. Or rather, I use it as an index, which comes to the same thing. Every language I've used since leaving BASIC behind has allowed me to use a comparison as if it were a 1 or a 0. (Many of them because 1 and 0 *are* the values for true and false.) Well, every language except one, and even that one has some oddities that make the values mostly equivalent. ChrisA

On Tue, Dec 22, 2020 at 1:14 AM Chris Angelico <rosuav@gmail.com> wrote:
Indeed. The discussion around this was quite specifically that many people defined: True = 1 False = 0 At the top of their code, and used that. This was the main reason they were built-ins rather than keywords, so as not to break that large body of existing code. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

22.12.20 03:18, David Mertz пише:
I think that it was rather to allow writing the code try: True, False except NameError: True = 1 False = 0 which would work with new and old Python versions. People that defined they own constants before adding bool would likely used "true" or "TRUE".

22.12.20 02:52, Christopher Barker пише:
Every time you use a builtin function which has semantically boolean parameter. They are usually converted to C using PyArg_Parse("i"). For example:
There are tons of such parameters in the stdlib (it can be changed) and zillions of them in third-party extensions which will be broken with non-int booleans.

On 22/12/2020 00:52, Christopher Barker wrote:
[f"{n+1} {g}{(n>0)*'s'}" for n, g in enumerate(gifts)][::-1] ['4 calling birds', '3 french hens', '2 turtle doves', '1 partridge in a
The `bool` function, of course, `in`, `is` and `isinstance`, `str.isdigit`, ..., |`threading.||Lock.acquire|`. Quite a few if you search for "return PyBool_". Except for counting (with `sum`), I don't think you would use any of those arithmetically. But there's this kind of thing: pear tree'] Go on, you know you want to. Jeff

On Sun, Dec 20, 2020 at 11:59:36PM -0800, Paul Bryan wrote:
An alternative, which may be better or worse but is probably worth considering, is to create your own wrapper proxy for True and False, then use that. It's probably a bit more effort up front (you need a proxy class that wraps True and False, a pair of constants TRUE, FALSE, and probably a dict subclass which replaces every True/False key with TRUE/FALSE) but once you have that you can go back to just using the object as key without bothering with the redundant type and tuple. -- Steve

On Tue, Dec 22, 2020 at 08:28:30AM +1100, Steven D'Aprano wrote:
It has come to my attention that my post may have been unclear. I don't mean to suggest that the *consumers* of the library be responsible for changing all uses of True/False bools into the special proxies. Rather, I meant that the *library itself* should internally replace True/False with its own custom objects that hash and compare differently. Here's an untested sketch of a solution: ``` class Boolean(object): # Proxy the genuine True and False bools. def __init__(self, obj): self._obj = obj def __bool__(self): return self._obj def __eq__(self, other): return self is other def __hash__(self): return 17 + self._obj TRUE = Boolean(True) FALSE = Boolean(False) class MyDict(dict): def __getitem__(self, key): if key is True: key = TRUE elif key is False: key = FALSE return super().__getitem__(key) ``` and similar for `__setitem__` etc. The caller of the library doesn't need to know that their data containing True/False bools are silently replaced by TRUE/FALSE inside the mapping. They just use True and False in their code as normal and it should all just work. Subclassing dict is sometimes tricky to get right, you might find it easier to subclass collections.UserDict. -- Steve

On Mon, Dec 21, 2020 at 8:26 AM Paul Bryan <pbryan@anode.ca> wrote:
Wouldn't that still work if bool's __int__ returns 1?
Yes, there are ways you could accomplish the `sum(trues)` pattern other than making bool a subclass of int. But as Chris points out, the value of `1 == True == 1.0` is pretty fundamental to many other patterns also. And likewise, things being in sets by equality rather than identity is likewise fundamental. The change you suggest to make `True != 1` "breaks the world".
-- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

21.12.20 10:11, Paul Bryan пише:
Wouldn't that still work if bool's __int__ returns 1?
Implementing `__index__` would solve 90% of all problems with non-int booleans. But not the example provided by David (it requires `__add__`). And there may be more specific cases when you need to make arithmetic or bit operations with booleans or sort them. In result you would need to implement all method inherited from int in new bool. And what is the difference?

On Mon, Dec 21, 2020 at 7:15 PM Paul Bryan <pbryan@anode.ca> wrote:
Hmm, I see ints, floats and decimal.Decimal all produce the same hashes for integer values, so they also suffer the same fate in sets and dicts.
Yes, because they compare equal. (The hash is secondary to that.) Since 1 == 1.0, they must be coalesced in a set. ChrisA

Surely what you're looking for is some kind of typed hash table? Maybe you should be suggesting adding a TypedMapping to collections or something like that? But it seems to be your solution is fine, but maybe could abstract away the types in the keys in the class dunders? ``` class TypedMapping(dict): def __getitem__(self, item): return super().__getitem__((item, type(item))) ... ``` And so on

Surely what you're looking for is some kind of typed hash table?
Maybe, maybe not. My impression is that the Typed hash table is a kluge to get around this one issue. As others have pointed out, 1 and 1.0 are considered equal, and thus hash the same — a typed hash table would not consider them the same. And while maybe useful, it would violate the spirit of duck typing. If you really want that kind of static typing, maybe Python is not the language for the job. (And since booleans are subclasses of Ints, they might still class in a typed has table, depending on the implementation) What I think the OP wants, and I happen to agree, is for Booleans to not only not BE integers in the subclassing sense, but also to not behave as numbers in the. Duck Typing sense. That is: True == 1 # false True * 4. # Exception Etc. However, that ship has sailed. I think it would have been minimally disruptive when True and False were first introduced, but it’s way too late now. There is far too much code out there that expects the bools to behave like integers. To the OP: perhaps you could create your own non-integer Bool type, and then put a wrapper around set and/or dict, that substitutes it on insertion. Whether that is more or less kludgy than your solution depends on your use case. -CHB Maybe you should be suggesting adding a TypedMapping to collections or
-- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Mon, Dec 21, 2020 at 5:27 PM Christopher Barker <pythonchb@gmail.com> wrote:
I've occasionally wanted an "identity set". Except, after I want it, I pretty quickly always realize I don't actually want it. Exactly what strings and integers will actually be interned, etc? There's a lot of implementation dependent stuff there that isn't a clear semantics (I mean, it's knowable, but too complex). In any case, I know how to write that in a few lines if I feel like having it. Basically just use pairs `(id(x), x)` in a set. That's more specific than `(type(x), x)`, although both have their own gotchas. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

On Mon, Dec 21, 2020 at 5:56 PM Paul Bryan <pbryan@anode.ca> wrote:
I'm interested in knowing when (id(x), x) would be preferable over (type(x), x).
Something along these lines:
-- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

On Tue, Dec 22, 2020 at 5:20 AM David Mertz <mertz@gnosis.cx> wrote:
If equality is based on self.x and self.y, the easiest way to do the hash is the same way: def __hash__(self): return hash((self.x, self.y)) But the real question is: Why do points compare equal based on their locations, if you need them to be independently stored in a set? Logically, if they are equal, the set either contains that one thing or it doesn't. ChrisA

On Mon, Dec 21, 2020 at 6:32 PM Chris Angelico <rosuav@gmail.com> wrote:
This is a 3 minute example, not a fleshed out application design. The intuition I was going for was that various places might be located at e.g. lat/lon coordinates. But some are in the same building, hence equal address. Using `==` as a way of comparing being in the same place could be useful. Yes, I can also think of other ways of designing this (e.g. `p1.sameAddress(p2)`). My goal here was showing plausibility, not proposing a specific software design for a given need. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

Christopher Barker wrote:
The OP states that in their case True and 1 should _not_ hash the same, whether that gels with Python was not my point, I was saying that this use case, to me, is asking for some kind of typed hash table not a change to the typing of Booleans themselves.
(And since booleans are subclasses of Ints, they might still class in a typed has table, depending on the implementation)
Hence my suggestion that the OP's solution of a bespoke key mapping to tuples is not so bad (i.e. not making it part of the stdlib), but that it may be nicer to deal with if it is abstracted away into the getitem, setitem ... dunder methods.
Apologies, but that is not what the user is saying, they are saying arguably the opposite. I am not saying I agree with the OP on this, but I don't think the behaviour of Bools in mathematical operations is their point.
I think this is a similar idea to what I was suggesting but limited to only handle Booleans.

On Mon, Dec 21, 2020 at 9:25 AM Christopher Barker <pythonchb@gmail.com> wrote:
For what it's worth, functools.lru_cache has a "typed" option that effectively adds the type()s of the arguments to the cache key. It was added by https://bugs.python.org/issue13227 . The only application of it in that patch was to re's compiled pattern cache, which needs to be typed to avoid spurious warnings or errors with -b or -bb, I suppose.

On Mon, Dec 21, 2020 at 3:37 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I know why, but I'm not so sure -- no one was using a built in True or False as an integer, because they didn't exist. I suppose folks were using the results of, e.g. `a == b` as an integer, but how often? Where else is an explicit True or False returned by Python itself? But anyway, it would certainly be even more disruptive now. - CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Tue, Dec 22, 2020 at 11:52 AM Christopher Barker <pythonchb@gmail.com> wrote:
No, but AIUI people were creating their own globals to do that job. And in Python 2, True and False weren't keywords, just built-ins, so you could keep on writing "True = 1" and everything would be fine.
I suppose folks were using the results of, e.g. `a == b` as an integer, but how often? Where else is an explicit True or False returned by Python itself?
Actually, I do that sort of thing periodically. Or rather, I use it as an index, which comes to the same thing. Every language I've used since leaving BASIC behind has allowed me to use a comparison as if it were a 1 or a 0. (Many of them because 1 and 0 *are* the values for true and false.) Well, every language except one, and even that one has some oddities that make the values mostly equivalent. ChrisA

On Tue, Dec 22, 2020 at 1:14 AM Chris Angelico <rosuav@gmail.com> wrote:
Indeed. The discussion around this was quite specifically that many people defined: True = 1 False = 0 At the top of their code, and used that. This was the main reason they were built-ins rather than keywords, so as not to break that large body of existing code. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

22.12.20 03:18, David Mertz пише:
I think that it was rather to allow writing the code try: True, False except NameError: True = 1 False = 0 which would work with new and old Python versions. People that defined they own constants before adding bool would likely used "true" or "TRUE".

22.12.20 02:52, Christopher Barker пише:
Every time you use a builtin function which has semantically boolean parameter. They are usually converted to C using PyArg_Parse("i"). For example:
There are tons of such parameters in the stdlib (it can be changed) and zillions of them in third-party extensions which will be broken with non-int booleans.

On 22/12/2020 00:52, Christopher Barker wrote:
[f"{n+1} {g}{(n>0)*'s'}" for n, g in enumerate(gifts)][::-1] ['4 calling birds', '3 french hens', '2 turtle doves', '1 partridge in a
The `bool` function, of course, `in`, `is` and `isinstance`, `str.isdigit`, ..., |`threading.||Lock.acquire|`. Quite a few if you search for "return PyBool_". Except for counting (with `sum`), I don't think you would use any of those arithmetically. But there's this kind of thing: pear tree'] Go on, you know you want to. Jeff

On Sun, Dec 20, 2020 at 11:59:36PM -0800, Paul Bryan wrote:
An alternative, which may be better or worse but is probably worth considering, is to create your own wrapper proxy for True and False, then use that. It's probably a bit more effort up front (you need a proxy class that wraps True and False, a pair of constants TRUE, FALSE, and probably a dict subclass which replaces every True/False key with TRUE/FALSE) but once you have that you can go back to just using the object as key without bothering with the redundant type and tuple. -- Steve

On Tue, Dec 22, 2020 at 08:28:30AM +1100, Steven D'Aprano wrote:
It has come to my attention that my post may have been unclear. I don't mean to suggest that the *consumers* of the library be responsible for changing all uses of True/False bools into the special proxies. Rather, I meant that the *library itself* should internally replace True/False with its own custom objects that hash and compare differently. Here's an untested sketch of a solution: ``` class Boolean(object): # Proxy the genuine True and False bools. def __init__(self, obj): self._obj = obj def __bool__(self): return self._obj def __eq__(self, other): return self is other def __hash__(self): return 17 + self._obj TRUE = Boolean(True) FALSE = Boolean(False) class MyDict(dict): def __getitem__(self, key): if key is True: key = TRUE elif key is False: key = FALSE return super().__getitem__(key) ``` and similar for `__setitem__` etc. The caller of the library doesn't need to know that their data containing True/False bools are silently replaced by TRUE/FALSE inside the mapping. They just use True and False in their code as normal and it should all just work. Subclassing dict is sometimes tricky to get right, you might find it easier to subclass collections.UserDict. -- Steve
participants (11)
-
Ben Rudiak-Gould
-
Chris Angelico
-
Christopher Barker
-
David Mertz
-
Eric V. Smith
-
Greg Ewing
-
Jeff Allen
-
Mathew Elman
-
Paul Bryan
-
Serhiy Storchaka
-
Steven D'Aprano