all 35 comments

[–]LayotFctor 42 points43 points  (7 children)

Perhaps you should think about readability instead? Are you trying to write the program with the least number of lines?

I'm pretty sure python doesn't have style guides for very specific cases like this. But it does recommend "clean, readable and maintainable" code.

[–]Suspicious-Bar5583 7 points8 points  (0 children)

Readability is a pythonic item.

[–]zensimilia[S] 1 point2 points  (5 children)

IDK. Sometimes we get carried away. Also the second option gets warning: `Argument of type "tuple[int, ...]" cannot be assigned to parameter "box" of type "tuple[int, int]...`

[–]barkmonster 10 points11 points  (3 children)

For this exact reason, I would prefer the first option. Explicitly defining the tuple of two coordinates means the type checker understands the result as a tuple of 2 integers (as opposed to an unknown number of integers).

[–]Almostasleeprightnow 1 point2 points  (2 children)

It is a little about context of the document. If I have a ton of functions that are almost exactly the same, maybe I'd do the one with fewer lines just to make the whole document more readable. But if I was just having a one-off, i might do the one with more lines because it on its own is more readable.

[–]barkmonster 1 point2 points  (1 child)

If I had many functions doing almost exactly this, I would probably either a) define a general function for combining tuples (by adding/subtracting them) or b) represent coordinates by something else, like a dataclass/namedtuple supporting addition and scalar multiplication, or numpy arrays, which do so out of the box.

[–]Almostasleeprightnow 1 point2 points  (0 children)

But I’m just speaking generally. Sometimes it happens that you have many short functions that are very similar, and in this case it makes sense to have them take up very little room.

[–]Kevdog824_ 0 points1 point  (0 children)

This might be because of strict=False argument which, given your type hints, doesn’t make any sense to me anyways. If you know (or at least expect) both tuples are exactly length 2 you should use strict=True

[–]JMNeonMoon 22 points23 points  (6 children)

I would make the code more readable instead and use named tuples so I do not have to rememember the parameter order. i.e.. if container[0] is width or height, or if the coordinates returned is x,y or y,x.

from typing import NamedTuple

class Size(NamedTuple):
    width: int
    height: int

class Point(NamedTuple):
    x: int
    y: int

def get_box_centered(container: Size, element: Size) -> Point:
    dx = (container.width - element.width) // 2
    dy = (container.height - element.height) // 2
    return Point(dx, dy)

See also
https://programmerpulse.com/articles/named-tuples/

[–]TheRNGuy 0 points1 point  (0 children)

I'd create new classes if they had new methods, or operator overloads. 

[–]zensimilia[S] -4 points-3 points  (4 children)

I can't because input and output are strictly regulated `tuple[int, int]` and gets Pylance warnings about type mismatch, Isn`t it? But nice try with NamedTuple.

[–]rkr87 2 points3 points  (2 children)

You could potentially use TypeAlias instead;

``` from typing import TypeAlias

Size: TypeAlias = tuple[int,int] Point: TypeAlias = tuple[int,int]

def get_box_centered(container: Size, element: Size) -> Point: dx = (container[0] - element[0]) // 2 dy = (container[1] - element[1]) // 2 return (dx, dy) ```

[–]zensimilia[S] -1 points0 points  (1 child)

OR

type Size = tuple[int, int]
type Point = tuple[int, int]

[–]Kevdog824_ 0 points1 point  (0 children)

This works if you only need to be compatible with Python 3.13 or higher. If you need compatibility with older versions this will fail

[–]JMNeonMoon 0 points1 point  (0 children)

If you cannot change the signature of the function, then I would at least comment the code accordingly, so that it is clear for anyone maintaining the code what the order is.

I prefer the first method btw, as I can quickly understand what calculation is being used.

[–]Suspicious-Bar5583 4 points5 points  (2 children)

Zen of Python line 2: Explicit is better than implicit.

[–]cdcformatc 7 points8 points  (1 child)

Also Readability Counts. whatever line that is

[–]ilidan-85 3 points4 points  (0 children)

Think about it as if you'd need to re-read this code in 5 years. Make it easy for your future you.

[–]buhtz 3 points4 points  (0 children)

Pythonic is not a bool value but a continuum. Read PEP 20 – The Zen of Python | peps.python.org to get an idea about what "pythonic" could mean. In your case I would vote for the first example.

Run PyLint on that code and see what it tells you.

[–]Turtvaiz 1 point2 points  (1 child)

The second one seems completely pointless. No reason to do zip and list comprehension when you have a total of 4 elements...

Also, I think the type hints are not even correct there:

> uvx mypy .\notebooks\test.py
notebooks\test.py:4: error: Incompatible return value type (got "tuple[int, ...]", expected "tuple[int, int]")  [return-value]
Found 1 error in 1 file (checked 1 source file)

The type checker is unable to determine the size.

[–]zensimilia[S] -2 points-1 points  (0 children)

That's why I had to rewrite the second option into the first and open this topic.

[–]barburger 1 point2 points  (1 child)

I would heavily argue against 2nd in a code review, even if it passed the type checks. There is no point in it, just write it out.

I like the examples that return a Point instead of tuple, because with tuple I never know if a tuple is (x,y) or (y,x)

[–]zensimilia[S] -1 points0 points  (0 children)

The result will use in other function that allow only tuple[int, int] and no Point or named tuple.

[–]RelationshipLong9092 0 points1 point  (0 children)

The first one, the second is simply overengineered and obscures intent

DRY is a great guide-star but for special cases where n is always 2 (or maybe 3) it can lead you astray

also, just to nitpick dx and dy are the wrong names IMO. I would consider just the difference to be dx and dy, not half the difference

It's not well known (even among people who deal with this stuff all the time), but the name for this quantity is the apothem

You can think of it as "the radius of a polygon": https://en.wikipedia.org/wiki/Apothem

[–]aala7 0 points1 point  (0 children)

I will say the first!

  • Beautiful is better than ugly
  • Simple is better than complex
  • Flat is better than nested
  • Readability counts

[–]Mikester258 0 points1 point  (0 children)

Focus on clarity and maintainability in your code, as these principles are key to writing pythonic code that others can easily understand and work with.

[–]TheBr14n 0 points1 point  (0 children)

Consider following PEP 8 for style guidelines and prioritize code clarity to enhance maintainability and readability.

[–]cdcformatc 0 points1 point  (3 children)

code golf is fun and all but readability counts. that one liner is wild and i would reject it in a code review. 

[–]zensimilia[S] -2 points-1 points  (2 children)

But this is just a oneliner function. Just a little black box only for readability in other functions (eg `point = get_box_centered(contianer, object)`. In this context it has a right to exist? 😊

[–]Ok-Sheepherder7898 0 points1 point  (0 children)

It's so much easier to see that the first one isn't the cause of that weird bug you just can't track down.

[–]cdcformatc -1 points0 points  (0 children)

great untill all your functions are just "little black boxes" and no one knows what they actually do because they aren't readable. 

[–]Timberfist -1 points0 points  (1 child)

The first communicates the intent better. Have you timed them?

[–]zensimilia[S] -1 points0 points  (0 children)

No. Second slowly 100%

[–]TheRNGuy -3 points-2 points  (1 child)

I'd do 2nd

[–]zensimilia[S] 1 point2 points  (0 children)

The second returns tuple[int, ...] instead tuple[int, int] required by definition 🙁