Additional meanings for "in" operator and ability to customize implementation

Note that I'm new to this system, so I'm not sure if this will format correctly or whether I'll be able to edit it afterward to format it properly if not. Fingers crossed. Examples: import re from collections import Sequence # Equivalent of re.compile(r'b.d').search(<str>) re.compile(r'b.d') in 'abcdef' # -> <_sre.SRE_Match object; span=(1, 4), match='bcd'> re.compile(r'b.d') in 'xyz' # -> None # Equivalent of isinstance([1, 2], Sequence) [1, 2] in Sequence # -> True class BrightColorsMeta(type): def __rin__(self, other): other.startswith('bright ') class BrightColors(metaclass=BrightColorsMeta): pass 'red' in BrightColors # -> False 'bright blue' in BrightColors # -> True

That's neat! I did not know about __contains__. In that case, I'm really just suggesting some additional implementations of __contains__ in builtin and standard lib classes. PS: Sorry for the missing "return" in the __rin__ example.

I see that __contains__ is not a new thing, so I'm not sure why I didn't notice it. Thanks very much for pointing it out. :)

I would not want to overload plain strings' .__contains__() method to mean "has this substring OR matches this compiled regex." Besides being on a likely performance path, it's too special. And what about glob patterns, for example? Those too? But you can wrap your strings in RegexSearchableString or something, and customize the behavior of that class however you like. On Sun, Oct 13, 2019, 2:20 PM Steve Jorgensen <stevej@stevej.name> wrote:

You make an excellent point. I withdraw that from my list of proposed cases. …and that leaves only the suggestion that `type.__contains__(self, other)` could be a synonym for `isinstance(other, self)`.

On Sun, Oct 13, 2019 at 07:15:09PM -0000, Steve Jorgensen wrote:
…and that leaves only the suggestion that `type.__contains__(self, other)` could be a synonym for `isinstance(other, self)`.
We don't normally think of an instance as being an element contained by its class. We wouldn't say that Nemo is *in* the type Fish, we say that Nemo *is a* Fish, and we don't expect that iterating over Fish will eventually yield Nemo. In Python, we spell containment testing as `in` and `is-a` relationship testing as `isinstance`. But types aren't containers and we surely don't want isinstance(float, collections.abc.Container) to return True. Even when the class can be identified as equivalent to its set of values, such as for numeric types, there are all sorts of complexities that will only lead to confusion: from numbers import Real 1.0 in Real # okay, unproblematic 2+3j in complex # also okay 1 in complex # mathematically true, but isinstance-wise false float('INF') in Real # isinstance-wise true, but mathematically false. -- Steven

On Oct 13, 2019, at 18:39, Steven D'Aprano <steve@pearwood.info> wrote:
I agree with your conclusion, but I think this is the wrong way to argue it. `1 in complex` is _not_ mathematically true. Simplifying a bit, the elements of the algebra of complex numbers are ordered pairs of real numbers, and `1` is not a pair. When you’re working in, say, complex analysis, you just assume the usual morphism that maps every real to a unique complex (and some complexes to a unique real), and you can loosely multiply a real by a complex and so on. But when you’re working on the actual algebras or the sets/types beneath them, you can’t do that. `R` is not a subset of `C`, and `1 in C` is just not true. In other words, the distinction in Python—that 1 and 1+0j are in some sense “the same number” but in another sense not even the same type of number—is exactly the same as in math; you don’t need anything extra to justify it. As for infinity: the problem there is that the algebra of IEEE floats just isn’t the same thing as the real algebra (and in fact it’s closer to rationals than to reals). By pretending otherwise, Python has already bitten that bullet. And if the type you want to call `Real` does include IEEE floats, then mathematically it does include `inf`. Finally, it’s not just numeric types, but all types that are identifiable as the set of their values (plus possibly some additional structure). The fact that there are a potentially infinite number of `Dog` instances doesn’t mean they don’t form a set, or that `fido` isn’t a member of that set; `N` (and, in fact, Python `int`) is just as infinite, and `R` is just as non-denumerable. All that being said, it isn’t _useful_ to treat Python types as pure type-theory constructs. They make for a good analogy, and a good grounding to argue for changes to Python’s type system, but they’re not the same thing. What `isinstance` means is not inclusion in a set, or at least not one that can be expressed even in an approximate way in Python. Python allows for subclasses that aren’t subtypes, and instance checking that approximates structural subtypes, and even explicitly lying to the type system for good practical reasons, and all of that is what `isinstance` means. And that’s not confusing in practice, but using `in` to mean the same thing would be. Also, from a practical point of view, why do we need another way to spell the isa relationship when we already have a perfectly good—and more flexible (e.g., surely `spam in (Eggs, Cheese)` wouldn’t mean the same thing as `isinstance(spam, (Eggs, Cheese))`)—way to do it. TOOWTDI isn’t an iron-clad rule, but this is about as close to a paradigm case where it should apply as you’re ever going to see. Finally, in languages that care a lot more about this kind of thing (like Haskell, or Scala), either types aren’t first-class objects at all, or they’re first-class objects into a two-kinded system; they’re definitely not sets at the language level. So why should Python try to go farther than any of them?

On Mon, Oct 14, 2019 at 3:46 PM Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
Mathematically, what's the difference between '1' and '1+0j' (or '1+0i')? Everything I've seen has said that a complex number with its imaginary part zero is equivalent to a real number, which would mean that '1' is as valid a way of representing that number as any other. It's the same as considering 42 to be rational, because it can be written as 42/1. You're absolutely right that Python's floats aren't actually representing reals (aside from inf/nan, they're all rationals), but I don't see a problem with calling 1 a member of the Complex numbers, as Python does:
isinstance(1, numbers.Complex) True
Explain the difference? ChrisA

On Oct 13, 2019, at 22:54, Chris Angelico <rosuav@gmail.com> wrote:
Mathematically, what's the difference between '1' and '1+0j' (or '1+0i')?
The details depend on what foundations you use, but let’s go with the most common construction. The natural number 1 is defined as 0 U {0}. Because 0 is defined as {}, that means it’s {{}}. The integer 1 is the equivalence class of all ordered pairs (where the ordered pair `(x,y)` is defined as the set `{x, {x,y}}`) of natural numbers such that the first one is the successor of the second. The rational number 1/1 is the equivalence class of ordered pairs of integers where the first one is equal to the second. The real number 1.0 is (loosely) the equivalence class of (infinite) sets of rational numbers less than 1/1. The complex number 1.0+0.0j is the ordered pair of the real numbers 1.0 and 0.0. (There’s no need for an equivalence class here, but if you want to add that, the class has one element, that ordered pair.) None of these sets is the same as any of the others. You can expand the sets all the way out, but you really don’t have to; it’s obvious that all complex numbers are finite (an ordered pair can’t have more than two elements), and all real numbers are infinite. There are lots of other ways to construct numbers (technically an infinite number of ways, but I mean ways that are practically interesting), but generally, no real number is a complex number. The only foundation I know of that sort of does what you want is based on category theory, and then the real numbers and complex numbers aren’t sets, so the question technically doesn’t even apply.
Everything I've seen has said that a complex number with its imaginary part zero is equivalent to a real number,
But “equivalent to” doesn’t mean “same as”. It’s sort of like the difference between `==` and `is` in Python: `1.0 == 1.0+0.0j` is true, but that doesn’t mean `1.0 is 1.0+0.0j`, and therefore, the fact that `isinstance(1.0=0.0j, complex)` doesn’t mean that `isinstance(1.0, complex)`. But don’t take that analogy too seriously. The right thing to say when you need to be strict is that there’s a morphism that maps every real number to a unique complex number, and a morphism that maps every complex number whose real part is 0 to a unique real number, and that all the laws of real and complex algebra work as you’d expect with respect to these morphisms (and you can even define things like real-complex multiplication and then prove that you always get the same result as if you mapped the real to its complex equivalent and did complex-complex multiplication). In most areas of math, and in most applications, you really don’t care which definition you’re using, and you loosely just assume it’s any of the ones that works, and that you’re using any of the morphisms that works, and so on. But in those areas of math, you don’t deal with things like the set of reals, much less the internal structure of a real; the fact that you don’t ask (e.g.) whether some arbitrary object is an element of C is why you can be loose about it.
Well, any string is a valid way of representing any number under some system. And when you’re doing complex analysis, it’s perfectly reasonable to say that the string `1` represents the number `1+0i`. But then the string `1` isn’t representing the real number `1`, so the mapping between reals and complex numbers isn’t even relevant in the first place. Again, think of Python, but don’t take the analogy too far. You can use `1` almost anywhere you can use a float, and it will almost always mean the same thing as `1.0` when you do. But that doesn’t mean when you use it in `isinstance(1, float)` it’s the same as `1.0`. So, is `'1'` a valid way of representing the float `1.0`? Yes if you’re doing arithmetic, no if you’re doing type-switching.

1 is perfectly valid natural number, rational number (equivalent to 1/1), real number (equivalent to 1.0) and complex number (equivalent to 1.0+0.0i). Everything you can do with 1 as a natural number, you can do with its equivalents in the other (super) sets. I guess (I hope) what Andrew is saying is that natural numbers are not rationals, nor are reals, etc. Which means, if "1" is a natural number, you cannot divide it (pretty much by anything except itself) to remain natural, or add 'i'. The same goes for any other numbers "superset". But I believe it was not a point. 1 can be natural, rational, real, or complex depending on the context and claiming that it is not (because we did not add the mandatory rational pair, decimal points or the imaginary part) is absurd. Richard

On Mon, Oct 14, 2019 at 12:54:13AM -0700, Andrew Barnert via Python-ideas wrote:
More importantly, the details depend on where you sit on the Practicality vs Purity axis. I think it is important to state that most working mathematicians would have fallen asleep as soon as you mention "foundations".^1 I think the best answer (note plural) to the question of whether the Integers are a subset of the Reals can be paraphrased from this thread: https://math.stackexchange.com/questions/14828/set-theoretic-definition-of-n... Yes. But no. When we say that the Integers form a subset of the Reals, what we really mean is that there exists a subset of the reals that is isomorphic to ("exactly the same in every way that matters") to the integers. Distinguishing between that "image under the canonical embedding" and a true subset becomes tiresome very quickly, so anyone who isn't mad about foundational issues quickly drops the distinction. (I've paraphrased a number of different people in that discussion.) Mathematicians don't typically talk about "duck-typing", but if they did, they'd be saying that the Integers quack like a subset of the Reals no matter what those weird guys who care about foundational issues say :-) ^1 My cousin is a mathematics professor, ex of Harvard and Yale, currently at Rutgers. I once asked her opinion on Gödel's incompleteness theorems, and her response was more or less "Who?". At the time she was working in a field where the foundational issues raised by Gödel simply had no impact. Mathematics is a HUGE area of knowledge, and it is possible to have a long and successful career without once being the least bit concerned about the difference between Integers and Reals, beyond the obvious stuff they teach in secondary schools. -- Steven

On Oct 14, 2019, at 06:07, Steven D'Aprano <steve@pearwood.info> wrote:
Well, sure. And in fact that axis is a different axis when working in different fields of mathematics. Type theory is a lot closer to foundations stuff than most areas of math are, and so is large-cardinal set theory, but they’re close in different ways from each other, and require different bits from it.
Sure, but if you asked them “is it mathematically correct that integers are a subset of the reals, or that they aren’t?” their answer is usually going to be “Who cares?” Normally that isn’t a useful question, so it doesn’t need an answer. If you really need to know the rigorous answer (maybe you need it for your work, but much more likely it’s because, say, your nephew, who’s taking undergrad math courses, is asking…), you’d defer to people who care about that stuff, and give an answer based on the foundations. But in most fields you can go your entire career without ever needing to think about it. (At least once you pass and forget the relevant class late in your undergrad studies.) And of course that’s even more true for people doing mathematical physics or econ or programming theory or something. They probably didn’t even take that undergrad class to forget it later, and might not even know who to ask if the question came up. But because the question isn’t going to come up, it doesn’t matter.

On 10/14/2019 12:54 AM, Andrew Barnert via Python-ideas wrote:
On Oct 13, 2019, at 22:54, Chris Angelico <rosuav@gmail.com> wrote:
Mathematically, what's the difference between '1' and '1+0j' (or '1+0i')?
Again, think of Python, but don’t take the analogy too far. You can use `1` almost anywhere you can use a float, and it will almost always mean the same thing as `1.0` when you do. But that doesn’t mean when you use it in `isinstance(1, float)` it’s the same as `1.0`. So, is `'1'` a valid way of representing the float `1.0`? Yes if you’re doing arithmetic, no if you’re doing type-switching.
Interesting you should say that as I just had an instance of needing to use 0.0 instead of 0 (aka False) to correctly communicate with an ORM back-end that I wanted an integer column with a zero value, not a null. -- ~Ethan~

Sorry, missed this part: On Oct 13, 2019, at 22:54, Chris Angelico <rosuav@gmail.com> wrote:
`Complex` is an abstract type that defines an interface, which is loosely: supporting all the complex-arithmetic operators, properties like `real`, and methods like `conjugate`. Because `int` supports that interface, `int` is a subtype of `Complex`. The numeric-tower ABCs don’t have the same purpose as the concrete number types, or we wouldn’t need them in the first place. So it shouldn’t be surprising that they don’t have the same subtype relations. If you’re asking which set of relations is “mathematically correct”, then it depends on the context. Obviously the set of all Python `int` objects is a subset of the set of all possible things that meet the `Complex` interface, but that doesn’t mean they’re complex numbers, much less that they’re instances of `complex`. If you’re wondering whether integers are something you could define the laws of complex algebra over, then no, it isn’t. For example, one of the laws is that every number besides 0 has a multiplicative inverse, which obviously isn’t true for the set of integers. Or for the set of Python `int` values. But that’s not what the ABC is testing for, so that’s fine.

On Mon, Oct 14, 2019 at 7:04 PM Andrew Barnert <abarnert@yahoo.com> wrote:
If you’re wondering whether integers are something you could define the laws of complex algebra over, then no, it isn’t. For example, one of the laws is that every number besides 0 has a multiplicative inverse, which obviously isn’t true for the set of integers. Or for the set of Python `int` values. But that’s not what the ABC is testing for, so that’s fine.
Hmm, but every nonzero integer DOES have a multiplicative inverse - that value isn't another integer, but there is one. If the floating point value 2.0 has a multiplicative inverse 0.5, doesn't the integer value 2 also have that same multiplicative inverse? Your definitions are rather odd in places here. I'm not saying they're wrong, but I can't disprove the things you're saying are impossible, so it's hard for my brain to grok it. ChrisA

On Mon, Oct 14, 2019 at 07:53:07PM +1100, Chris Angelico wrote:
It depends on whether you are working in the Integers only, or something else. In the Integers, no, there is no multiplicative inverse for 2, because there is no *integer* (whole number) N such that 1/N = 2. Analogy: you can't divide one apple between two people so that *both* people get a whole number of apples. But in the Rationals, we allow additional values that cannot be expressed as whole numbers, namely the fractions. In the Rationals, there is a multiplicative inverse for all numbers except 0. Likewise for the Reals. Similarly there is no Real number X such that X*X = -1, but if you extend the values to Complex, there are two such values. -- Steven

On Oct 14, 2019, at 01:53, Chris Angelico <rosuav@gmail.com> wrote:
Sure, you can define a function from integers to rationals, but that isn’t a function from integers to integers, so that doesn’t make integers a field. To define a group/ring/field/algebra/etc. you need operations from that object to itself. Those operations are part of the definition of the object. And the rules that distinguish what object counts as a field, or as a division ring, etc. are rules on the operations used to define it. This is hard to explain without starting with group theory and set theory primers and working up through multiple levels from there, but let me given another analogy to programming: type T is only a field if it has (among other things) an inverse(x:T)->T method, and that method doesn’t raise for any values of T except T.zero(). Just having a method named inverse is not sufficient. People often think “well, natural numbers aren’t closed over subtraction, so we’ll just always use integers, and integers aren’t closed over division so we’ll just always use rationals, …” assuming that if you keep following that you get to the one true “numbers” somewhere around complex or quarternion. If this were true, we wouldn’t bother with different kinds of numbers. You’d learn about them in a set theory or math logic class and then never use them again. But in fact at each step you lose features as well as gain them. For the most obvious example, while complex numbers give you closed exponentiation, they take away ordering. So, they’re all useful for different purposes.
No, if you define a multiplicative inverse operation on the integers, it’s only defined for 1->1; its undefined for every other value. Compare with the integers modulo 5. Every integer modulo 5 but 0 has an inverse: 1*1, 2*3, 3*2, and 4*4 are all 1, so 1 and 4 are their own inverses, 2 and 3 are each others’. So this actually is a field, unlike the integers. But that doesn’t mean that the things we call “2” and “3” in some other field (like the rationals) are each others’ inverses. Even though we have the same kind of morphism as from Z to Q for Z to Z/5Z, and can say that the int 2, the rational 2/1 and the int-mod-5 2mod5 are all in a useful sense “the same number”, the inverse of 2/1 is 1/2, the inverse of 2mod5 is 3mod5, and the inverse of 2 does not exist. However, you can say something like this: if you have a morphism M that maps each integer to a unique rational and maps 0 and 1 to 0/1 and 1/1 and follows various other rules and has a partial reverse and so on, revM(M(n) / M(m)) gives you the same value as integer division whenever integer division is defined. So now you can define an integer-to-rational division function trivially as M(n)/M(m). And you can say useful things about that. For example, with rational division, 1/q is always the same as inverse(q) except at 0, and p/q is always the same as p * inverse(q), and so on—and all of these rules apply to your integer-to-rational division function as well. And that’s why, e.g., Python’s division operator makes sense: that’s almost exactly what it’s doing.
Well, they are all oversimplified, and the explanations are skimmed over very loosely on top of that. If you really want to understand it without working through a complete course in set theory and foundations… you’ll need explanations out there which are better than anything I could write (much less write on the fly in an ASCII email for an audience of Python developers…), but I’m sure they exist, because it’s really not that huge or that abstract. On stuff like this, Wikipedia is usually either a great place or start, or such an obvious mess that you can tell at first glance, so maybe try searching there first. The way to do this in category theory is even harder to explain, but let me try anyway, because it may be illustrative anyway. You start by assuming we can define the algebra C without defining a set C. Sure, it must have a big infinite pile of elements, but we don’t know or care if they’re a set. This requires changing the definition of algebraic structures to not use sets, but categorists do that all over the place. Now, the subalgebra of C consisting of all of the members whose imaginary part is 0 is not a working algebra, but can we define another algebra R that uses the same elements and works? Every proof that you can’t depends on the elements of C forming a set. But we didn’t bother requiring that, so let’s just say it isn’t. And voila, I’ve got a real algebra, using a sub-thingy of the exact same elements that are in my complex algebra. I can’t show it to you, but you can’t prove that it doesn’t exist, and I can prove that if it does exist it meets all the criteria you might demand of it (except the ones that rely on R or C being sets). And this works all the way down to N—then you find that each number isn’t a set either. Then what is it? It’s an object in a category, and categories and morphisms are all there is; there is no deeper question. And you can go off in other directions basically the same way other mathematicians do, and create all the other algebras anyone could ever want, and they all work as expected unless none of them exist, and that’ll show those fools at the academy who’s crazy. But anyway, even if you aren’t a categorist and don’t want to eliminate set theory foundations, you have to accept that they proved that `issubset(R, C)` is only false because of something about sets, not something about numbers or algebra.

On 14/10/2019 18:25, Andrew Barnert via Python-ideas wrote:
Speaking as someone with a dusty maths degree, can I just say that you are massively missing the point here? There exists an injection from integers to complex numbers, that's all that anyone has actually claimed here. You seem to be basing your entire contradiction on the informality of the notation they used. That's really not useful, nor is it meaningfully contributing to the original discussion. Can we get back to the original point, whatever that was? -- Rhodri James *-* Kynesim Ltd

Andrew Barnert via Python-ideas writes:
This is the central point. The rules that you can use to calculate (or prove things) depend on which definition of "number" you use (this is precisely where category theory places its emphasis). I would choose a different example, myself: when you move from the integers to the reals, you lose the notion of "next". This lack makes explaining some properties of dynamic models based on differential equations a real PITA if you are teaching students who haven't already internalized the mathematics of continuous time dynamics. Can't blame them, really, it confused Zeno, too. :-)

On Mon, 14 Oct 2019 at 05:47, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
`1 in complex` is _not_ mathematically true. Simplifying a bit, the elements of the algebra of complex numbers are ordered pairs of real numbers, and `1` is not a pair. When you’re working in, say, complex analysis, you just assume the usual morphism that maps every real to a unique complex (and some complexes to a unique real), and you can loosely multiply a real by a complex and so on.
This all just depends on your definitions. You can see some discussion of the approach taken in metamath here: http://us.metamath.org/mpegif/mmset.html#trivia """ One of the reasons that the proof of 2 + 2 = 4 is so long is that 2 and 4 are complex numbers—i.e. we are really proving (2+0i) + (2+0i) = (4+0i)—and these have a complicated construction (see the Axioms for Complex Numbers) but provide the most flexibility for the arithmetic in our set.mm database """ I guess the authors decided it was too painful to have 1 not be a complex number so they decided instead to define the integers as a subset of the complex numbers. Nothing wrong with that, just a different definition. The integers as a subset of the complex numbers still satisfy all the same properties. Oscar

On Sun, Oct 13, 2019, at 14:51, David Mertz wrote:
what about an "__rcontains__" method? For glob patterns, those don't currently have an externally exposed 'compiled version' at all, and I assume if they ever gained one it would be a compiled regex (globs are translated to regex internally, and there is a private method called _compile_pattern which returns the compiled regex's .match method)

On Oct 13, 2019, at 02:38, Steve Jorgensen <stevej@stevej.name> wrote:
That method already exists, it’s just called `__contains__`. Many types already customize this; you even get it for free from mixins like `collections.abc.Sequence`. Also, I’m not sure why you want `BrightColors` to be a class here. It doesn’t have any instances, or any other class behavior. It’s sort of like an open-ended `Enum`, but without any of the actual enum functionality. But anyway, you could write this today if you want it. I think what you want for the other examples is to add an `__rcontains__` method, so instead of `x in y` meaning `y.__contains__(x)`, it means either `y.__contains__(x)` or `x.__rcontains__(y)` depending on some rule that you have to define. Should it be the same rules as `__radd__`? You probably want to work through all of the cases for those rules with some example types.

On Mon, Oct 14, 2019 at 12:18 AM Steve Jorgensen <stevej@stevej.name> wrote:
For this specific example, have you looked at the enum module? https://docs.python.org/3/library/enum.html You can create a class that represents the enumeration, have members for each valid term, and then see if something is a part of the enumeration. ChrisA

This isn't really the same thing as enum since it has nothing to do with numeric values. Per Andrew, however, `__contains__` actually does exactly what I was asking for.

That's neat! I did not know about __contains__. In that case, I'm really just suggesting some additional implementations of __contains__ in builtin and standard lib classes. PS: Sorry for the missing "return" in the __rin__ example.

I see that __contains__ is not a new thing, so I'm not sure why I didn't notice it. Thanks very much for pointing it out. :)

I would not want to overload plain strings' .__contains__() method to mean "has this substring OR matches this compiled regex." Besides being on a likely performance path, it's too special. And what about glob patterns, for example? Those too? But you can wrap your strings in RegexSearchableString or something, and customize the behavior of that class however you like. On Sun, Oct 13, 2019, 2:20 PM Steve Jorgensen <stevej@stevej.name> wrote:

You make an excellent point. I withdraw that from my list of proposed cases. …and that leaves only the suggestion that `type.__contains__(self, other)` could be a synonym for `isinstance(other, self)`.

On Sun, Oct 13, 2019 at 07:15:09PM -0000, Steve Jorgensen wrote:
…and that leaves only the suggestion that `type.__contains__(self, other)` could be a synonym for `isinstance(other, self)`.
We don't normally think of an instance as being an element contained by its class. We wouldn't say that Nemo is *in* the type Fish, we say that Nemo *is a* Fish, and we don't expect that iterating over Fish will eventually yield Nemo. In Python, we spell containment testing as `in` and `is-a` relationship testing as `isinstance`. But types aren't containers and we surely don't want isinstance(float, collections.abc.Container) to return True. Even when the class can be identified as equivalent to its set of values, such as for numeric types, there are all sorts of complexities that will only lead to confusion: from numbers import Real 1.0 in Real # okay, unproblematic 2+3j in complex # also okay 1 in complex # mathematically true, but isinstance-wise false float('INF') in Real # isinstance-wise true, but mathematically false. -- Steven

On Oct 13, 2019, at 18:39, Steven D'Aprano <steve@pearwood.info> wrote:
I agree with your conclusion, but I think this is the wrong way to argue it. `1 in complex` is _not_ mathematically true. Simplifying a bit, the elements of the algebra of complex numbers are ordered pairs of real numbers, and `1` is not a pair. When you’re working in, say, complex analysis, you just assume the usual morphism that maps every real to a unique complex (and some complexes to a unique real), and you can loosely multiply a real by a complex and so on. But when you’re working on the actual algebras or the sets/types beneath them, you can’t do that. `R` is not a subset of `C`, and `1 in C` is just not true. In other words, the distinction in Python—that 1 and 1+0j are in some sense “the same number” but in another sense not even the same type of number—is exactly the same as in math; you don’t need anything extra to justify it. As for infinity: the problem there is that the algebra of IEEE floats just isn’t the same thing as the real algebra (and in fact it’s closer to rationals than to reals). By pretending otherwise, Python has already bitten that bullet. And if the type you want to call `Real` does include IEEE floats, then mathematically it does include `inf`. Finally, it’s not just numeric types, but all types that are identifiable as the set of their values (plus possibly some additional structure). The fact that there are a potentially infinite number of `Dog` instances doesn’t mean they don’t form a set, or that `fido` isn’t a member of that set; `N` (and, in fact, Python `int`) is just as infinite, and `R` is just as non-denumerable. All that being said, it isn’t _useful_ to treat Python types as pure type-theory constructs. They make for a good analogy, and a good grounding to argue for changes to Python’s type system, but they’re not the same thing. What `isinstance` means is not inclusion in a set, or at least not one that can be expressed even in an approximate way in Python. Python allows for subclasses that aren’t subtypes, and instance checking that approximates structural subtypes, and even explicitly lying to the type system for good practical reasons, and all of that is what `isinstance` means. And that’s not confusing in practice, but using `in` to mean the same thing would be. Also, from a practical point of view, why do we need another way to spell the isa relationship when we already have a perfectly good—and more flexible (e.g., surely `spam in (Eggs, Cheese)` wouldn’t mean the same thing as `isinstance(spam, (Eggs, Cheese))`)—way to do it. TOOWTDI isn’t an iron-clad rule, but this is about as close to a paradigm case where it should apply as you’re ever going to see. Finally, in languages that care a lot more about this kind of thing (like Haskell, or Scala), either types aren’t first-class objects at all, or they’re first-class objects into a two-kinded system; they’re definitely not sets at the language level. So why should Python try to go farther than any of them?

On Mon, Oct 14, 2019 at 3:46 PM Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
Mathematically, what's the difference between '1' and '1+0j' (or '1+0i')? Everything I've seen has said that a complex number with its imaginary part zero is equivalent to a real number, which would mean that '1' is as valid a way of representing that number as any other. It's the same as considering 42 to be rational, because it can be written as 42/1. You're absolutely right that Python's floats aren't actually representing reals (aside from inf/nan, they're all rationals), but I don't see a problem with calling 1 a member of the Complex numbers, as Python does:
isinstance(1, numbers.Complex) True
Explain the difference? ChrisA

On Oct 13, 2019, at 22:54, Chris Angelico <rosuav@gmail.com> wrote:
Mathematically, what's the difference between '1' and '1+0j' (or '1+0i')?
The details depend on what foundations you use, but let’s go with the most common construction. The natural number 1 is defined as 0 U {0}. Because 0 is defined as {}, that means it’s {{}}. The integer 1 is the equivalence class of all ordered pairs (where the ordered pair `(x,y)` is defined as the set `{x, {x,y}}`) of natural numbers such that the first one is the successor of the second. The rational number 1/1 is the equivalence class of ordered pairs of integers where the first one is equal to the second. The real number 1.0 is (loosely) the equivalence class of (infinite) sets of rational numbers less than 1/1. The complex number 1.0+0.0j is the ordered pair of the real numbers 1.0 and 0.0. (There’s no need for an equivalence class here, but if you want to add that, the class has one element, that ordered pair.) None of these sets is the same as any of the others. You can expand the sets all the way out, but you really don’t have to; it’s obvious that all complex numbers are finite (an ordered pair can’t have more than two elements), and all real numbers are infinite. There are lots of other ways to construct numbers (technically an infinite number of ways, but I mean ways that are practically interesting), but generally, no real number is a complex number. The only foundation I know of that sort of does what you want is based on category theory, and then the real numbers and complex numbers aren’t sets, so the question technically doesn’t even apply.
Everything I've seen has said that a complex number with its imaginary part zero is equivalent to a real number,
But “equivalent to” doesn’t mean “same as”. It’s sort of like the difference between `==` and `is` in Python: `1.0 == 1.0+0.0j` is true, but that doesn’t mean `1.0 is 1.0+0.0j`, and therefore, the fact that `isinstance(1.0=0.0j, complex)` doesn’t mean that `isinstance(1.0, complex)`. But don’t take that analogy too seriously. The right thing to say when you need to be strict is that there’s a morphism that maps every real number to a unique complex number, and a morphism that maps every complex number whose real part is 0 to a unique real number, and that all the laws of real and complex algebra work as you’d expect with respect to these morphisms (and you can even define things like real-complex multiplication and then prove that you always get the same result as if you mapped the real to its complex equivalent and did complex-complex multiplication). In most areas of math, and in most applications, you really don’t care which definition you’re using, and you loosely just assume it’s any of the ones that works, and that you’re using any of the morphisms that works, and so on. But in those areas of math, you don’t deal with things like the set of reals, much less the internal structure of a real; the fact that you don’t ask (e.g.) whether some arbitrary object is an element of C is why you can be loose about it.
Well, any string is a valid way of representing any number under some system. And when you’re doing complex analysis, it’s perfectly reasonable to say that the string `1` represents the number `1+0i`. But then the string `1` isn’t representing the real number `1`, so the mapping between reals and complex numbers isn’t even relevant in the first place. Again, think of Python, but don’t take the analogy too far. You can use `1` almost anywhere you can use a float, and it will almost always mean the same thing as `1.0` when you do. But that doesn’t mean when you use it in `isinstance(1, float)` it’s the same as `1.0`. So, is `'1'` a valid way of representing the float `1.0`? Yes if you’re doing arithmetic, no if you’re doing type-switching.

1 is perfectly valid natural number, rational number (equivalent to 1/1), real number (equivalent to 1.0) and complex number (equivalent to 1.0+0.0i). Everything you can do with 1 as a natural number, you can do with its equivalents in the other (super) sets. I guess (I hope) what Andrew is saying is that natural numbers are not rationals, nor are reals, etc. Which means, if "1" is a natural number, you cannot divide it (pretty much by anything except itself) to remain natural, or add 'i'. The same goes for any other numbers "superset". But I believe it was not a point. 1 can be natural, rational, real, or complex depending on the context and claiming that it is not (because we did not add the mandatory rational pair, decimal points or the imaginary part) is absurd. Richard

On Mon, Oct 14, 2019 at 12:54:13AM -0700, Andrew Barnert via Python-ideas wrote:
More importantly, the details depend on where you sit on the Practicality vs Purity axis. I think it is important to state that most working mathematicians would have fallen asleep as soon as you mention "foundations".^1 I think the best answer (note plural) to the question of whether the Integers are a subset of the Reals can be paraphrased from this thread: https://math.stackexchange.com/questions/14828/set-theoretic-definition-of-n... Yes. But no. When we say that the Integers form a subset of the Reals, what we really mean is that there exists a subset of the reals that is isomorphic to ("exactly the same in every way that matters") to the integers. Distinguishing between that "image under the canonical embedding" and a true subset becomes tiresome very quickly, so anyone who isn't mad about foundational issues quickly drops the distinction. (I've paraphrased a number of different people in that discussion.) Mathematicians don't typically talk about "duck-typing", but if they did, they'd be saying that the Integers quack like a subset of the Reals no matter what those weird guys who care about foundational issues say :-) ^1 My cousin is a mathematics professor, ex of Harvard and Yale, currently at Rutgers. I once asked her opinion on Gödel's incompleteness theorems, and her response was more or less "Who?". At the time she was working in a field where the foundational issues raised by Gödel simply had no impact. Mathematics is a HUGE area of knowledge, and it is possible to have a long and successful career without once being the least bit concerned about the difference between Integers and Reals, beyond the obvious stuff they teach in secondary schools. -- Steven

On Oct 14, 2019, at 06:07, Steven D'Aprano <steve@pearwood.info> wrote:
Well, sure. And in fact that axis is a different axis when working in different fields of mathematics. Type theory is a lot closer to foundations stuff than most areas of math are, and so is large-cardinal set theory, but they’re close in different ways from each other, and require different bits from it.
Sure, but if you asked them “is it mathematically correct that integers are a subset of the reals, or that they aren’t?” their answer is usually going to be “Who cares?” Normally that isn’t a useful question, so it doesn’t need an answer. If you really need to know the rigorous answer (maybe you need it for your work, but much more likely it’s because, say, your nephew, who’s taking undergrad math courses, is asking…), you’d defer to people who care about that stuff, and give an answer based on the foundations. But in most fields you can go your entire career without ever needing to think about it. (At least once you pass and forget the relevant class late in your undergrad studies.) And of course that’s even more true for people doing mathematical physics or econ or programming theory or something. They probably didn’t even take that undergrad class to forget it later, and might not even know who to ask if the question came up. But because the question isn’t going to come up, it doesn’t matter.

On 10/14/2019 12:54 AM, Andrew Barnert via Python-ideas wrote:
On Oct 13, 2019, at 22:54, Chris Angelico <rosuav@gmail.com> wrote:
Mathematically, what's the difference between '1' and '1+0j' (or '1+0i')?
Again, think of Python, but don’t take the analogy too far. You can use `1` almost anywhere you can use a float, and it will almost always mean the same thing as `1.0` when you do. But that doesn’t mean when you use it in `isinstance(1, float)` it’s the same as `1.0`. So, is `'1'` a valid way of representing the float `1.0`? Yes if you’re doing arithmetic, no if you’re doing type-switching.
Interesting you should say that as I just had an instance of needing to use 0.0 instead of 0 (aka False) to correctly communicate with an ORM back-end that I wanted an integer column with a zero value, not a null. -- ~Ethan~

Sorry, missed this part: On Oct 13, 2019, at 22:54, Chris Angelico <rosuav@gmail.com> wrote:
`Complex` is an abstract type that defines an interface, which is loosely: supporting all the complex-arithmetic operators, properties like `real`, and methods like `conjugate`. Because `int` supports that interface, `int` is a subtype of `Complex`. The numeric-tower ABCs don’t have the same purpose as the concrete number types, or we wouldn’t need them in the first place. So it shouldn’t be surprising that they don’t have the same subtype relations. If you’re asking which set of relations is “mathematically correct”, then it depends on the context. Obviously the set of all Python `int` objects is a subset of the set of all possible things that meet the `Complex` interface, but that doesn’t mean they’re complex numbers, much less that they’re instances of `complex`. If you’re wondering whether integers are something you could define the laws of complex algebra over, then no, it isn’t. For example, one of the laws is that every number besides 0 has a multiplicative inverse, which obviously isn’t true for the set of integers. Or for the set of Python `int` values. But that’s not what the ABC is testing for, so that’s fine.

On Mon, Oct 14, 2019 at 7:04 PM Andrew Barnert <abarnert@yahoo.com> wrote:
If you’re wondering whether integers are something you could define the laws of complex algebra over, then no, it isn’t. For example, one of the laws is that every number besides 0 has a multiplicative inverse, which obviously isn’t true for the set of integers. Or for the set of Python `int` values. But that’s not what the ABC is testing for, so that’s fine.
Hmm, but every nonzero integer DOES have a multiplicative inverse - that value isn't another integer, but there is one. If the floating point value 2.0 has a multiplicative inverse 0.5, doesn't the integer value 2 also have that same multiplicative inverse? Your definitions are rather odd in places here. I'm not saying they're wrong, but I can't disprove the things you're saying are impossible, so it's hard for my brain to grok it. ChrisA

On Mon, Oct 14, 2019 at 07:53:07PM +1100, Chris Angelico wrote:
It depends on whether you are working in the Integers only, or something else. In the Integers, no, there is no multiplicative inverse for 2, because there is no *integer* (whole number) N such that 1/N = 2. Analogy: you can't divide one apple between two people so that *both* people get a whole number of apples. But in the Rationals, we allow additional values that cannot be expressed as whole numbers, namely the fractions. In the Rationals, there is a multiplicative inverse for all numbers except 0. Likewise for the Reals. Similarly there is no Real number X such that X*X = -1, but if you extend the values to Complex, there are two such values. -- Steven

On Oct 14, 2019, at 01:53, Chris Angelico <rosuav@gmail.com> wrote:
Sure, you can define a function from integers to rationals, but that isn’t a function from integers to integers, so that doesn’t make integers a field. To define a group/ring/field/algebra/etc. you need operations from that object to itself. Those operations are part of the definition of the object. And the rules that distinguish what object counts as a field, or as a division ring, etc. are rules on the operations used to define it. This is hard to explain without starting with group theory and set theory primers and working up through multiple levels from there, but let me given another analogy to programming: type T is only a field if it has (among other things) an inverse(x:T)->T method, and that method doesn’t raise for any values of T except T.zero(). Just having a method named inverse is not sufficient. People often think “well, natural numbers aren’t closed over subtraction, so we’ll just always use integers, and integers aren’t closed over division so we’ll just always use rationals, …” assuming that if you keep following that you get to the one true “numbers” somewhere around complex or quarternion. If this were true, we wouldn’t bother with different kinds of numbers. You’d learn about them in a set theory or math logic class and then never use them again. But in fact at each step you lose features as well as gain them. For the most obvious example, while complex numbers give you closed exponentiation, they take away ordering. So, they’re all useful for different purposes.
No, if you define a multiplicative inverse operation on the integers, it’s only defined for 1->1; its undefined for every other value. Compare with the integers modulo 5. Every integer modulo 5 but 0 has an inverse: 1*1, 2*3, 3*2, and 4*4 are all 1, so 1 and 4 are their own inverses, 2 and 3 are each others’. So this actually is a field, unlike the integers. But that doesn’t mean that the things we call “2” and “3” in some other field (like the rationals) are each others’ inverses. Even though we have the same kind of morphism as from Z to Q for Z to Z/5Z, and can say that the int 2, the rational 2/1 and the int-mod-5 2mod5 are all in a useful sense “the same number”, the inverse of 2/1 is 1/2, the inverse of 2mod5 is 3mod5, and the inverse of 2 does not exist. However, you can say something like this: if you have a morphism M that maps each integer to a unique rational and maps 0 and 1 to 0/1 and 1/1 and follows various other rules and has a partial reverse and so on, revM(M(n) / M(m)) gives you the same value as integer division whenever integer division is defined. So now you can define an integer-to-rational division function trivially as M(n)/M(m). And you can say useful things about that. For example, with rational division, 1/q is always the same as inverse(q) except at 0, and p/q is always the same as p * inverse(q), and so on—and all of these rules apply to your integer-to-rational division function as well. And that’s why, e.g., Python’s division operator makes sense: that’s almost exactly what it’s doing.
Well, they are all oversimplified, and the explanations are skimmed over very loosely on top of that. If you really want to understand it without working through a complete course in set theory and foundations… you’ll need explanations out there which are better than anything I could write (much less write on the fly in an ASCII email for an audience of Python developers…), but I’m sure they exist, because it’s really not that huge or that abstract. On stuff like this, Wikipedia is usually either a great place or start, or such an obvious mess that you can tell at first glance, so maybe try searching there first. The way to do this in category theory is even harder to explain, but let me try anyway, because it may be illustrative anyway. You start by assuming we can define the algebra C without defining a set C. Sure, it must have a big infinite pile of elements, but we don’t know or care if they’re a set. This requires changing the definition of algebraic structures to not use sets, but categorists do that all over the place. Now, the subalgebra of C consisting of all of the members whose imaginary part is 0 is not a working algebra, but can we define another algebra R that uses the same elements and works? Every proof that you can’t depends on the elements of C forming a set. But we didn’t bother requiring that, so let’s just say it isn’t. And voila, I’ve got a real algebra, using a sub-thingy of the exact same elements that are in my complex algebra. I can’t show it to you, but you can’t prove that it doesn’t exist, and I can prove that if it does exist it meets all the criteria you might demand of it (except the ones that rely on R or C being sets). And this works all the way down to N—then you find that each number isn’t a set either. Then what is it? It’s an object in a category, and categories and morphisms are all there is; there is no deeper question. And you can go off in other directions basically the same way other mathematicians do, and create all the other algebras anyone could ever want, and they all work as expected unless none of them exist, and that’ll show those fools at the academy who’s crazy. But anyway, even if you aren’t a categorist and don’t want to eliminate set theory foundations, you have to accept that they proved that `issubset(R, C)` is only false because of something about sets, not something about numbers or algebra.

On 14/10/2019 18:25, Andrew Barnert via Python-ideas wrote:
Speaking as someone with a dusty maths degree, can I just say that you are massively missing the point here? There exists an injection from integers to complex numbers, that's all that anyone has actually claimed here. You seem to be basing your entire contradiction on the informality of the notation they used. That's really not useful, nor is it meaningfully contributing to the original discussion. Can we get back to the original point, whatever that was? -- Rhodri James *-* Kynesim Ltd

Andrew Barnert via Python-ideas writes:
This is the central point. The rules that you can use to calculate (or prove things) depend on which definition of "number" you use (this is precisely where category theory places its emphasis). I would choose a different example, myself: when you move from the integers to the reals, you lose the notion of "next". This lack makes explaining some properties of dynamic models based on differential equations a real PITA if you are teaching students who haven't already internalized the mathematics of continuous time dynamics. Can't blame them, really, it confused Zeno, too. :-)

On Mon, 14 Oct 2019 at 05:47, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
`1 in complex` is _not_ mathematically true. Simplifying a bit, the elements of the algebra of complex numbers are ordered pairs of real numbers, and `1` is not a pair. When you’re working in, say, complex analysis, you just assume the usual morphism that maps every real to a unique complex (and some complexes to a unique real), and you can loosely multiply a real by a complex and so on.
This all just depends on your definitions. You can see some discussion of the approach taken in metamath here: http://us.metamath.org/mpegif/mmset.html#trivia """ One of the reasons that the proof of 2 + 2 = 4 is so long is that 2 and 4 are complex numbers—i.e. we are really proving (2+0i) + (2+0i) = (4+0i)—and these have a complicated construction (see the Axioms for Complex Numbers) but provide the most flexibility for the arithmetic in our set.mm database """ I guess the authors decided it was too painful to have 1 not be a complex number so they decided instead to define the integers as a subset of the complex numbers. Nothing wrong with that, just a different definition. The integers as a subset of the complex numbers still satisfy all the same properties. Oscar

On Sun, Oct 13, 2019, at 14:51, David Mertz wrote:
what about an "__rcontains__" method? For glob patterns, those don't currently have an externally exposed 'compiled version' at all, and I assume if they ever gained one it would be a compiled regex (globs are translated to regex internally, and there is a private method called _compile_pattern which returns the compiled regex's .match method)

On Oct 13, 2019, at 02:38, Steve Jorgensen <stevej@stevej.name> wrote:
That method already exists, it’s just called `__contains__`. Many types already customize this; you even get it for free from mixins like `collections.abc.Sequence`. Also, I’m not sure why you want `BrightColors` to be a class here. It doesn’t have any instances, or any other class behavior. It’s sort of like an open-ended `Enum`, but without any of the actual enum functionality. But anyway, you could write this today if you want it. I think what you want for the other examples is to add an `__rcontains__` method, so instead of `x in y` meaning `y.__contains__(x)`, it means either `y.__contains__(x)` or `x.__rcontains__(y)` depending on some rule that you have to define. Should it be the same rules as `__radd__`? You probably want to work through all of the cases for those rules with some example types.

On Mon, Oct 14, 2019 at 12:18 AM Steve Jorgensen <stevej@stevej.name> wrote:
For this specific example, have you looked at the enum module? https://docs.python.org/3/library/enum.html You can create a class that represents the enumeration, have members for each valid term, and then see if something is a part of the enumeration. ChrisA

This isn't really the same thing as enum since it has nothing to do with numeric values. Per Andrew, however, `__contains__` actually does exactly what I was asking for.
participants (12)
-
Andrew Barnert
-
Antoine Rozo
-
Chris Angelico
-
David Mertz
-
Ethan Furman
-
Oscar Benjamin
-
Random832
-
Rhodri James
-
Richard Musil
-
Stephen J. Turnbull
-
Steve Jorgensen
-
Steven D'Aprano