sten
sten

Reputation: 7486

Detecting None-Scalar and None-tuple?

Just of curiosity I'm trying to detect None-Scalar and None-tuple with short-code to use in lambda ... i.e. using isinstance is cumbersome.

So possible values are :

None
(None, 0)
Value
(Value, 1)

so far not (not x or not x[0]) , works for the cases 1,2,4, but not the 3rd

xx = lambda x: not( not x or not x[0] )
xx(None)  => False
xx((None,0)) => False
xx(5)

TypeError: 'int' object has no attribute '__getitem__'

Upvotes: 2

Views: 169

Answers (2)

gilch
gilch

Reputation: 11681

You'll want some form of is not None to distinguish None from a 0 value.

There's really no way to distinguish a pair from a number by coercing to a boolean. The non-empty tuples will always be truthy, the None will always be falsy, but the number could be either true or false (when 0).

You can do it in one line without using isinstance() by using type() instead:

lambda x: {tuple: x}.get(type(x), [x])[0] is not None
# or
lambda x: (x[0] if type(x) is tuple else x) is not None

The advantage of isinstance() is that it works with subclasses.


Or with a standard library import:

from unittest.mock import ANY

lambda x: x not in [None, (None, ANY)]

This kind of thing is easy in languages that have pattern matching. (Which Python might be getting soon, see PEP 622.) I'm (ab)using the ANY mock to achieve a similar effect.

Be careful with ANY. It works by overriding .__eq__() and .__ne__(), so it's not necessarily reflexive.

Upvotes: 2

Patrick Artner
Patrick Artner

Reputation: 51683

Using truthy-testing for that is dangerous because it misfires for

''     # empty string
0      # integer zero
[]     # empty list
False  # boolean False
set()  # empty other iterable
dict()
# etc.

because they all are Falsy - like None.

# Problem:
xx(0) # False
xx( ([],42) ) # False

See pythong.org truth value testing

Create a function and use it instead/inside your lambda:

data = [None,  (None, 0),  "g",  ("g", 1), 0, ([],42)]

def test(thing):
    """Returns 'False' for None and iterable inputs that have
    None as first value."""
    if thing is None: 
        return False
    try:
        a, *b = thing
        if a is None: 
            return False
    except: # purposefully not catching specific
        pass
    return True

for d in data:
    print(d, test(d))

Output:

None False
(None, 0) False
g True
('g', 1) True 
0 True
([], 42) True

Upvotes: 0

Related Questions