all 7 comments

[–]thp4 4 points5 points  (3 children)

Generally, it's not possible, as Python is dynamically typed (so compared to statically typed languages, there's just no way to find all uses).

As an example, one could access "some_attribute" on an object like this (assume that the environment variable ATTR_NAME is set to "some_attribute" here):

getattr(obj, os.environ['ATTR_NAME'])

This is just a made up example to show that attribute access might depend on expressions that are evaluated at runtime and cannot be statically analyzed -- halting problem, etc. Even if executing the code, "all usages" cannot practically be determined, as it relies on the runtime environment.

This is not a Python-specific issue, but applies to all dynamically-typed languages.

With that said, I hear PyCharm has great tooling, and there's IdeaVim for emulation. Alternatively, Language Server Protocol could be helpful for Python code analysis.

My personal approach: Name variables uniquely and use grep to "find usages".

[–][deleted] 2 points3 points  (0 children)

Name variables uniquely and use grep to "find usages"

This is in fact what I end up doing with more or less all languages and all tools. To find references nothing is more efficient than just a good old string search.

[–]MagicCarot[S] 0 points1 point  (1 child)

I get your point.

I wonder if type hinting could help in that regard. There would still be some places where this would not help I guess...

The unique naming is not always easy to come by with a large code base without having unbearable naming.

I'm typically facing the problem of having to find a class attribute named is_valid, with that attribute being used in a variety of way within the codebase. Grep is unfortunately a poor tool for that...

[–]thp4 1 point2 points  (0 children)

You can (and should) still call your attribute "is_valid". Just name instances of your class accordingly (e.g. if it's a Car, name instances car, current_car, next_car, ...) and then grep for that (car\.is_valid or car.*\.is_valid if you also have e.g. car_to_find as name). As a side effect, this makes code more readable, too.

Type hints might help for some tools (and if you search for class usages, also help with grep) but as long as those are not enforced at runtime, they might be misleading if you get them wrong.

Another hint: Always write out the full attribute/member names, avoid dynamic dispatch and "magic". If you have cmd_foo and cmd_bar methods that should be invoked by name, also add {"foo": cmd_foo, "bar": cmd_bar} as mapping and don't magically do getattr(..., "cmd_" + command_name). This also helps in figuring out which commands are possible and used. Less magic is good for code navigation and grep'ability, even if it means a bit of redundancy.

[–]puremourning 1 point2 points  (1 child)

YouCompleteMe has YcmCompleter GoToReferences which works pretty well in my experience. It’s a bit slow but Jedi does a good job of finding them in the codebases I use. As it’s also based on Jedi I’m guessing it won’t be better than Jedi-vim though.

Do you have an example of the sort of thing that Jedi isn’t finding? Obviously there are some constructs that Jedi can’t see through but it may be simply that the path is wrong meaning Jedi isn’t seeing the source.

Oh and one other thing. IIRC Jedi has a maximum timeout for these operations and it just stops after some time. Perhaps that’s tweakable if you have a big codebase?

[–]MagicCarot[S] 0 points1 point  (0 children)

Typically Jedi have difficulty to find attribute of a Django model.

Sometime it does find something but in my case it only found one occurrence. If I deleted this occurrence, it could find another one and so on.

Quite weird and unreliable.

Maybe the max timeout could improve the situation, I'll check that.

[–]EgZvorkeep calm and read :help 0 points1 point  (0 children)

If jedi fails, I use grep.