• Imports and dot-notation

    From Oliver Schinagl@21:1/5 to All on Wed Aug 9 12:30:51 2023
    Dear list,


    First a disclaimer, I am a python novice ;) so apologies beforehand for
    any incorrect terms and use thereof :)

    I have a question about the preferred/pythonic way dealing with imports.
    But let me start by giving a little bit of an example (which lead me to
    this question).

    Looking at a python projects code and repository layout, we see the
    following directory structure.


    /project/core
    /project/components/module1
    ...
    /project/components/moduleN
    /projects/util

    (this is far from complete, but enough to help paint a picture.

    Some modules import other modules, and I see (at the very least) two
    (kind of three?) different ways of doing so.

    `from project.components.module1 import function1, function2 as func,
    CONST1, CONST2 as CONST`

    or maybe even (which has as an advantage that it becomes clear which
    namespace something belongs to

    `from project.components.module1 import function1, function2 as module1_function2, CONST1, CONST2 as MODULE1_CONST2`

    but then it really just becomes personal preference, as the number of characters saved on typing is almost negative (due to having a more
    complex import).


    but also the dot notation being used

    `from project.components import module1`

    where obviously the functions are invoked as `module1.function1` etc

    I hope the above is clear enough from an example.


    Simply put, which of the two would be considered cleaner and more pythonic?

    Now for a bit more thought, looking at PEP8, we notes about imports, but
    sadly PEP8 does not state which method is better/preferred. While
    obviously in the end, it's always the maintainers choice in what they
    prefer, so are tabs vs spaces and PEP8 is opinionated about that too (in
    a good way, even though I'm not a fan :p).

    Likewise, if we look at PEP20, there's quite a few 'guidelines' that
    suggest the dot-notation. To name a few (opinionated) examples (but note
    I am just using PEP20 as a helper to give some arguments, I know some
    people feel strongly against pep20 :p):

    * Beautiful is better than ugly.
      - Personally, I think the dot notation is more beautiful, but I
    understand that some people find the short notation more beautiful and
    less of an eye sore ;)

    * Explicit is better than implicit.
      - To me, this is a strong point. Being more explicit is always
    better, using the dot notation (if though it's not doing the WHOLE path)
    is a nice balance in being explicit enough. Code readability comes from
    being more explicit (without being to terse of course).

    * Simple is better than complex.
    * Flat is better than nested.
      -  I think having more flat and simple imports, and (while more
    characters to potentially type in the invocation) more readable
    invocations would be better?

    * Readability counts.
      - The dot notation seems to help in readability. For those unfamiliar
    (or less familiar) with a code base, it becomes incredibly clear where a something comes from (WITHOUT having to rely on IDE's; sure you can
    hover over something and see that the IDE has hopefully worked out where something came from, but this doesn't work when just reading a page of
    code, you aren't going to hover over everything and remember all that
    context).

    * In the face of ambiguity, refuse the temptation to guess.
      - I'd argue this is almost self-explanatory, not using dot-notation
    makes it more ambiguous where something is coming from.

    * There should be one-- and preferably only one --obvious way to do it.
      - This is where my confusion/question obviously comes from, as
    there's multiple ways of doing it, so maybe PEP8 (or a new one) should
    more strongly favor one?

    * Namespaces are one honking great idea -- let's do more of those!
      - Basically, this question/proposal would 'force/nudge' you into
    writing more with namespaces by using the namespace as prefix.


    So what IS the preferred/more pythonic/recommended way to deal with imports?

    Why was this not mentioned in PEP8?

    Is it worth while to introduce a new PEP or amend PEP8?


    For what it's worth, hallucinating chatgpt/google bard both say the same
    about this topic, that within the python community, it is generally
    preferred and a good idea to use dot-notation. But how correct are they lol.


    Thank you for reading,

    Olliver

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Oliver Schinagl on Wed Aug 9 16:42:21 2023
    Oliver Schinagl <oliver+python@schinagl.nl> writes:
    Simply put, which of the two would be considered cleaner and
    more pythonic?

    I'm not sure what "clean" or "pythonic" means here.

    To me, the canonical import is

    import math
    print( math.sin( 0 ))

    , because "math.sin" shows where "sin" does come from.
    This is valid for production code.

    For tiny code examples it might look nicer to use

    from math import sin
    print( sin( 0 ))

    , but I would try to avoid

    from math import sin as sine
    print( sine( 0 ))

    because it hides the common name of the object.

    When there is a nested module,

    import tkinter.messagebox
    tkinter.messagebox.showinfo( "ok.", "It's alright." )

    , as this won't work:

    import tkinter
    tkinter.messagebox.showinfo( "ok.", "It's alright." )

    . But for small code examples:

    from tkinter.messagebox import showinfo
    showinfo( "ok.", "It's alright." )

    .

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From dn@21:1/5 to All on Thu Aug 10 11:28:36 2023
    On 09/08/2023 22.30, Oliver Schinagl via Python-list wrote:
    Looking at a python projects code and repository layout, we see the
    following directory structure.

    /project/core
    /project/components/module1
    ...
    /project/components/moduleN
    /projects/util
    Some modules import other modules, and I see (at the very least) two
    (kind of three?) different ways of doing so.

    `from project.components.module1 import function1, function2 as func,
    CONST1, CONST2 as CONST`

    or maybe even (which has as an advantage that it becomes clear which namespace something belongs to

    `from project.components.module1 import function1, function2 as module1_function2, CONST1, CONST2 as MODULE1_CONST2`

    but then it really just becomes personal preference, as the number of characters saved on typing is almost negative (due to having a more
    complex import).

    but also the dot notation being used

    `from project.components import module1`

    where obviously the functions are invoked as `module1.function1` etc

    Firstly, the path followed will depend upon the starting position!

    Keep reading and you should come across a reference to 'hobgoblin of
    little minds'.

    What should we be concentrating on/about? If 'naming' is a great (?the greatest) challenge of programming, surely remembering the names of
    classes, methods, functions, modules, etc; follows...
    (and by implication, as you've illustrated, perhaps where they
    come-from), ie code is read more often than it is written, so think
    about comprehension rather than typing!


    If one (one's team!) frequently uses a module, then a jargon may
    develop. For example:

    import numpy as np
    ...
    a = np.arange(6)

    In which case, those au-fait with the jargon know that the "np." prefix
    tells them where "arange()" can be found - conversely, that if the IDE
    doesn't alert, that np/numpy must be (first) import-ed.

    However, there is a cognitive-load to 'translate' "np" into "numpy". Not
    much, but then over-load is not a single thought but the combination of 'everything'.

    The second option (OP) is very (laboriously) clear in mapping the source
    of each function or constant. By way of comparison then, the code may
    now appear cluttered, because there's so much text to read. There would
    be less if an abbreviation were used.

    The dev.tool in-use may also influence this decision. If hovering-over
    an identifier reveals source-information, what value the extra code? Intelligent completion also reduces relevance of 'number of characters
    saved on typing'.

    Accordingly, give frequently-used functions/modules the abbreviation
    treatment -but only if YOU feel it right. Otherwise, use a longer-form
    to improve readability/comprehension.

    THE answer will thus vary by library/package/module, by application, and
    by coder (jargon works best if 'all' use it/understand same).

    Side note: Using "...import identifier, ..." does not save storage-space
    over "import module" (the whole module is imported regardless, IIRC),
    however it does form an "interface" and thus recommend leaning into the "Interface Segregation Principle", or as our InfoSec brethren would say
    'the principle of least privilege'. Accordingly, prefer "from ... import
    ... as ...".

    --
    Regards,
    =dn

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Cameron Simpson@21:1/5 to Oliver Schinagl on Thu Aug 10 09:22:41 2023
    On 09Aug2023 12:30, Oliver Schinagl <oliver+python@schinagl.nl> wrote:
    Looking at a python projects code and repository layout, we see the
    following directory structure.


    /project/core
    /project/components/module1
    ...
    /project/components/moduleN
    /projects/util

    (this is far from complete, but enough to help paint a picture.

    Some modules import other modules, and I see (at the very least) two
    (kind of three?) different ways of doing so.

    `from project.components.module1 import function1, function2 as func,
    CONST1, CONST2 as CONST`

    or maybe even (which has as an advantage that it becomes clear which >namespace something belongs to

    `from project.components.module1 import function1, function2 as >module1_function2, CONST1, CONST2 as MODULE1_CONST2`

    but then it really just becomes personal preference, as the number of >characters saved on typing is almost negative (due to having a more
    complex import).


    but also the dot notation being used

    `from project.components import module1`

    where obviously the functions are invoked as `module1.function1` etc

    I hope the above is clear enough from an example.


    Simply put, which of the two would be considered cleaner and more pythonic?

    This is almost entirely a programmer preference thing.

    The Zen, as usual, offers guideance but not dictates.

    As you say, the module1.func form is very clear and has its beauty.
    Personally I lean towards the single name flavour, sometimes with a
    rename. FOr example, with os.path I often go:

    from os.path import join as joinpath

    and likewise for several other names from there, because the bare names
    are very generic (eg "join"). I see to be alone here. Many other people
    use:

    import os.path

    and use the full os.path.join in their code, which I find jarring and
    visually noisy.

    Now for a bit more thought, looking at PEP8, we notes about imports,
    but sadly PEP8 does not state which method is better/preferred. While >obviously in the end, it's always the maintainers choice in what they
    prefer, so are tabs vs spaces and PEP8 is opinionated about that too
    (in a good way, even though I'm not a fan :p).

    PEP8 is for the stdlib source code; that said it is also the basis for
    most Python coding styles in the wild.

    For me, the Zen's "readability counts" is the most important guideline;
    and to this end, your "module1.function" is an entirely reasonable
    response, particularly if you've got a lot of imports. But it does make
    for more verbose code, and that works against readability to a degree.

    This is why it's all subjective.

    Particular workplaces may mandate particular styles, but in your
    personal code? Do what seem best, and experience will cause that to
    evolve over time.

    Also, the style can depend on the code you're working on. For a small
    module with few imports the:

    from module1 import funcname

    can be a win because the code is short, and this short form of funcname
    is both clear and readable. But in a very long piece of code with many
    imports you might go for module1.funcname for clarity, particularly if
    funcname is generic or overlaps with another similar imported name.

    Cheers,
    Cameron Simpson <cs@cskk.id.au>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mats Wichmann@21:1/5 to dn via Python-list on Thu Aug 10 08:45:31 2023
    On 8/9/23 17:28, dn via Python-list wrote:

    Side note: Using "...import identifier, ..." does not save storage-space
    over "import module" (the whole module is imported regardless, IIRC),
    however it does form an "interface" and thus recommend leaning into the "Interface Segregation Principle", or as our InfoSec brethren would say
    'the principle of least privilege'. Accordingly, prefer "from ... import
    ... as ...".


    Attribute lookup has *some* cost. That is, finding "c" in the local
    namespace is going to be a little quicker than "b.c", where Python finds
    "b" in the local namespace and then finds its "c" attribute; that's then
    a little quicker than "a.b.c", etc.

    See all relevant commentary about premature optimisation, spending time optimising the wrong things, etc. but there *are* cases where it
    matters (c.f. a tight loop that will be run many many times)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)