Domanda

I've been wondering whether should I write

lambda pixel: (0,0,0) if pixel == (0,0,0) else (255,255,255),

or

lambda pixel: pixel if pixel == (0,0,0) else (255,255,255),

This may be a silly question, but I want to get better at forming questions.

And I wonder if there may be any caveats I don't know of.

I personally consider latter to be more readable.

È stato utile?

Soluzione

Definitely the latter.

If you use the former, and someone edits just one of the literal values, now it's broken:

lambda pixel: (0,0,0) if pixel == (0,0,8) else (255,255,255),

I introduced a bug in the above. I edited the tuple used in the condition, but didn't edit the first tuple. This is hard to spot by casual inspection.

Consider this:

big_long_expression if something == big_long_expression else whatever

The reader has to mentally evaluate big_long_expression each time, and compare whether they are actually the same or not. If you instead use the latter form, now it's clear: you either return the expression unchanged, or in the else case you return something else.

It's the same reason I prefer the += operator:

my_class.a.b[i+3] = my_class.a.b[i+3] + 2

compare with:

my_class.a.b[i+3] += 2

Both do the same thing, but the second one is so much easier to understand.

I always prefer the syntax that expresses your intentions more clearly. I think your second format makes it most clear: check a condition, and when it's true leave the value alone, else return a special value.

EDIT: in comments directly under the question, @DSM made an excellent point. The first expression also has a subtle property: it always returns a tuple, even when the expression being compared wasn't a tuple. As long as pixel is any class that can be compared to a tuple, either expression will work. But if you want your lambda to consistently return a tuple from either case of the ternary, then the first form is preferable.

Here are two rewrites of your code, for your consideration.

The first:

ORIGIN = (0, 0, 0)
lambda pixel: ORIGIN if pixel == ORIGIN else (255,255,255),

Now at least there is a single point to edit to change the origin. This is the way I'd recommend if you do want to always return a tuple.

The second:

lambda pixel: pixel if pixel == (0, 0, 0) else type(pixel)((255, 255, 255))

This gets the type of pixel, then calls that type to construct a new object. This assumes that the pixel is of a class that is not only comparable to a tuple, but that if you pass a tuple to the class it will use that to build a new instance of the class based on the tuple.

An example of this in use:

f = lambda pixel: pixel if pixel == (0, 0, 0) else type(pixel)((255, 255, 255))

class T(tuple):
    """
    Make a class that acts exactly like a tuple.
    """
    pass

x = T((1,2,3))
assert type(x) == T
assert type(x) == type(f(x))

x = (1, 2, 3)
assert type(x) == tuple
assert type(x) == type(f(x))

Altri suggerimenti

Well, this definitely isn't a syntax issue; it's just a matter of style. (In a more complex expression, it would be a different story… but then in a more complex expression, often you should have been breaking it up into simpler expressions anyway…)

Personally, I think they're both perfectly readable. The first one emphasizes that (0, 0, 0) is the default value more clearly, while the second one emphasizes the purpose of what you're returning more clearly, and it repeats a simpler thing (pixel instead of (0, 0, 0)). But these are both such trivial benefits that I don't think it matters that much.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top