W = print('Weird')
nW = print('Not Weird')
These lines will cause printing to happen immediately, and assign the value that print returns (the special value None) to each of W and nW.
if n == range(2, 5):
nW
This (similarly for the other lines that just have W or nW by themselves) will not cause print to be called. They will check what the current value of nW (or W) is, and then ignore that value - in much the same way that if you wrote 1 + 2 on a line by itself in the middle of your program, it would compute 3 and then do nothing with that 3 result.
If you want to "alias" a function call like this, you have some options: you can define your own simpler function explicitly, for example
def W():
print('Weird')
or you can use e.g. functools.partial from the standard library to "bind" parameters to the call:
import functools
W = functools.partial(print, 'Weird')
# Now W is a special object that represents calling `print` and passing `'Weird'` to it
But in any of these cases you will still need to call the function: W(), not W.
The more reasonable approach is to give names to the messages you want to print, and then print them at the appropriate times:
W = 'Weird'
# and later you can just
print(W)
nWandWto invoke the respective calls toprintwhich you 'bound' above. That's not how it works. What you're actually doing is printing two strings ("Weird" and "Not Weird"), and then binding the return value ofprint(which isNone) to those variables. You'll have to use separateprints on the actual lines where you want printing to take place. You can't just bind entire executable Python snippets to a variable, and then expect it to work.