Search code examples
pythonpython-3.xstringmathsymbols

Change Python's default logic when comparing strings


Is there any way I can make Python "think" that 5 > 6, or that some other arbitrary math/string equation is interpreted in a specific way?

I mainly want to take a string with a symbol and automatically make it larger than the other, so that "⇟" > "◈" evaluates to True.

I know you can assign these symbols to numbers, but if there is an easier way to do this, it could be more efficient.

This is a basic idea of what I want:

firstSymbol = input("Type in a symbol: ")
secondSymbol = input("Type in a second symbol: ")
if firstSymbol > secondSymbol:
    print("firstSymbol > secondSymbol")
elif secondSymbol > firstSymbol:
    print("secondSymbol > firstSymbol")
elif firstSymbol == secondSymbol:
    print("Your two symbols are equal")

Since Python already has something like this programmed in, I want to change it so that I can create my own symbols that are either greater than or less than the other symbol, without the interruption of Python's automatic string comparisons.


Solution

  • This is fairly simple if you define your own integer class:

    class BadInteger(int):
        def __gt__(self, other):
            return super().__lt__(other)
    
    print(BadInteger(5) > BadInteger(6))  # prints True
    

    The BadInteger class is based on the int class (i.e. the regular Python integer). But in this case, we reversed how a "greater than" comparison works by reimplementing the __gt__ special method ("gt" stands for "greater than"). The implementation simply calls the integer's implementation of __lt__ ("less than").

    This effectively means that, when we try to compare two BadIntegers using >, it will work as if we compared two regular integers with <. If the "greater than" comparson would evaluate to True with regular integers, it will now evaluate to False with our BadInteger class, and vice-versa.

    You still need to reimplement any other relevant methods, so that they work the opposite way (e.g., __ge__ for the "greater than or equal" operator (>=), __lt__ for "less than" (<), etc). But this sets the base to achieve what you want.

    Edit

    Since you edited your original question, here's a follow-up answer.

    We can once again define a custom class in order to implement the functionality you want. Consider this:

    class MySymbol:
        def __init__(self, symbol, value = None):
            if len(symbol) != 1:
                raise ValueError('Symbol string must have length 1')
            if value is None:
                value = ord(symbol)
    
            self.symbol = symbol
            self.value = value
    
        def __str__(self):
            return self.symbol
    
        def __repr__(self):
            return f'MySymbol(symbol={self.symbol}, value={self.value})'
    
        def __gt__(self, other):
            self.check_instance(other=other)
            return self.value > other.value
    
        def __ge__(self, other):
    
            self.check_instance(other=other)
            return self.value >= other.value
    
        def __eq__(self, other):
            self.check_instance(other=other)
            return self.value == other.value
    
        def check_instance(self, other):
            if not isinstance(other, MySymbol):
                error_message = (
                   f"'==' not supported between instances of"
                   f" '{self.__class__.__name__}' and"
                   f" '{other.__class__.__name__}'"
                )
                raise TypeError(error_message)
    

    The MySymbol class takes a symbol and an optional value as the input. You may have seen where this is going, but the symbol represents your string, and the value is a number used in comparisons.

    Since we have implementations for the __gt__, __ge__ and __eq__ magic methods, our symbols "know" how to be compared to one another with the >, >= and == operators, respectively.

    Additionally, Python is smart enough to re-use these implementations and simply flip the result around - so we also get <, <= and != for free.

    Now this might not be exactly what you hoped for, because we still have to inform what is the specific value of each symbol we create. But this is the price to pay when creating a custom comparison - at some point in your program, you're gonna have to declare which of your custom symbols is greater than the rest, and vice-versa. Python won't ever "just know" that you're trying to compare things in an unusual fashion without you ever telling Python that you want to do so.

    Quick demo:

    firstSymbol = MySymbol('w', 30)
    secondSymbol = MySymbol('$', 108)
    
    firstSymbol != secondSymbol
    # True
    
    firstSymbol > secondSymbol
    # False
    
    # Note that it will use Python's default string values if we don't provide one
    thirdSymbol = MySymbol('a')
    fourthSymbol = MySymbol('b')
    
    thirdSymbol > fourthSymbol  # same as comparing 'a' > 'b'
    # False
    

    And following your example:

    s1 = input("Type in a symbol: ")
    v1_text = input("Type in its value: ")
    try:
        v1 = int(v1_text)
    except ValueError:  # use default string value
        v1 = None
    
    s2 = input("Type in a second symbol: ")
    v2_text = input("Type in its value: ")
    try:
        v2 = int(v2_text)
    except ValueError:  # use default string value
        v2 = None
    
    firstSymbol = MySymbol(s1, v1)
    secondSymbol = MySymbol(s2, v2)
    
    if firstSymbol > secondSymbol:
        print("firstSymbol > secondSymbol")
    elif secondSymbol > firstSymbol:
        print("secondSymbol > firstSymbol")
    elif firstSymbol == secondSymbol:
        print("Your two symbols are equal")
    

    Example output:

    Type in a symbol: !
    Type in its value: 100
    Type in a second symbol: #
    Type in its value: 10
    firstSymbol > secondSymbol