tef

bad poster & mediocre photographer

  • they/them

i guess the only thing to do is to learn dutch, and convince a young Guido van Rossum to create a slightly different version of python


python got a lot of things right

before i start, i want to be clear: python is great, and often a good example for a well designed, well maintained, and well run language. python's build tooling is a pain in the ass, sure, but we'll save that for another time.

python's great success is in being a language with low floors, wide walls, and high ceilings. it's relatively easy to get started, there's lots of different things you can do with it as a beginner, and there's almost no limit to the sorts of things you can build, too. as for the design? well, modula-3 was a good influence, and peps are a great idea too.

so yes, i like a lot of things about python, but this list is the opposite.

a list of warts i'd have polished.

less exceptions, please

look, exceptions are one of the reasons python got created, and i can understand the excitement and desire to use them everywhere, but iteration isn't a great use-case. it could have been much simpler:

i = iter(obj)
while i.next():
    print(i.value)

(the design is eric meijer's, for what it's worth. from one of his talks that's sneakily about monadic composition)

hopefully i don't need to do much convincing that this is a better design, especially given how little code it takes to use, or even to implement:

class Iterator:
    def __init__(self, obj):
        self.obj = obj
        self.len, self.pos = len(obj), -1
        self.value = None

    def next(self):
          self.pos +=1
          if self.pos >= len: return False
          self.value = self.obj[pos]
          return True

the nicest thing about not using exceptions is that when you write def next(self): you don't have to worry about your code throwing an GeneratorExit or a StopIteration and accidentally exiting the iterator early.

slightly less duck typing too, while we're at it

perhaps python's most obvious wart is that "abc" is also ["a", "b", "c"] as far as any operations are concerned. every so often you'll encounter an api that takes a list as an argument, and then you stare at the single letters in the exception message until you find the line that needs a ("abc",) around it

there's a similar problem in that [] and "" and 0 are all False, too. although a little convenient, this sugar-like behaviour regularly backfires in practice. the problem isn't "when it quacks like a duck" the problem "strings shouldn't quack like lists", "lists shouldn't quack like booleans"

implicit casts are the devil's playthings, and often the downfall of structural typing.

and strings, well, really, really shouldn't be lists.

let me be clear, foo[x] should not work on strings, but you shouldn't have a foo.charAt(n) method, either.

in python 3, if you have an emoji in your string, the entire thing gets converted to utf-32, just so that the language can do foo[x] in a reasonable time. something you shouldn't be doing in the first place.

strings really aren't lists of codepoints, but lists of lists of codepoints. you probably don't want to split the combining characters apart from the letters they're for. strings should be opaque blobs, without a len, and have methods for splitting it apart: foo.codepoints() foo.chars() foo.lines() and of course, foo.split().

dynamic scope would be nice, too

dynamic scope gets a lot of flak, and by all accounts it's a very clumsy way to build programs, but every so often you need contextual information, and without dynamic scoping, you end up with some sort of kludge.

imagine python had dynamic scope for a moment:

  • $var would mean 'dynamically scoped variable'
  • using $stdin instead of importing sys and using sys.stdin
  • overriding variables by passing named arguments foo($stdin=fh)

there are a number of python global variables, like sys.path or process wide variables like argv or environment variables, that could all be reasonably exposed through dynamically scoped variables, and it would involve far less bullshit every time you needed to use them

decorators shouldn't be functions

decorators should be objects, with a __decorate__ method. @foo(args) calls foo.__decorate__(args, obj), and functions have a __decorate__ method built in that expects empty args.

now? @foo and @foo() are the same thing. a small footgun, to be sure, but decoupling decorators from functions can be taken one step further. we could pass in the containing module or class into decorator, like __decorate__(obj, module, args)

decorators are already useful, but passing in the enclosing class would save an awful lot of boilerplate.

there's more but

i have other things i'd want to fix. using a __sortkey__ method instead of __eq__ or __hash__. more literals being immutable. classes that flatten instead of inherit. a dictionary type that allows for key-value pairs with empty keys, just so i can write f(**args) and not f(*args, **kwargs).

but most of that feels a little too sugary.

the big changes above—dynamic scoping, contextual decorators, strings as opaque blobs, iterators without exceptions—would change python enough that i'd have new problems to boil piss about.


You must log in to comment.

in reply to @tef's post:

kuroko is kinda like "python but with hindsight", but it's still fairly early in development so it's missing things like async i/o. between some of its syntactical changes and the fact that it's lua-grade embeddable (the author has a demo where they have it running as an efi application!) I really like it.

I mean, it's mostly python, so I can see why you like it, but it still has pretty much all the warts above, with the exception of a weird "return the iterator to signal the end of a collection" method

there's a similar problem in that [] and "" and 0 are all False, too.

I used to like this, but working in Clojure really changed my mind. The only “logical false” things are false and nil, everything else is “logical true”. To check for emptiness, you call seq which either constructs a lazy sequence over the collection or returns nil if empty. It’s short and clear.