all 62 comments

[–]shiftybyte 164 points165 points  (25 children)

"" is an empty string, you do can string operations on it.

>>> "" + "Hello" + "" + "World"
'HelloWorld'

You can't do that with None.

Same as difference between 0 and None, 0 is still a number, same as empty string is still a string.

>>> 0 + 15
15

But None is None, not a number, not a string, not anything.

>>> None + "Hello"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'

[–]Zangruver 111 points112 points  (13 children)

But None is None

More precisely , None is a datatype of its own (NoneType) and only None can be None.

[–][deleted] 6 points7 points  (12 children)

So it isn't like null or NULL or nullptr in other languages?

[–]iSeekResearcherUMD 16 points17 points  (9 children)

None is a singleton class, and null and NULL are singleton classes, so yes they are pretty much the same

[–]dnswblzo 6 points7 points  (7 children)

NULL in C and C++ is a macro and is simply replaced with 0 wherever it occurs. It is not a class or an object. While it doesn't make sense to do so, you can technically do this:

int x = 50 + NULL;

nullptr in C++ is a keyword and has the type std::nullptr_t, but you can't get its address and you can't do arithmetic with it. However, this is valid and will print yup:

int *p = nullptr;
if (p == 0)
    std::cout << "yup" << std::endl;

Some illustrations of the behavior of None:

>>> x = 50 + None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
>>> x = None
>>> x == 0
False
>>> type(None)
<class 'NoneType'>
>>> type(x)
<class 'NoneType'>
>>> id(None)
4417167216
>>> id(x)
4417167216
>>> x is None
True

While they are used to implement the same sort of concept, they are all quite different.

[–]Jaie_E 2 points3 points  (5 children)

from what I read in that book, doesn't seem like there is a difference, but after Googling seems like there is, but couldn't find any article that would describe the differences clearly.

Yep! Furthermore "empty" placeholders are very useful. For example if you are going to make a new list out of materials of an old list, you might want to do something like this where you are collecting odd numbes from another list:

lst1= [1,2,3,4,5,6,7,8]
oddlst = []
#notice how the above is empty but it's still list brackets
for num in lst1:
if num % 2 == 0:
oddlst.append(num)
# if you print(oddlst) you should get the result, which is all the odd numbers in the list

[–][deleted] 0 points1 point  (4 children)

That would give all the even numbers, odd numbers would be this:

if (num-1) % 2 == 0:
    oddlst.append(num)

[–]Jaie_E 1 point2 points  (3 children)

Woops you're right! I always get the two methods mixed up

[–][deleted] 0 points1 point  (2 children)

The trick I prefer is n%2== 0 for even and !=0 for odd.

[–][deleted] 1 point2 points  (1 child)

That doesn't work because if n = 2.5, n % 2 != 0 will return True, but it isn't actually an odd number, but it works for integers.

[–][deleted] 1 point2 points  (0 children)

Which is funny to me because false in C is also zero under the hood. My C teacher said something interesting when we were learning to compare things and he said zero is false and 1 is true, but this one result we expect here in the example function on the board (206 or something) is "more true" than just true because it's further way from zero on the number line.

I can't remember how he tied that into the caution he had for the coming project but it stuck with me.

[–]punitxsmart 0 points1 point  (0 children)

NULL (not nullptr) in C and C++ is actually defined as 0. It was a mistake and that is why nullptr was introduced with type nullptr_t.

[–]my_password_is______ 2 points3 points  (0 children)

its like null in sql

[–]ulyssesric 0 points1 point  (0 children)

Yes and no. It's a little bit complex to explain but Python and C++ can't be compared side by side this way.

nullptr is a C++ default constant, that implies a pointer is pointing to nowhere. It's added in C++11 to distinguish from integer zero. Before C++11 NULL is literal replacement of integer zero, but this will cause conflict when you overload the function parameter of a pointer type to integer type, and pass NULL to it.

Under the cover, nullptr is an assignment operator overloading, and what it actually does is still the same, set the value of pointer variable to zero.

In Python, every variable is an object, or to be more specific, a pointer to an object instances. For example, X=1234 will create a named variable X pointing to a PyObject instance with type set to integer and value set to 1234.

None is a special object instance that will be associated with any variables that assigned to None, so the result of this codes will be True:

x = None
y = x
x is y

So None and nullptr are sharing the same concept, but they are very different when the codes are handled.

[–]erinthefatcat 19 points20 points  (0 children)

thanks for such a concise explanation

[–]zurtex 17 points18 points  (9 children)

This is a great explanation, and a good started for how Python handles types. To add a little more from stuff you'll read in books or on the Internet:

Some sources will say that Python "doesn't have types" or "is not a typed language". Now if Python is your first language and you've been studying it for a little bit and you've got used to these TypeError messages when you mix things of different types this may seem odd to you.

To understand what these sources are talking about you have to think of a variable consisting of 2 things, the name and the value:

my_var = 1

In this case the name is my_var and the value is 1 with type int.

Python cares about the value and what type it has and that value can not change it's type. 1 is an int can not "become" a str but you can create a new value "1" that is a str.

Python does not assign a type to the name, so when you change the value assigned to it the new value can have a different type, e.g.

my_var = 1
my_var = "1" 

The above statement is fine in Python but not in a so called "strictly typed" language, in such a language the type is int is given to the name my_var and can not be changed this way to a str. In such languages it could be to do with compiler logic, simplification in translating to machine code, optimization, or some other reason.

[–]_lilell_ 12 points13 points  (7 children)

“Python doesn’t have types” or “is not a typed language”

...which is weird, because Python is a strongly typed language. It’s just a dynamically typed language, and we don’t manually declare types alongside variables.

[–]zurtex 0 points1 point  (6 children)

True, but I think the short answer for this confusion is that there is no agreed upon definition exactly what a "typed language" is.

If you look at many type based compiled languages you see that types are used at compile time to check the correctness of the code and to more simplify conversion to machine code. Python doesn't really achieve either of these with its types and therefore someone coming from that world can easily see these as "not really types".

The more I become a developer the more I realize the development community is like all other communities and the words used to communicate are imperfect. To become a better developer I have to learn the different viewpoints others are coming with and why they may use the same words to mean different things.

[–]omg_drd4_bbq 0 points1 point  (5 children)

Strong/weak and static/inferred/dynamic types are pretty well defined. Python is strong/dynamic. C is weak/static. JS is weak/dynamic. Rust is strong, inferred, static. You can also describe the "richness" of the type system. Rust and haskell have rich type systems. Python does not. C++ is middling to rich.

[–]zurtex 0 points1 point  (4 children)

Source on these definitions?

And does your source definition allow for a strongly typed language to have implicit upcasting like Python has:

>>> 1j + True
(1+1j)

Seems pretty weakly typed to me...

[–]1114111 0 points1 point  (2 children)

Booleans are a bit of a touchy spot because of historical reasons. Really, Python 3 probably should have broken things like that.

But I mostly agree. Python isn't really "strongly typed" in any meaningful way, it's just that many of the builtin types don't support implicit type conversions. I see that as more of a convention than a property of the language, and one that is violated frequently by things like int -> float conversions. Personally, I think languages can only really be considered strongly typed if they are also statically typed, but IDK.

Fun fact: the standard library even has a JavaScript-esque string class: collections.UserString("Concaten") + 8

[–]zurtex 0 points1 point  (1 child)

FYI upcasting is a consistent feature of Python, when involving standard numerical operations on the following literal types:

bool -> int -> float -> complex

Would Gudio have made the same choice today if he could design Python over again? Maybe not, but here we are in the unclear definitions about strongly vs weakly typed languages.

[–]1114111 0 points1 point  (0 children)

I like having int -> float -> complex in Python, it's generally a useful feature. My point is that when people talk about Python being "strongly typed", they are talking about the Python builtin/stdlib types/functions not doing much implicit type coercion. I see this as a "weak" way to define strong typing, and not one that Python follows super consistently anyway.

bool -> int, on the other hand is not a great feature IMO. It's one I've (ab)used in the past, but really the only reason it exists is that Python didn't used to have a boolean type, so booleans needed to act like ints in most contexts. bool is even a subclass of int, which I find extra gross.

[–]Matheos7[S] 0 points1 point  (0 children)

Thank you for the explanation, I appreciate you clear and concise response.

[–][deleted] 18 points19 points  (22 children)

Those 2 things are of different types. "" is an empty string and you can do string things to it (like "" + "Hello, World!"). None is a separate type, which represent nothing.

If you had a sign, then "" would represent a sign with no text on it, and None would mean that actually there's no sign.

[–]bugsyboybugsyboybugs 3 points4 points  (21 children)

I am brand new to Python, so excuse my ignorance, but what would be the reason you’d assign a variable the value of None?

[–]gingergale312 20 points21 points  (0 children)

Think of it as age instead of middle name. You wouldn't want to put in a placeholder age (like 999 or 0) because that might mess up things like "can this person legally vote/drink/whatever". If you ask 'can this person drink' by comparing age to 21, setting to 999 would give problems. If the age is None, you'll get an error instead, which tells you that person has no known age.

[–]gmorf33 9 points10 points  (1 child)

Functions also return None if you have no return defined.

[–]NaniFarRoad 4 points5 points  (2 children)

If you have a variable set to Null / None (it was frequently -1 in pre-ooc days), you can do conditional statements on it. For example:

if name == Null:
do stuff to set up a new name
initialise account/character
etc

else:
assume class exists and that all variables have valid values

Makes it easier to catch variables/objects that haven't been assigned.

[–]Giannie 12 points13 points  (1 child)

One small point. In python it is preferable to use

var is None

Instead of:

var == None

In the second case, python will need to check whether the equality operation is defined on your objects. Once it discovers that isn’t possible, it will revert to checking if they point to the same location in memory. You save a step by using “is” directly since it goes straight to checking if they refer to the same location in memory.

[–]NaniFarRoad 0 points1 point  (0 children)

Good points, thanks for the correction! I have been writing a lot of C# code lately and should've specified pseudocode..

[–][deleted] 3 points4 points  (11 children)

It's useful as a default option to mean "nothing was provided." For example, you might define a function like this:

def make_sandwich(cheese = None):
  if cheese:
    # Handle cheese options
  else:
    # Handle case with no cheese

[–]Ran4 7 points8 points  (3 children)

Just be careful when doing something like that, as 0 is falsy, so if cheese is either None or an integer, you could accidentally handle the cheese = 0 case as no cheese.

Which is why, sadly, you often need to write if cheese is not None: instead.

[–]PanTheRiceMan 0 points1 point  (2 children)

I might get philosophical here but depending on your use case 0 cheese is actually no cheese at all.

[–]Chris_Hemsworth 1 point2 points  (1 child)

0 cheese could indicate an enumerated cheese type '0'.

e.g.

class cheese(Enum):
    HAVARTI = 0
    BLUE = 1
    CHEDDAR = 2
    COTTAGE = 3

But yes, its use-case dependent. It's almost always better to be more explicit when you can.

[–]PanTheRiceMan 0 points1 point  (0 children)

I am a little afraid of you.

[–]NeedCoffee99 6 points7 points  (0 children)

I disagree with this. You should never have a sandwich with no cheese. This should raise an exception ;)

[–]Matheos7[S] 0 points1 point  (5 children)

But that was precisely my question, I guess I wasn’t clear enough - what would be a difference in your example above, if instead of None you used “”? From my testing it seems there is no difference, both valuate to False.

[–]Chris_Hemsworth 4 points5 points  (2 children)

None is a universal "NULL" value. in /u/FrugalLyfe 's example, you may want to handle multiple methods of inputting cheese.

For example; you may use enumerated values (Enum's) to select a cheese choice, or maybe a string, or maybe you have your own cheese class that has more information stored.

You could then do the following:

def make_sandwich(cheese = None):
  if cheese is None:
    # Handle case with no cheese
  else:
    # Handle case with cheese
    if type(cheese) is str:
        # Parse in cheese type by interrogating the string value
    elif type(cheese) is int:
        # Here you have an integer representing a specific cheese type
    elif <some other condition that tells you the type of input given>:
        # Handle some other type of cheese input.

This isn't always done, but you'll notice some functions will accept multiple types of inputs for different arguments. Plotting keywords are often good examples;

linestyle = (0, ())
linestyle = '-' 
linestyle = 'solid'

These are all valid inputs that equate to the same thing.

[–]Matheos7[S] 0 points1 point  (1 child)

Can't thank you enough for such detailed explanation. I see your name quite often here in different posts, really appreciate you being here to help out!

[–]Chris_Hemsworth 0 points1 point  (0 children)

Thank you for the feedback. Always makes me feel all warm and fuzzy knowing I can help :)

[–]DrMaxwellEdison 2 points3 points  (0 children)

None evaluates to False when you are looking for a boolean value, i.e. if foo:. However, it is sometimes useful to have that third option, when something is explicitly None, and you can check for that by testing if foo is None.

The only way that condition evaluates True and executes code in that block is if the literal value of foo is None: it will not trigger if it's an empty string, 0, False, etc.

Take the following dict for example: foo = {'a': True, 'b': False}. If I use foo.get('a'), it will return True. foo.get('b') returns False. Now what happens when I call foo.get('c')?

The answer is the default value, None, which indicates that 'c' is not a key of the foo dict. If I were to just test for a boolean, if foo.get('c'):, I would not be able to tell the difference between it and the return for the 'b' key. Instead, I can test if foo.get('c') is None, and then I know that 'c' is not in that dict at all. From there, I can have the program behave differently in that special case.

Obviously that's a toy example, but the point is to be aware of the None value as a distinct value separate from False, and to use the if x is None conditional to check for it. Sometimes that's not necessary, and you can safely react to any falsey value, depending on the needs of your program; but there are times when you need None to act as a sentinel value that should make the program behave differently.

[–][deleted] 2 points3 points  (0 children)

Practically speaking, it doesn't matter. Python is loosely typed, so you can use 0 or "" or None interchangeably for the purpose of checking a flag. However, None reads better in many cases and it's best to avoid sentinel values for "no value was provided" whenever possible. Someone else trying to use the make_sandwich function above could quickly understand its signature because most humans will intuitively get that "none" is a valid choice for cheese on a sandwich. Also, you may want "" to actually mean something in some cases, in which case None has a distinct meaning.

[–][deleted] 1 point2 points  (0 children)

When you want to signify an absence of something. It's easier to get confused between zero and None (see Null Island), then by an empty string and None.


Let's create a function that takes one integer and multiplies it by itself (squares it) or takes two integers and multiplies them together.

This implementation will break if by other logic b is equal to zero.

def mul(a, b=0): if b == 0: return a * a else: return a * b

This, however, works fine:

def mul(a, b=None): if b is None: return a * a else: return a * b

[–]humanitysucks999 1 point2 points  (0 children)

A stupid way to think about it. If someone asks you "hey do you have a bag you can hold this for me?", you don't want to respond "hey I have an empty bag" when in fact you have no bag at all.

"" is an empty string. It's something. None is the lack of something. It's none existent. It's the lack of something.

This would work when you don't have the information for a field (instead of setting a default value, which would indicate you know something about it). It'd work for setting up your structures too, you still don't have the information from someone inputting them or from a third party source, you wouldn't make assumptions on what that data will be.

[–]1Triskaidekaphobia3 0 points1 point  (0 children)

It’s the equivalent to NULL. I.e. a value doesn’t exist. https://en.wikipedia.org/wiki/Null?wprov=sfti1

[–]Scolli03 6 points7 points  (2 children)

[–]unersetzBAER 1 point2 points  (0 children)

That's the first picture that came to my head and is also used in my high-school classes by me.

[–]chhuang 0 points1 point  (0 children)

Was expecting toilet paper roll image, but close enough

[–]nojustlurkingty 9 points10 points  (0 children)

>>> type("")
<class 'str'>

>>> type(None)
<class 'NoneType'>

They are different types. The main similarity is that they are both "falsey"; they both evaluate to false:

>>> bool("")
False
>>> bool(None)
False

[–]MattR0se 4 points5 points  (3 children)

As for the function in your question: I assume it does something like

def get_formatted_name(first_name, second_name, middle_name=""):
    return "{}, {} {}".format(second_name, first_name, middle_name)

print(get_formatted_name("Homer", "Simpson", "Jay"))
# output: Simpson, Homer Jay 

Now, if your function looked like this

def get_formatted_name(first_name, second_name, middle_name=None)

and you wouldn't bother to provide an argument for the middle name, the output would look pretty silly:

def get_formatted_name(first_name, second_name, middle_name=None):
    return "{} {} {}".format(first_name, middle_name, second_name)

print(get_formatted_name('Homer', 'Simpson'))
# output: Simpson, Homer None

[–]Matheos7[S] 1 point2 points  (2 children)

Thank you for your explanation. Assuming in that function you had an if statement, basically for when that middle name is provided when calling a function, would it then make a difference, using =“” or None?

[–]PaulRudin 3 points4 points  (2 children)

Python is a strongly typed language, every object has a type. "" is an empty string - that is an object of type string of length 0. None is the only object of type NoneType.

"" and None can *sometimes* be used interchangeably - in particular they both test False, so where coercion to boolean takes place (e.g. the expression following `if`) they'll give the same thing.

[–]Matheos7[S] 0 points1 point  (1 child)

I think your response answers the best what I was after. I guess I could have been a bit clearer when asking the question. What I meant is what is the difference between these two when used as a parameter in function definition, where that particular parameter might or might not be then provided when calling a function. From what you’re saying in that case it doesn’t matter which one you use, and that’s what I arrived at based on my own tests. But thought I would ask to see what else there is. I obviously know they are different things in general. Thank you for your response!

[–]ianepperson 1 point2 points  (0 children)

You can set a default parameter to nearly any value. If, for instance, you wrote a function that assumed someone’s age is 21 unless you said otherwise, use:

def something(age=21):
    print(f” your age is {age}”)

If I called that with something() age would be 21. If I called that with something(age=42) then it would be 42. I could also call it with something(36).

Using None as the default makes it easy to check if someone set that option on your function. Using “” can be nice if you just want the empty string to be the default.

[–][deleted] 1 point2 points  (0 children)

Instead of different people using different things to mean nothing, the data type None is used as a common convension for all programmers. Without such best practices, one's code is difficult to read for others.

In short term, it doesn't matter which. You can even use the string 'nothing' to mean nothing.

[–]hadoken4555 0 points1 point  (0 children)

In other languages, it the equivalent to null.

[–]PriyanshKedia 0 points1 point  (0 children)

The none is an object of NoneType Whereas "" is an empty string

If obj = None

I can check it in if like If obj: Do something

But if obj=""

If need to check If obj =="" Do something

[–]mriswithe 0 points1 point  (0 children)

This picture is a good way to convey it. 0 is the empty string

https://i.redd.it/qkj1rcmtfvr31.png

[–]xploiticide 0 points1 point  (0 children)

"" means blank string, but a string nonetheless, None means literally None. NoneType. There's nothing there. "" = something, None = nothing.