Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

One question comes to mind: isn't cyclic dependencies something to be resolved at the root cause (remove the cycle)? It's an issue that causes various pains down the line, like type checking in this case.


The issue is that, often, without type annotations there would be no cyclic dependency (because any type matching the implicit interface would work). So by introducing type annotations (which means extra imports), you end up having to refactor quite a lot of code and the end game is a one-type-per-module-with-separate-interface-definition system of the sort encountered in, well, statically-typed languages. If you don't do this you end up with the 'Type in string' annotations mentioned in the article and TYPE_CHECKING-gated imports. Both of which feel kind of hackish.

It's fine to have implicit circular dependencies in Python because duck typing means that every function is effectively generic. Type annotations eliminate this and turn Python into a fundamentally different language.

Type annotations aren't just extra boilerplate in function signatures, they also have these sorts of knock-on effects.


I read 'Type in string' like a forward declaration. I find uses for them within a single module. They're not always hacks.


Aside from type checking being a clusterfuck in python, wouldn't checking against a protocol be the right thing?


Broadly speaking, yes, but for some situations like database models with bi-directional relationships that can cause unnecessary maintenance burden simply for the benefit of type checking which this allows you to work around since the cyclical nature is only caused by typing not runtime semantics


Removing cycles may require extensive refactoring of the code, sometimes resulting in unintuitive layouts. All, just to solve the cyclic-import errors.

At the core of the problem is the fact that there's no thing as "partial import" in Python. When you do "from M import A" in Python, all the contents of M are evaluated and a reference to A is added to the current namespace. So, cyclic dependencies arise sooner or later, unless you adopt some very un-pythonic style for your codebase (e.g. one file per class).


In python it's very hard to avoid cyclic dependencies. Something as simple as a parent-child link between two classes is a cyclic dependency, and if the classes are in different files you have a car like in the blog post. This comes up especially hard when typing your codebase, because you have to name every type, and in python implementation is the interface, i.e. you can't just include a type definition file.


Parametrize the parent with the child and the child with the parent. Tie the knot after both are defined.


> parent-child link between two classes is a cyclic dependency

What do you mean? Where is the cycle?


I also wonder. If a child class knows its concrete parent (apart from where inheritance is declared), and not just references it via super(), things have already gone badly wrong.


The parent holds a reference to the child. The child holds a reference to the parent.


Why does the parent hold a reference to the child?

Are you talking about a specific pattern or implementation detail in the interpreter? Because I would not call this "dependency"...


    class Parent:
      children : List[Child]

    class Child:
      __init__(self, parent: Parent):


Ah yes, it might be what they mean. I assumed inheritance. Thanks.




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

Search: