First, let's put your code(s) in a function
def func(): # line 1
x = 'hello joe' # line 2
if x == 'hello': # line 4
print('nope') # line 5
else: # line 6
if x == 'hello joe': # line 7
print(x) # line 8
now disassemble with that (using CPython 3.4):
import dis
dis.dis(func)
we get:
2 0 LOAD_CONST 1 ('hello joe')
3 STORE_FAST 0 (x)
4 6 LOAD_FAST 0 (x)
9 LOAD_CONST 2 ('hello')
12 COMPARE_OP 2 (==)
15 POP_JUMP_IF_FALSE 31
5 18 LOAD_GLOBAL 0 (print)
21 LOAD_CONST 3 ('nope')
24 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
27 POP_TOP
28 JUMP_FORWARD 25 (to 56)
7 >> 31 LOAD_FAST 0 (x)
34 LOAD_CONST 1 ('hello joe')
37 COMPARE_OP 2 (==)
40 POP_JUMP_IF_FALSE 56
8 43 LOAD_GLOBAL 0 (print)
46 LOAD_FAST 0 (x)
49 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
52 POP_TOP
53 JUMP_FORWARD 0 (to 56)
>> 56 LOAD_CONST 0 (None)
59 RETURN_VALUE
now change to elif:
2 0 LOAD_CONST 1 ('hello joe')
3 STORE_FAST 0 (x)
4 6 LOAD_FAST 0 (x)
9 LOAD_CONST 2 ('hello')
12 COMPARE_OP 2 (==)
15 POP_JUMP_IF_FALSE 31
5 18 LOAD_GLOBAL 0 (print)
21 LOAD_CONST 3 ('nope')
24 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
27 POP_TOP
28 JUMP_FORWARD 25 (to 56)
6 >> 31 LOAD_FAST 0 (x)
34 LOAD_CONST 1 ('hello joe')
37 COMPARE_OP 2 (==)
40 POP_JUMP_IF_FALSE 56
7 43 LOAD_GLOBAL 0 (print)
46 LOAD_FAST 0 (x)
49 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
52 POP_TOP
53 JUMP_FORWARD 0 (to 56)
>> 56 LOAD_CONST 0 (None)
59 RETURN_VALUE
The only differences are the line numbers.
else: # line 6
if x == 'hello joe': # line 7
becomes (and shifts the rest as well)
elif x == 'hello joe': # line 6
There are as many instructions in both versions. The else and if keywords in that case seem to have been converted exactly the same way as elif. Not guaranteed in all implementations. Personally I'd stick to the shortest code (elif) because it's more "meaningful" and if a code should be faster, it would probably be that one.