 We're going to get started. My name is Dustin. I'm DI on GitHub. I work at Promforks, which is a consultancy in Philadelphia who I would like to thank for sponsoring my work in the open source community, but also this talk, which I call WOT, Mindbending Edge Cases in Python. So we're going to talk about some WOTs in Python. And you might ask, what is a WOT? WOTs are not trick questions. They're not bugs in the language. WOTs are like edge cases in a language that make you say WOT. Sorry. They seem weird, but if you actually understand how they happen or why they happen, they actually end up making sense. So we're going to look at 10 different WOTs. And to make things interesting, a few are actually going to be impossible. And so you'll need to decide whether they are actually real or not. So let's start with an example, which I'll call WOT number zero, just to get started. So the first goal here is just to determine if this WOT is possible or not. And if it is possible, what can we replace the ellipsis with to make it the desired result true? So in this case, we want to set x equal to some value. And we want to say when x is compared with itself, it's false. It's not itself. So the missing values in these WOTs are just going to be only limited to the built-in primitive types in collections. So these are Booleans, integers, strings, list sets, and combinations of these. So no lambdas, no partials, classes, or other tricky business like byte code or anything like that. So who thinks this is possible and definitely can be done in Python? Raise your hand. Who thinks this is definitely not possible and is the abomination in the eyes of Guido van Rossen? Raise your hand. Who's just not sure if this is possible or not? OK, so those are raise your hand the first time. This actually is possible. And so if we set x equal to 0 times 1 times 10 to the power of 309, it turns out that x is not equal to itself. And the reason for this is because 0 times 1 e 309 is not a number in Python. And that's how it's interpreted by Python. This is the same as saying 0 times infinity, which we can get by calling float inf, or just float na n. That's not a number in Python. And it's never equal to itself. So let's talk about na n for just one second. There's a really good reason why na n's not equal to itself or anything else. And it's because it's just not a number, right? Na n's designed to propagate through all your calculations. So somewhere if you produce something that's not a number, it kind of bubbles up. And it doesn't let you continue whatever you're doing. So because of this, na n is actually the source of lots of watts in Python and in other languages too. And in fact, it's the star of Gary Bernhardt's kind of infamous talk called Watt also, which is definitely the inspiration for this talk. And does anyone know what language that is? It's not Python. Yeah, that's JavaScript, of course. So in Python, there's a number of actually sensible ways to generate na n. And none of them are equal to themselves. And so for the purposes of this talk, na n is also not the answer to any of the questions. Because it turns out just when you set things equal na n, lots of weird stuff happens. All right, so the first Watt that I'm going to discuss. Can we create a list which is set equal to x? When sliced by a, b, and c, it actually has a new max. So the max of a list x will give you the biggest thing in that list. And then a, b, and c, that's the slice notation. So we're sort of like taking a chunk of that list. And we're actually saying that the max of 1 is less than the max of some subset of that list. Who thinks this is possible? Who thinks this is not possible? I guess everyone else is not sure. So this is actually not possible, right? We will, when we compare some subset of the list to the list, no matter how you slice it, no matter what you give us the slice arguments, the maximum of the entire thing is always going to be the largest value. You can't ever produce some value that's larger. So number two. Can we make an x and a y such that the minimum of x and y is not the same thing as the minimum of y and x? This depends on the order of the arguments to min. Does anybody think that this is possible? Anyone think it's not possible? This y is actually possible, but it's only possible if we set x equal to some set, such as the set containing 0, and y equal to some other set with a different value, such as 1. But it's only if x and y are sets. The reason for this is kind of a little bit strange. If we take the min of the set containing 0 and the set containing 1, we get the set containing 0. But if we do the opposite, we get the set containing 1. So it just sort of seems like min is broken here, right? It's just returning whatever the first argument that we pass it. Unless we look at it like this, if we pass min of the set containing 0 and 1 and the set containing 0, we get the set containing 0. So maybe not, maybe it's not broken. So let's imagine what the min function might look like if we implemented it in Python. So the min function can take any number of arguments, and it finds the min of all of them. It could also take just 1, which would be an iterator, but we're going to ignore that for now. So we're going to have two variables in our function. HasItem tells us if minItem has been set yet. And minItem holds the smallest item found at any point when we're going over the list of arguments. So next thing we'll do is go over, iterate through the list of arguments. And if HasItem has not been set yet, or if the current argument is less than the minimum argument that we've found, we set HasItem to true, and we set minItem to x, and then finally we return minItem. So this is really simplified. This is not really actually what happens. But for the purposes of this watt, this is what happens. So the key here is that less than operator all the way up in the if not hasItem or x less than minItem, which is comparing x to minItem. So the less than operator works just as you'd expect for something like integers. So zero is less than one, true. But when we use sets with the comparison operator, it behaves a little differently because it's overloaded. So specifically it's the inclusion operator. So here it's actually checking if what's in the set on the left side is included in the set on the right side. So zero is not included in the set containing one, but zero is contained in the set containing zero and one. So that returns true. And so when we say min of zero and one with two one element sets, we'll always get the first element back regardless. All right, watt number three. Can we create two lists x and y such that at least one element in x is true? That's what any does. It looks at all of them and tries to see if any of them are truthy. And when we add the variable y to x or append to it, there is not any elements that are true. Anyone think this y is possible? Anyone think it's not possible? Definitely never possible. This is actually not possible. So no matter what we add to x, if there is some truthy value in it, it will not change what's in x. If we add y to it. So the elements in y have no effect on what's in x. And anything in x, if that's true, the entire any will be true as well. So number four, if we have two variables x and y, can we set x such that the number of times that y appears in x is more than the length of x? So this y is actually possible. So if we set x to really any string at all, and we set y to the empty string, when we do the length of the original string x, we get something like six. But if we count the number of empty strings in that string, we get actually more than the actual length of the string. And so let's imagine what the count function might look like. So to make things easy, let's just make a function that takes a string, s, and a substring. And so we'll initialize the result to return zero. So that's the zero counts have been found so far. We'll iterate through all the indexes at which the substring could start in s. So this is gonna save us some search space, right? Like if s is a short string, like three characters long and substring is longer, there is no possible place where that substring can occur in the string. So the range will end up being an empty list. If they're exactly the same length, we only get one index into that original search string, which is zero. So that's the only place that the substring can occur. If the substring is shorter than the original string, we get a series of indices. These are all the possible places where the substring can occur in foobar. But if the substring is an empty string, this range function actually ends up giving us one extra index, one extra place to look in the sequence, which isn't actually a location in the original string. And so when we go and get our substring, our possible match out of the original string, we take a slice that looks like this. And again, if s is longer than the substring, we get something like the slice of the same size. And if the substring is an empty string though, we just get another empty string. So the slice from zero to zero, or really from any integer to the same integer, it's just always gonna give us an empty string. And actually, slicing won't raise an index error, even when the index is longer than the string. So we could do slice of 42 to 42, and it'll still just keep giving us an empty string, even if the original string is not that long. So to continue our function, if the possible match is equal to the substring, we increment the result. So in this case, we're comparing the possible match, which is always gonna be an empty string with the substring, which is also an empty string. So we're gonna increment result seven times instead of the original six, the length of the string. And so, as we see, for an empty string, the range of the indices, zero through six, or seven total, and the possible match gives us matches every single time. So this gives us a total count of seven. All right, number five, is it possible? So we were all sort of told in high school, order of operations for multiplication, not important, right? So is it possible in Python to have three variables, X, Y, and Z, such that when we multiply Y and Z and then multiply it by X, it produces a different result than when we multiply X and Y and then multiply it by Z. Does anyone think this is possible? A lot of people do. Okay, I'm surprised by that. Anyone think it's not possible? Some people? Okay, so this is actually possible. And it's possible if we set X equal to some list, such as the list containing zero, and Y and Z both equal to negative one. So in this case, when we multiply Y and Z, we get negative one times negative one, which produces one. And then when we multiply any list by one, we get itself. So this also becomes zero. So this left-hand side of the equation produces the list containing zero, the original value of X. However, on the other hand side of the equation, when we multiply a list by a negative number, does anyone know what happens? We actually end up producing an empty list because it's the same as multiplying the list by zero. And then when we multiply an empty list by negative one, again, this yields an empty list. So in this way, the two different sides of the equation are totally different. So actually, there's one other way that I found to satisfy this function, which is a little crazy. If we set X, Y, and Z equal to some very specific numbers, we can exploit some of the ways that Python handles floating-point numbers. And actually, the result of this multiplication is that, and that is off by just the tiniest, little least significant bit. And I can't totally explain this. I haven't been able to look into it enough, but basically floats are not always exactly what they seem. All right, so for number six, can we define two lists, X and Y, such that X is less than Y, but when we compare each individual element, every element in X, when we compare each individual element, every element in X is greater than or equal to Y. So we're actually gonna take two lists, we're gonna zip them together, which is when we take two and we just sort of combine them as tuples. And then we're gonna say that every element in one is actually greater than the other. So this Y is actually possible. And the way to do it is, again, if X is any zero-length iterable and Y is really any iterable at all. So here we'll use the empty string again and a string called foobar. So what happens here is the comparison operator is just elixir graphical ordering. So an empty string comes before foobar, just like dog would come before dogfish in the alphabet. And in the same way, empty lists come before non-empty lists. So when we zip an empty iterable with some iterable up with a length, the result is actually just still, they have two uneven lengths. So the result will be the iterable of the shorter length. And when it's an empty string, we just get an empty list. And when we check for all of an empty list, it short circuits. So it goes through and it looks to see if anything will return false. If it does, it returns false. But otherwise, it just returns true. So all of an empty list is actually true. All right, Watt number seven. Can we set X equal to something such that when we cast X to a list and then to a set and then we check the length of it, it's not the same thing as casting X to a set then to a list in opposite order and checking the length of that. Does anyone think that this is possible? Does anybody think this is not possible? Definitely never possible. This Watt is not possible. So no matter what we set X to, converting it from a list to a set might actually reduce the length of X, right? The list could have duplicate values. The set would eliminate them. But converting a set to a list is always, it's never going to add elements. It's always gonna be the same. So in this case, there's nothing that we can set X to such that this equation ends up being false. Number eight. Can we make an X such that the minimum of X is not the same as the minimum of X unpacked? So this is sort of talking about what I said earlier about min is that min might take a number of arguments or it might take an interval, right? Anyone have an idea if this is possible? Russell thinks maybe, anybody else? This Watt is possible. So if we set X equal to a list containing another list containing like something like zero, this is the result. So this Watt exists because, like I said, the different way that min handles its arguments. So minimum of the list containing one, two, three is the same as the minimum of containing one, two, three unpacked, which is the same as min of one, comma, two, comma, three. These are all the same. So the min of X is just the first element in it, right? So this returns the list of containing zero. And but the min of X unpacked is the min of the list containing zero, which is just zero. So when we unpack X, we get that list back and then take the min of that iterable and then it's just that single element. So this way we can see that min of X produces a different value than the min of X unpacked. All right, number nine. Can we have two values, X and Y, such that the sum of zero times whatever X is with Y as a starting value is not equal to Y. So this is a sort of, I don't know, we don't use this feature of sum too often, but sum takes two arguments. One is an iterable and the other is a starting value. So it's where it starts the sum, it sort of adds it to the beginning of the summation. Does anyone think this Y is possible? Anyone think it's not possible? Okay, this Y is not possible. I'll say that confidently. Anything times zero is going to be either zero or it's going to be an empty sequence. And the sum of zero or the sum of an empty sequence is always going to be zero. So no matter what we start the starting value at, the result is always going to be the same. So here Y is the start of sum, which is seven. Sum of three ones would be 10. Sum of an empty list would still be seven. So this would always end up being, if X is multiplied by zero, always equal to the starting value. And last is number 10. This is my favorite of all of them. I saved it for last. Can we find two values, X and Y, such that Y is greater than the maximum value in X, but Y is also contained in X? I'll let you think for just for one second, because I like this one so much. Who thinks that this is possible? Who thinks that it is not possible? This Y is actually possible, but it only works if we set X equal to a string, such as AA, and Y is also that exact same string. I see some people nodding, which is great. So this is the only way that it works. And the reason why this works is because Python uses the in operator a little bit differently for strings than for lists. So the max of a string containing like AA is just going to return in a single character. So it's going to take that iterable just like min was doing and find the biggest one. In this case, it's just A. And because of a lexicographical ordering, like I mentioned before, A is going to proceed AA, just like dog precedes dogfish. But also, like I said, Python handles in a little bit differently. So here we're actually doing like a substring match, right? We're checking to see if the substring AA is in the full string AA, which is true because the strings are the same. And this is different than the way we would do it for a list, right? Like we could turn the string AA into a list containing the characters A and A, but then AA wouldn't be in that list, so this is false. All right, so one last thing I want to say. These watts seem really like technically interesting, right? They're really quirky, they're really simple questions, but I can almost guarantee that you have never encountered these in the wild. In fact, I've been writing Python for a long time, and I've only seen like one or two of these. And so the only reason I really have to share them with you is because a really smart fellow named Christopher Knight collected a bunch of them and put them online somewhere. I'd never even saw them in person. So this is why I have to ask you, please don't use these for job interviews. It seems really tempting, but if you didn't know the answer to them when I first showed them, I'd guarantee your interviewees not gonna know them either. So again, I want to thank my employer Promptworks for sending me here. Thanks to Christopher Knight for introducing me to these. Thanks to Py Gotham for the event, and thanks to you all for listening. I have any questions. So if you think you have a watt that is just proving something in my list, you can come up to me afterwards and we can open up an interpreter and we can take a look at it. But if you have any other questions about the way that these functions work or why one thing is one way or another, feel free to ask it now. In the way JavaScript does stuff, plus and minus should be mathematical operations, but aren't, and they aren't in different ways depending upon how they're being interpreted and things like that. Whereas these are, they're divided by zeros. They're edge cases of not a number and man and float and string. The only one that isn't is kind of your last one where it's the difference between a string being interpreted as a list of characters versus being a substring and operated within. So, absolutely, your philosophical interpretation of these watts, these are things you should normally see whereas the watts in JavaScript you need to know because it's way too late. Right, so the question, just to repeat it, is basically saying that if you watch the original Gary Bernhardt's talk about watts in JavaScript, all those watts are just like, they really don't actually make any sense, but these watts in Python when you look at them, they're actually, they make sense, like this is the way that the language should work. They're not bugs, they're not like really poorly designed edge cases of the language, like these functions, this is the way that they should work. And yeah, like you said, with the exception of the last one, which is sort of like an ambiguous way in which Python just sort of like helps us out because when we're comparing strings, we really wanna know if a substring is in a string and not if a character is in a string. Yeah, I think that this is one of the ways that we can see that Python is a really well-designed language because these watts are just, we had to look pretty hard to find these watts too. They're not as immediately obvious as the ones that Gary talks about in his talk. I'd recommend watching that. It'll make, that talk will give you a good appreciation for how good our language is compared to JavaScript. Any other questions? Okay, thanks again, everybody.