Re: [Python-ideas] Forward-References & Out-of-order declaration
On Wed, Aug 26, 2015 at 8:11 AM, Sven R. Kunze <srkunze@mail.de> wrote:
The issue (not a huge problem again but annoying from time to time) is that the order of declaration in a module actually matters. IIRC other modern languages like C# don't require you do actually care about this anymore.
Possible example (for whatever reason an author wants to do that -- also cf. stackoverflow):
class UseThis(Base): pass
class UseThat(Base): pass
class Base: pass
In that regard, Python feels a bit rusty.
Frankly, I don't have a problem with this. You get a mandate that requires you to do what's good practice anyway: lay things out in a logical order. In the same way that Python's use of indentation for block structure is generally just enforcing what you'd have done regardless of language, this requires that you sort things in dependency order. That tends to mean that the first use of any name in a module is its definition/source. Want to know what 'frobnosticate' means? Go to the top of the file, search for it.
Having that enforced by the language is a restriction, but how often does good code have to be seriously warped to fit into that model? Not often, in my experience.
ChrisA
Just to provide a concrete example, sqlalchemy's ORM seems to really contort itself (at least from the user's perspective) to get around this problem. The reason in that case is that the dependencies between tables don't have to be directed acyclic graphs, e.g. it's common for two tables to depend on each other. I've also run into this problem when working with my home-grown message-passing APIs, which can also form more complicated dependency graphs. So I do think that good code occasionally has to warp itself to fit into python's model. -Kale
On Thu, Aug 27, 2015 at 3:15 AM, Kale Kundert <kale@thekunderts.net> wrote:
Just to provide a concrete example, sqlalchemy's ORM seems to really contort itself (at least from the user's perspective) to get around this problem. The reason in that case is that the dependencies between tables don't have to be directed acyclic graphs, e.g. it's common for two tables to depend on each other.
Fair point, but in my experience with SQLAlchemy, it's not that bad to identify tables with string identifiers: class Manufacturer(Base): __tablename__ = 'manufacturers' id = Column(Integer, primary_key=True) name = Column(String, nullable=False) stuff = relationship("Thing", backref="manufacturer") class Thing(Base): __tablename__ = 'things' id = Column(Integer, primary_key=True) name = Column(String, nullable=False) makerid = Column(Integer, ForeignKey('manufacturer.id'), nullable=False) (I might have the specifics a bit wrong, but it's something like this.)
I've also run into this problem when working with my home-grown message-passing APIs, which can also form more complicated dependency graphs. So I do think that good code occasionally has to warp itself to fit into python's model.
Same sort of thing. You're right that these violate (by necessity) the principle of "first use is definition", but these are incredibly rare cases. Just in the example above, and without any real code doing any real work, I have seven names: Manufacturer, Base, Column, Integer, relationship, Thing, String Two of them have a cyclic relationship (Manufacturer and Thing). All of the rest can still follow that principle (and in this case, most of them would be listed in the top-of-file import block). There are specific situations where the graph is more complicated, but I still like being confident that the code will broadly follow that design layout. The two basic solutions still apply: either use string names to identify not-yet-defined objects, or have a "pre-declare" syntax to make things possible. In C, "struct foo;" is enough to let you declare pointers to foo; in Python, you could have "Thing = Table()" prior to defining Manufacturer, and then you could use an unquoted Thing to define the relationship. Either way makes it clear that something unusual is happening. ChrisA
On 27 August 2015 at 09:41, Chris Angelico <rosuav@gmail.com> wrote:
The two basic solutions still apply: either use string names to identify not-yet-defined objects, or have a "pre-declare" syntax to make things possible. In C, "struct foo;" is enough to let you declare pointers to foo; in Python, you could have "Thing = Table()" prior to defining Manufacturer, and then you could use an unquoted Thing to define the relationship. Either way makes it clear that something unusual is happening.
It's also the case that *circular dependencies hint at a design problem*. They're sometimes an unavoidable problem (because you're modelling a genuinely bidirectional relationship), but they're still a problem, since acyclic models structurally avoid a *lot* of the challenges that come up when cycles may be present (for example, consider how much easier it is to traverse a filesystem tree if you *don't* support following symlinks). Teasing apart a data model (which is what a class hierarchy represents) to either eliminate the circular references, or else limit them to within particular files is actually a pretty good way to figure out which parts of that model are tightly coupled, and which are more loosely related. Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (3)
-
Chris Angelico
-
Kale Kundert
-
Nick Coghlan