I'm new to Python and I'm playing a bit with some code snippets.
In my code I need to check for variable initialization and I was using this idiom:
if my_variable:
# execute some code
but reading some posts I found this other idiom is used:
if my_variable is not None:
# execute some code
Are they equivalent or is there some semantic difference?
Quoting Python documentation on boolean operations,
In the context of Boolean operations, and also when expressions are used by control flow statements, the following values are interpreted as false: False, None, numeric zero of all types, and empty strings and containers (including strings, tuples, lists, dictionaries, sets and frozensets). All other values are interpreted as true.
So, if my_variable will fail, if my_variable has any of the above mentioned falsy values where as the second one will fail only if my_variable is None. Normally the variables are initialized with None as a placeholder value and if it is not None at some point of time in the program then they will know that some other value has been assigned to it.
For example,
def print_name(name=None):
if name is not None:
print(name)
else:
print("Default name")
Here, the function print_name expects one argument. If the user provides it, then it may not be None, so we are printing the actual name passed by the user and if we don't pass anything, by default None will be assigned. Now, we check if name is not None to make sure that we are printing the actual name instead of the Default name.
Note: If you really want to know if your variable is defined or not, you might want to try this
try:
undefined_variable
except NameError as e:
# Do whatever you want if the variable is not defined yet in the program.
print(e)
No if 0 would be False where if my_variable was actually 0 then if my_variable is not None: would be True, it would be the same for any Falsey values.
In [10]: bool([])
Out[10]: False
In [11]: bool(0)
Out[11]: False
In [12]: bool({})
Out[12]: False
In [13]: [] is not None
Out[13]: True
In [14]: 0 is not None
Out[14]: True
It's worth noting that python variables cannot be uninitialised. Variables in python are created by assignment.
If you want to check for actual uninitialisation, you should check for (non) existence, by catching the NameError exception.
Taking an example of a null string i.e. '' which is not None
>>> a = ""
>>> if a:
... print (True)
...
>>> if a is not None:
... print (True)
...
True
>>>
And a boolean value
>>> a = False
>>> if a:
... print (True)
...
>>> if a is not None:
... print (True)
...
True
>>>
Thus they are not equivalent
Check if variable exists is in globals dict, if not initialize variable.
if 'ots' not in globals():
ots=0.0
Related
I came across a strange behaviour of python comparing a string with True/False.
I thought that python would print in the following:
if "Test" == True:
print("Hello1")
but it does not.
So I wrote some Test cases and I do not understand some of them.
if "Test" == True:
print("Hello1")
if "Test" == False:
print("Hello2")
#This I understand
if bool("Test") == True:
print("Hello3")
#This I understand too
if bool("") == False:
print("Hello4")
if "Test":
print("Hello5")
Output
>> Hello3
>> Hello4
>> Hello5
So I do not understand:
If Hello1 is not printed why is not Hello2 either?
Why does Hello5 get printed, is the cast to bool("Test") made implicit?
In the first two comparisons, you are checking whether the string "Test" has the same value as the object True or False. This is a value comparison.
If they have a different type, the comparison will return False. You can see this also when comparing lists, numbers etc.: [1]==1 (false), (1,)==[1] (false).
In the third and fourth comparisons, you are still doing a value comparison, but since both sides are of the same type (boolean), it will compare the values.
Hello5 is printed because it is not the null string "". You can see this by trying "Test" != None, which returns True.
While it is a comparison to None when it comes to most classes(None is Python's null value), Python's standard data types are compared to their "null" value, which are:
The empty string "" for strings,
[] for lists (similary () for tuples, {} for dictionaries),
0 for ints and floats,
just like a boolean comparison. Therefore it is not wrong to think of if expression as an implicit cast to if bool(expression).
What is going on under the hood is the evaluation of the __non-zero__(python2.x) or __bool__(python3.x) method of the class.
In the case of Hello1, Hello2 and Hello5 there is an object comparison and not boolean comparions.
That means that
the string-object "Test" is not the same as object True ("Hello1")
the string object "Test" is not the same as object False("Hello2")
but the string object "Test" is not None ("Hello5")
How do I refer to the null object in Python?
In Python, the 'null' object is the singleton None.
To check if something is None, use the is identity operator:
if foo is None:
...
None, Python's null?
There's no null in Python; instead there's None. As stated already, the most accurate way to test that something has been given None as a value is to use the is identity operator, which tests that two variables refer to the same object.
>>> foo is None
True
>>> foo = 'bar'
>>> foo is None
False
The basics
There is and can only be one None
None is the sole instance of the class NoneType and any further attempts at instantiating that class will return the same object, which makes None a singleton. Newcomers to Python often see error messages that mention NoneType and wonder what it is. It's my personal opinion that these messages could simply just mention None by name because, as we'll see shortly, None leaves little room to ambiguity. So if you see some TypeError message that mentions that NoneType can't do this or can't do that, just know that it's simply the one None that was being used in a way that it can't.
Also, None is a built-in constant. As soon as you start Python, it's available to use from everywhere, whether in module, class, or function. NoneType by contrast is not, you'd need to get a reference to it first by querying None for its class.
>>> NoneType
NameError: name 'NoneType' is not defined
>>> type(None)
NoneType
You can check None's uniqueness with Python's identity function id(). It returns the unique number assigned to an object, each object has one. If the id of two variables is the same, then they point in fact to the same object.
>>> NoneType = type(None)
>>> id(None)
10748000
>>> my_none = NoneType()
>>> id(my_none)
10748000
>>> another_none = NoneType()
>>> id(another_none)
10748000
>>> def function_that_does_nothing(): pass
>>> return_value = function_that_does_nothing()
>>> id(return_value)
10748000
None cannot be overwritten
In much older versions of Python (before 2.4) it was possible to reassign None, but not any more. Not even as a class attribute or in the confines of a function.
# In Python 2.7
>>> class SomeClass(object):
... def my_fnc(self):
... self.None = 'foo'
SyntaxError: cannot assign to None
>>> def my_fnc():
None = 'foo'
SyntaxError: cannot assign to None
# In Python 3.5
>>> class SomeClass:
... def my_fnc(self):
... self.None = 'foo'
SyntaxError: invalid syntax
>>> def my_fnc():
None = 'foo'
SyntaxError: cannot assign to keyword
It's therefore safe to assume that all None references are the same. There isn't any "custom" None.
To test for None use the is operator
When writing code you might be tempted to test for Noneness like this:
if value==None:
pass
Or to test for falsehood like this
if not value:
pass
You need to understand the implications and why it's often a good idea to be explicit.
Case 1: testing if a value is None
Why do
value is None
rather than
value==None
?
The first is equivalent to:
id(value)==id(None)
Whereas the expression value==None is in fact applied like this
value.__eq__(None)
If the value really is None then you'll get what you expected.
>>> nothing = function_that_does_nothing()
>>> nothing.__eq__(None)
True
In most common cases the outcome will be the same, but the __eq__() method opens a door that voids any guarantee of accuracy, since it can be overridden in a class to provide special behavior.
Consider this class.
>>> class Empty(object):
... def __eq__(self, other):
... return not other
So you try it on None and it works
>>> empty = Empty()
>>> empty==None
True
But then it also works on the empty string
>>> empty==''
True
And yet
>>> ''==None
False
>>> empty is None
False
Case 2: Using None as a boolean
The following two tests
if value:
# Do something
if not value:
# Do something
are in fact evaluated as
if bool(value):
# Do something
if not bool(value):
# Do something
None is a "falsey", meaning that if cast to a boolean it will return False and if applied the not operator it will return True. Note however that it's not a property unique to None. In addition to False itself, the property is shared by empty lists, tuples, sets, dicts, strings, as well as 0, and all objects from classes that implement the __bool__() magic method to return False.
>>> bool(None)
False
>>> not None
True
>>> bool([])
False
>>> not []
True
>>> class MyFalsey(object):
... def __bool__(self):
... return False
>>> f = MyFalsey()
>>> bool(f)
False
>>> not f
True
So when testing for variables in the following way, be extra aware of what you're including or excluding from the test:
def some_function(value=None):
if not value:
value = init_value()
In the above, did you mean to call init_value() when the value is set specifically to None, or did you mean that a value set to 0, or the empty string, or an empty list should also trigger the initialization? Like I said, be mindful. As it's often the case, in Python explicit is better than implicit.
None in practice
None used as a signal value
None has a special status in Python. It's a favorite baseline value because many algorithms treat it as an exceptional value. In such scenarios it can be used as a flag to signal that a condition requires some special handling (such as the setting of a default value).
You can assign None to the keyword arguments of a function and then explicitly test for it.
def my_function(value, param=None):
if param is None:
# Do something outrageous!
You can return it as the default when trying to get to an object's attribute and then explicitly test for it before doing something special.
value = getattr(some_obj, 'some_attribute', None)
if value is None:
# do something spectacular!
By default a dictionary's get() method returns None when trying to access a non-existing key:
>>> some_dict = {}
>>> value = some_dict.get('foo')
>>> value is None
True
If you were to try to access it by using the subscript notation a KeyError would be raised
>>> value = some_dict['foo']
KeyError: 'foo'
Likewise if you attempt to pop a non-existing item
>>> value = some_dict.pop('foo')
KeyError: 'foo'
which you can suppress with a default value that is usually set to None
value = some_dict.pop('foo', None)
if value is None:
# Booom!
None used as both a flag and valid value
The above described uses of None apply when it is not considered a valid value, but more like a signal to do something special. There are situations however where it sometimes matters to know where None came from because even though it's used as a signal it could also be part of the data.
When you query an object for its attribute with getattr(some_obj, 'attribute_name', None) getting back None doesn't tell you if the attribute you were trying to access was set to None or if it was altogether absent from the object. The same situation when accessing a key from a dictionary, like some_dict.get('some_key'), you don't know if some_dict['some_key'] is missing or if it's just set to None. If you need that information, the usual way to handle this is to directly attempt accessing the attribute or key from within a try/except construct:
try:
# Equivalent to getattr() without specifying a default
# value = getattr(some_obj, 'some_attribute')
value = some_obj.some_attribute
# Now you handle `None` the data here
if value is None:
# Do something here because the attribute was set to None
except AttributeError:
# We're now handling the exceptional situation from here.
# We could assign None as a default value if required.
value = None
# In addition, since we now know that some_obj doesn't have the
# attribute 'some_attribute' we could do something about that.
log_something(some_obj)
Similarly with dict:
try:
value = some_dict['some_key']
if value is None:
# Do something here because 'some_key' is set to None
except KeyError:
# Set a default
value = None
# And do something because 'some_key' was missing
# from the dict.
log_something(some_dict)
The above two examples show how to handle object and dictionary cases. What about functions? The same thing, but we use the double asterisks keyword argument to that end:
def my_function(**kwargs):
try:
value = kwargs['some_key']
if value is None:
# Do something because 'some_key' is explicitly
# set to None
except KeyError:
# We assign the default
value = None
# And since it's not coming from the caller.
log_something('did not receive "some_key"')
None used only as a valid value
If you find that your code is littered with the above try/except pattern simply to differentiate between None flags and None data, then just use another test value. There's a pattern where a value that falls outside the set of valid values is inserted as part of the data in a data structure and is used to control and test special conditions (e.g. boundaries, state, etc.). Such a value is called a sentinel and it can be used the way None is used as a signal. It's trivial to create a sentinel in Python.
undefined = object()
The undefined object above is unique and doesn't do much of anything that might be of interest to a program, it's thus an excellent replacement for None as a flag. Some caveats apply, more about that after the code.
With function
def my_function(value, param1=undefined, param2=undefined):
if param1 is undefined:
# We know nothing was passed to it, not even None
log_something('param1 was missing')
param1 = None
if param2 is undefined:
# We got nothing here either
log_something('param2 was missing')
param2 = None
With dict
value = some_dict.get('some_key', undefined)
if value is None:
log_something("'some_key' was set to None")
if value is undefined:
# We know that the dict didn't have 'some_key'
log_something("'some_key' was not set at all")
value = None
With an object
value = getattr(obj, 'some_attribute', undefined)
if value is None:
log_something("'obj.some_attribute' was set to None")
if value is undefined:
# We know that there's no obj.some_attribute
log_something("no 'some_attribute' set on obj")
value = None
As I mentioned earlier, custom sentinels come with some caveats. First, they're not keywords like None, so Python doesn't protect them. You can overwrite your undefined above at any time, anywhere in the module it's defined, so be careful how you expose and use them. Next, the instance returned by object() is not a singleton. If you make that call 10 times you get 10 different objects. Finally, usage of a sentinel is highly idiosyncratic. A sentinel is specific to the library it's used in and as such its scope should generally be limited to the library's internals. It shouldn't "leak" out. External code should only become aware of it, if their purpose is to extend or supplement the library's API.
It's not called null as in other languages, but None. There is always only one instance of this object, so you can check for equivalence with x is None (identity comparison) instead of x == None, if you want.
In Python, to represent the absence of a value, you can use the None value (types.NoneType.None) for objects and "" (or len() == 0) for strings. Therefore:
if yourObject is None: # if yourObject == None:
...
if yourString == "": # if yourString.len() == 0:
...
Regarding the difference between "==" and "is", testing for object identity using "==" should be sufficient. However, since the operation "is" is defined as the object identity operation, it is probably more correct to use it, rather than "==". Not sure if there is even a speed difference.
Anyway, you can have a look at:
Python Built-in Constants doc page.
Python Truth Value Testing doc page.
The above answers only will result True for None, but there is such a thing as float('nan'). You could use pandas isnull:
>>> import pandas as pd
>>> pd.isnull(None)
True
>>> pd.isnull(float('nan'))
True
>>> pd.isnull('abc')
False
>>>
Or without pandas:
>>> a = float('nan')
>>> (a != a) or (a == None)
True
>>> a = None
>>> (a != a) or (a == None)
True
>>>
The reason this works is because float('nan') != float('nan'):
>>> float('nan') == float('nan')
False
>>> float('nan') != float('nan')
True
>>>
Use f string for getting this solved.
year=None
year_val= 'null' if year is None else str(year)
print(f'{year_val}')
null
Simple function to tackle "empty" element in Python:
Code:
def is_empty(element) -> bool:
"""
Function to check if input `element` is empty.
Other than some special exclusions and inclusions,
this function returns boolean result of Falsy check.
"""
if (isinstance(element, int) or isinstance(element, float)) and element == 0:
# Exclude 0 and 0.0 from the Falsy set.
return False
elif isinstance(element, str) and len(element.strip()) == 0:
# Include string with one or more empty space(s) into Falsy set.
return True
elif isinstance(element, bool):
# Exclude False from the Falsy set.
return False
else:
# Falsy check.
return False if element else True
Test:
print("Is empty?\n")
print('"" -> ', is_empty(""))
print('" " -> ', is_empty(" "))
print('"A" -> ', is_empty("A"))
print('"a" -> ', is_empty("a"))
print('"0" -> ', is_empty("0"))
print("0 -> ", is_empty(0))
print("0.0 -> ", is_empty(0.0))
print("[] -> ", is_empty([]))
print("{} -> ", is_empty({}))
print("() -> ", is_empty(()))
print("[1, 2] -> ", is_empty([1, 2]))
print("(3, 5) -> ", is_empty((3, 5)))
print('{"a": 1} -> ', is_empty({"a": 1}))
print("None -> ", is_empty(None))
print("True -> ", is_empty(True))
print("False -> ", is_empty(False))
print("NaN -> ", is_empty(float("nan")))
print("range(0) -> ", is_empty(range(0)))
Output:
Is empty?
"" -> True
" " -> True
"A" -> False
"a" -> False
"0" -> False
0 -> False
0.0 -> False
[] -> True
{} -> True
() -> True
[1, 2] -> False
(3, 5) -> False
{"a": 1} -> False
None -> True
True -> False
False -> False
NaN -> False
range(0) -> True
Per Truth value testing, 'None' directly tests as FALSE, so the simplest expression will suffice:
if not foo:
Null is a special object type like:
>>>type(None)
<class 'NoneType'>
You can check if an object is in class 'NoneType':
>>>variable = None
>>>variable is None
True
More information is available at Python Docs
In function definitions, one can define a boolean default argument's values as argument=None or argument=False.
An example from pandas concat:
def concat(
objs,
axis=0,
join="outer",
join_axes=None,
ignore_index=False,
keys=None,
levels=None,
names=None,
verify_integrity=False,
sort=None,
copy=True,
):
While both usages can be found, why would one be using one over the other?
Is there any PEP on this?
True and False are specific bool values. Use default False when you have a bool field and you want the default to be False.Don't use False as a value for a non-bool field.
None is used as a generic placeholder when the value will be set later. It can be typed as Optional[T], which is equivalent to Union[T, None].
You might be thinking of None and False as similar because they're both "falsy" (bool(x) returns False), but the same is true of several other values, [] () {} 0 0.0, etc and we don't use them like None either.
In your example, True/False are used where the field takes a boolean value. None is used where the field takes an Optional[List]. (The exception is sort: Optional[bool], which is being used temporarily as an ad-hoc compatibility tool for a deprecated behavior.)
True and False are boolean values, so in this context None can make sense if you have a boolean variable that can be in an 'unknown' state. Strictly spoken, such a variable would not be boolean, but in reality, this can be quite handy.
For example:
# In the beginning, we simply don't know if it's true or not
is_cat_alive = None
# But later we will determine the status
is_cat_alive = check_cat_in(box)
Note that if you are using a boolean like this, then if is_cat_alive will be false when is_cat_alive is either False or None which makes sense but might not be what you want to know. So to explicitly check for a dead cat, you would have to use if is_cat_alive == False.
None is similar to a null value if you know anything about those. Someone might say it's equal to 0, but None means that it is nothing. Say age = None for example. age is a variable, but it is equal to nothing. False is an actual value. True and False can be used as an indicator such as
def name_check(name)
if name == "Brittany":
return True
else:
return False
Calling that function with "Brittany" as the name parameter would return True which could be used in other conditions as well. Hope this helped! Good luck!
Look at the following code:
my_string = ''
my_other_string = None
my_final_string = 'I am a string'
If you were using these within python in something like an if statement they would actually equal the following:
my_string = False
my_other_string = None
my_final_string = True
Hope that helps
It is standard convention to use if foo is None rather than if foo == None to test if a value is specifically None.
If you want to determine whether a value is exactly True (not just a true-like value), is there any reason to use if foo == True rather than if foo is True? Does this vary between implementations such as CPython (2.x and 3.x), Jython, PyPy, etc.?
Example: say True is used as a singleton value that you want to differentiate from the value 'bar', or any other true-like value:
if foo is True: # vs foo == True
...
elif foo == 'bar':
...
Is there a case where using if foo is True would yield different results from if foo == True?
NOTE: I am aware of Python booleans - if x:, vs if x == True, vs if x is True. However, it only addresses whether if foo, if foo == True, or if foo is True should generally be used to determine whether foo has a true-like value.
UPDATE: According to PEP 285 § Specification:
The values False and True will be singletons, like None.
If you want to determine whether a value is exactly True (not just a true-like value), is there any reason to use if foo == True rather than if foo is True?
If you want to make sure that foo really is a boolean and of value True, use the is operator.
Otherwise, if the type of foo implements its own __eq__() that returns a true-ish value when comparing to True, you might end up with an unexpected result.
As a rule of thumb, you should always use is with the built-in constants True, False and None.
Does this vary between implementations such as CPython (2.x and 3.x), Jython, PyPy, etc.?
In theory, is will be faster than == since the latter must honor types' custom __eq__ implementations, while is can directly compare object identities (e.g., memory addresses).
I don't know the source code of the various Python implementations by heart, but I assume that most of them can optimize that by using some internal flags for the existence of magic methods, so I suspect that you won't notice the speed difference in practice.
Never use is True in combination with numpy (and derivatives such as pandas):
In[1]: import numpy as np
In[2]: a = np.array([1, 2]).any()
In[4]: a is True
Out[4]: False
In[5]: a == True
Out[5]: True
This was unexpected to me as:
In[3]: a
Out[3]: True
I guess the explanation is given by:
In[6]: type(a)
Out[6]: numpy.bool_
is there any reason to use if foo == True rather than if foo is True?"
>>> d = True
>>> d is True
True
>>> d = 1
>>> d is True
False
>>> d == True
True
>>> d = 2
>>> d == True
False
Note that bool is a subclass of int, and that True has the integer value 1. To answer your question, if you want to check that some variable "is exactly True", you have to use the identity operator is. But that's really not pythonic... May I ask what's your real use case - IOW : why do you want to make a difference between True, 1 or any 'truth' value ?
edit: regarding:
Is there a case where using if foo is True would yield different results from if foo == True?
there is a case, and it's this:
In [24]: 1 is True
Out[24]: False
In [25]: 1 == True
Out[25]: True
additionally, if you're looking to use a singleton as a sentinel value, you can just create an object:
sentinel_time = object()
def f(snth):
if snth is sentinel_time:
print 'got em!'
f(sentinel_time)
you don't want to use if var == True:, you really want if var:.
imagine you have a list. you don't care if a list is "True" or not, you just want to know whether or not it's empty. so...
l = ['snth']
if l:
print l
check out this post for what evaluates to False: Evaluation of boolean expressions in Python
Using foo is True instead of foo == True (or just foo) if is most of the time not what you want.
I have seen foo is True used for checking that the parameter foo really was a boolean.
It contradicts python's duck-typing philosophy (you should in general not check for types. A function acting differently with True than with other truthy values is counter-intuitive for a programmer who assumes duck-typing)
Even if you want to check for types, it is better to do it explicity like :
def myFunction(foo):
if not isinstance(foo, bool):
raise ValueError("foo should be a boolean")
>>> myFunction(1)
Exception: ValueError "foo should be a boolean"
For several reasons:
Bool is the only type where the is operator will be equivalent to isinstance(a, bool) and a. The reason for that is the fact that True and False are singletons. In other words, this works because of a poorly known feature of python (especially when some tutorials teach you that True and False are just aliases for 1 and 0).
If you use isinstance and the programmer was not aware that your function did not accept truthy-values, or if they are using numpy and forgot to cast their numpy-boolean to a python-boolean, they will know what they did wrong, and will be able to debug.
Compare with
def myFunction(foo):
if foo is True:
doSomething()
else:
doSomethingElse()
In this case, myFunction(1) not only does not raise an exception, but probably does the opposite of what it was expected to do. This makes for a hard to find bug in case someone was using a numpy boolean for example.
When should you use is True then ?
EDIT: this is bad practice, starting from 3.9, python raises a warning when you try to use is to compare with a literal. See # JayDadhania's comment below. In conclusion is should not be used to compare to literals, only to check the equality of memory address.
Just don't use it. If you need to check for type, use isinstance.
Old paragraph:
Basically, use it only as a shorthand for isinstance(foo, bool) and foo
The only case I see is when you explicitely want to check if a value is true, and you will also check if the value is another truthy value later on. Examples include:
if foo is True:
doSomething()
elif foo is False:
doSomethingElse()
elif foo is 1: #EDIT: raises a warning, use == 1 instead
doYetSomethingElse()
else:
doSomethingElseEntirely()
Here's a test that allows you to see the difference between the 3 forms of testing for True:
for test in ([], [1], 0, 1, 2):
print repr(test), 'T' if test else 'F', 'T' if test == True else 'F', 'T' if test is True else 'F'
[] F F F
[1] T F F
0 F F F
1 T T F
2 T F F
As you can see there are cases where all of them deliver different results.
Most of the time, you should not care about a detail like this. Either you already know that foo is a boolean (and you can thus use if foo), or you know that foo is something else (in which case there's no need to test). If you don't know the types of your variables, you may want to refactor your code.
But if you really need to be sure it is exactly True and nothing else, use is. Using == will give you 1 == True.
I'm new to Python and I am a bit confused with the way Python treats an empty object.
Consider this code piece;
a = {}
if a:
print "a is alive!"
else:
print "a is NOT alive!"
if not a:
print "NOT a!"
else:
print "a!"
if a is None:
print "a is None!"
else:
print "a is NOT None!"
I get the following output for this code piece.
a is NOT alive!
NOT a!
a is NOT None!
Edit::
I am under the assumption that an object initialized by {} is a Valid Object. Why doesn't Python treat it that way? and why do I get diff output for diff If conditions?
Edit 2::
In C++, when I say
Object obj;
if (obj){
}
It will enter the IF block if obj is NOT NULL(regardless if it is garbage value or whatever)
But the same thing when I translate to python.
a = {} #This is a valid object
if a:
# Doesn't work!
Why? and I read Python evaluates {} as False. Why is that?
Empy dict/sets/lists etc are evaluated as false. None is its own type, with only one possible value.
https://docs.python.org/2.4/lib/truth.html Tells you what is evaluated as true and false
I see nothing weird in your output.
Let's go step-by-step:
a is dictionary, more specifically a dictionary object;
a is a dictionary, but it's empty, so its truth value is False
Therefore:
The first if, since a is False, prints the else statement and that's right;
The second if, since not a evaluates to True because a is False, prints the if part and that's right too.
Last, but not least a is not a None object, but a dict object, so it's right too that the else part is taken and printed.
It is a valid python object, but it is empty, so it is treated as a False, the same goes for lists [] or numbers 0. You do have a dict, but it is not a None object.
with
a = {}
you are creating an dictionary object which is not NoneType you can
check the class of your object with
type(a)
which will give you:
type dict
if not a:
will return False if a has already any members and True if a is just and empty list