Hacker News new | past | comments | ask | show | jobs | submit login

Shameless plug: For people who like black, I've been working on ssort[0], a python source code sorter that will organize python statements into topological order based on their dependencies. It aims to resolve a similar source of bikeshedding and back and forth commits.

0: https://github.com/bwhmather/ssort




We use isort[0] for this. It even has a "black" compatible profile that line spits along black's defaults. Additionally we use autoflake[1] to remove unused import statements in place.

[0](https://github.com/PyCQA/isort)

[1](https://github.com/PyCQA/autoflake)


isort only sorts imports. ssort will sort all other statements within a module so that they go after any other statements they depend on. The two are complementary and I usually run both.


Thanks for your work on this. Coming back to python from golang, I really missed the auto-formatting. black plus something like ssort seems to bring the same to python. I've had really good results with black so far and look forward to trying ssort.


This is relevant to my interests. We have an internal code style guide at my company that includes guidelines for order of class statements, roughly matching yours. I have one pet peeve that made me write the style guide in the first place - Django's `class Meta` which we always have at the top of the class because it contains vital information you need to know as a programmer, like whether this class is abstract or not. Whenever I have to work with an external Django codebase and find myself scrolling through enormous classes trying to find the meta my blood pressure rises.


I've had the same problem with pydantic. Currently, properties are special cased and moved to the top. Everything else, including classes, is grouped with methods. Meta classes will end up somewhere in the middle, which is probably the worst possible case.

SSort is currently used for several hundred kilobytes of python so I'm wary, but if I'm going to make a breaking change before 1.0 then I think this is likely to be it.


By the way, I have raised a ticket to finalize method order before 1.0 release (https://github.com/bwhmather/ssort/issues/11). Please follow and comment if you would like to see a change.


Some illustrative before-after syntax-highlighted code segments would be a nice addition for the readme.


He added some :)


I'll try to add some better ones over the weekend. This should be a start.


Looks cool but it seems like it might still need some work?

I tried it on one of my Django `admin.py` files and it created NameErrors.

    class TestAdmin(admin.ModelAdmin):
      list_filter = ("foo_method",)

      def foo_method(self, obj):
        return "something"

      foo_method.short_description = "Foo method"

    # It turned it into this:

    class TestAdmin(admin.ModelAdmin):
      list_filter = ("foo_method",)

      # NameError
      foo_method.short_description = "Foo method"

      def foo_method(self, obj):
        return "something"


Yup, that's a bug. All assignments are treated as properties and moved to the top. Fix to follow shortly.


Have pushed fix as version 0.10.0. Thank you very much for reporting.


Cool no problem! I'll keep an eye on this project


This sounds like a living hell if you use git diff a lot to compare for small changes that might introduce a bug? which is what happens at work all the time since our unit test and CI are a joke. Not dumping on your project but the idea of that much of a change up of the code scares the dickens out of me.


Once the code is initially migrated (which should not break it), the diffs won't be large, since the order should be consistent.


One thing worth mentioning is that the `git blame` ignore file trick doesn't work as well with ssort as it does with black because the changes ssort makes tend to be much less local.


Use it at the editor level instead of in CI and I can't see how it can cause you any problems at all. I could easily be missing something though?


Does it put high-level business logic at the top or the implementation details at the top?

Which is preferable, and why?


Implementation details at the top. Python is a scripting language so modules are actually evaluated from top to bottom. Putting high level logic up top is nice when you just have functions, which defer lookup until they are called, but you quickly run into places (decorators, base classes) where it doesn't work and then you have to switch. Better to use the same convention everywhere. You quickly get used to reading a module from bottom to top.


But this is really backwards? Everyone uses the if __name__ == "__main__" dance to avoid calling functions before they're defined, no?


It is a bit backwards, but in exchange you get predictability

With backwards sorting you know that, unless there is a cycle, you can always scroll up from a call site to find the definition or down from a definition to see where it is used. With forwards sorting you can scroll down to find a definition, unless the function was imported, or used as a decorator somewhere, or called by something that was used as a decorator, or used in some other way that I haven't thought of.

My personal experience is that this predictability is hugely useful. It almost entirely obviates the need for jump-to-definition within a module, and gives modules a very obvious shape and structure.


I never thought of it as backwards. Defining functions before calling them makes as much sense as defining terms before using them or assigning variables before reading them.


Very interesting, especially the method order part. I dislike the order you chose, and yet, I would be tempted to use it on my projects anyway, because being congruent is so important to me.


This is about how I initially felt with black. I didn't like some of the things it did, but I was happy to have a standardized opinionated formatter so I went with it. Was definitely the right decision.


A standard no one likes is often better than no standard at all.


It’s an unfortunate compromise with code formatters. As someone who takes code formatting really serious and puts a lot of manual thought into code formatting, code formatters almost always make my code worse, in my opinion, or at best unchanged outside of small errors like double spaces, etc. But it creates a standard that it loves everyone’s code towards, which is good., and also obviously alleviates the chore of manually formatting.

I just wish sometimes they were more configurable. For example, the Elixir formatter is quite opinionated on things but is generally not configurable.


I often wish they were _less_ configurable. Even Black's handful of config options are too many... Each one is a chance to pick a whole new color for the bikeshed.


That makes sense, but it's a problem when the formatter makes a rather opinionated choice and doesn't allow one to configure it. The primary example I'm thinking of in Elixir is comments for pipelines.

For example, if I use the pipe example on Elixir's landing page and add comments to it (obviously a contrived example):

    "Elixir"              # string to get frequencies for
    |> String.graphemes() # Get all graphemes (i.e., character units)
    |> Enum.frequencies() # Get the number of occurrences of each grapheme
This gets formatted by the Elixir formatter to:

    # string to get frequencies for
    "Elixir"
    # Get all graphemes (i.e., character units)
    |> String.graphemes()
    # Get the number of occurrences of each grapheme
    |> Enum.frequencies()
This is a rather strongly opinionated format stance, and as far as I know, it's not configurable.


Could you show an example in the README? The first two pairs of input/output in https://github.com/bwhmather/ssort/tree/master/examples look unchanged


Will do. Examples directory isn't terribly helpful as documentation as it mostly contains real code with problematic syntax (and compatible licensing) that tripped up ssort when I ran it on a copy of my pip cache. I will move it into tests to avoid confusion


Thanks for sharing this. When solo coding, I tend to dump new classes and functions wherever is physically closest to where I was previously editing. It makes sense in the moment so I don't disrupt my train of thought by jumping all over the file, but then is a confusing ball of mud when I need to return to the project after time off. Was the shortest scroll direction up or down when I implemented it? etc…


this is great. Imagine i declare global variable which is used in function which is defined AFTER this global variable is declared (filled by value) and then function is executed later. Why does ssort put my declaration/filling of global variable before that function declaration?

def myfunc(): global globalvar str(globalvar)

globalvar='abc'

myfunc()

will be transfered to

globalvar='abc'

def myfunc(): global globalvar str(globalvar)

myfunc()

I understand why is it done but i dont want to have function definition block filled with this declaration of variables (which i do later) since it has no impact to my code and it makes is just a bit "cleaner". Dont tell me to not use global variables :D


Sounds interesting and perhaps novel. Might help if there were an example or two in the readme - as it is I still don't exactly know what this is.


Very cool - I'll be following this.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: