1. 14
  1. 3

    Sideways related, that << operator threw me for a loop! e.g.

    assert (whitespace >> int_parser << whitespace).parse(" 123    ") == 123
    

    What the whaa? For a minute I thought the package author had found a way to inject a new operator in the stdlib.

    But no! Turns out there’s builtin rshift and lshift operators in Python that I’ve never used before. How they interact with objects can be specified through dunder methods __rshift__ and __lshift__. The author simply overloaded their meaning in this context. From the parsy source:

    class Parser:
        ...
        # haskelley operators, for fun #
    
        # >>
        def __rshift__(self, other: Parser) -> Parser:
            return self.then(other)
    
        # <<
        def __lshift__(self, other: Parser) -> Parser:
            return self.skip(other)
    

    Very nice touch, @spookylukey.

    1. 2

      Actually I didn’t come up with that - I maintain the code and have added some significant enhancements, but it was written by Jeanine Adkisson.

    2. 2

      In regard to footnote [3], this seems to work:

      class Addable(Protocol[T]):
          def __add__(self: T, other: Addable[T]) -> T:
              pass
      
      
      def foo(x: Addable[T], y: Addable[T]) -> T:
          return x + y
      
      1. 1

        Thanks I’ll give that a go!

        1. 1

          This is my test case. A generic function like foo that can be used on both ints and lists without static type errors is what I’m trying to achieve.

          from __future__ import annotations
          from abc import abstractmethod
          from typing import Protocol, TypeVar
          
          T = TypeVar("T")
          
          
          class Addable(Protocol[T]):
              @abstractmethod
              def __add__(self: T, other: Addable[T]) -> T:
                  pass
          
          
          def foo(x: Addable[T], y: Addable[T]) -> T:
              return x + y
          
          
          myint: int = foo(1, 2)
          
          mylist: list[int] = foo([1], [2])
          

          With both mypy and pyright I get a bunch of errors for the last two lines with this. With or without abstractmethod.

          1. 1

            Turned out to be tricker than I thought! I’m not sure if this is the best way, but it seems that this would work: https://gist.github.com/ba0ae4e284ea9a09007a3b84e183ad26.

            Edit:

            Works even better than I thought, it does pass on:

            mylist: list[int|str] = foo([1], ["1"])
            

            But fail on:

            mylist: list[int] = foo([1], ["1"])
            
        2. 2

          I’ve been writing a lot of Python type annotations at work, and this article definitely rings true for some of the trade offs and considerations that have been floating around in my head.

          EXTREMELY tangential/nitpicky to the point I almost didn’t mention it, but the Unicode symbol on your section headings won’t load on either my Macbook or my iPhone, no matter what fonts I have configured.

          1. 1

            I feel like this is the first in-depth case study I’ve read where Python type annotations were a net negative.

            1. 1

              I actually have plenty more criticisms of Python type annotations - or to be precise, static type checking in Python based on type annotations - and very mixed feelings in general. I’m convinced there are significant benefits, but I’ve seen very few people talking about the costs.

              I’m also trying to put my thoughts together in the form of concrete case studies which are small enough for other people to analyse, push back on and see the issues.