Skip to content

1. Whitespaces and Newlines

Crowthebird edited this page Sep 8, 2021 · 3 revisions

This chapter is using the same representations of spaces and tabs in IFcoltransG's esoteric python guide (· for space and for tab).

Indentation and Operators

In PEP8, 4 spaces is the recommended indentation; no tabs are used as indentation; and spaces are put around operators (except for . and the unary operators). But anyways, to make esoteric code, we can choose not to do that.

Figure 1.1
if1:
↹ ↹ · ↹ ↹ · 'Hey, this works!'
· ↹ · ↹ ↹ · "But this doesn't work, because we replaced the first tab with a space"
↹ ↹ ↹ · ↹ · "This also doesn't work, because the order of spaces and tabs are switched"
Figure 1.2
while · 2:
· · · 'We can use three spaces'
· · · [
· · · · · ↹ · · · 'But in these hanging indents',
· 'We can use any indentation we want!',
· · · ↹ ↹ 'This works',
↹ · ↹ · ↹ · · 'This also does',
↹ · ↹ · ↹]
· · · if'a': # We can use absolutely no spaces at all between a keyword and a string
↹ · ↹ · ↹ · "This doesn't work, because the first three characters for indentation is a tab, a space, and a tab instead of three spaces"
· · · ↹ · ↹ · ↹ · 'But this works, because the first three characters for indentation are three spaces'
Figure 1.3
2. # There's a difference between this
2 · . # And this (this causes a `SyntaxError`); the explanation is if there's some one-line whitespace[s] between an object and a dot, the dot is 
interpreted as an attribute access operator instead
print(2 ↹ · ↹ . ↹ ↹ ↹ ↹ ↹ ↹ ↹ ↹ ↹ ↹ __class__)
<class 'int'>
print(2..__class__)
<class 'float'>

Space Places

· for space and for tab ends in this page from here
Confusing things can be made when spaces are put where they shouldn't be put. Like this example:

a = [0x_for x in (1, 2, 3)]
print(a)

Figure 1.4-1a

Output:

[15]

Figure 1.4-1b

Explanation to Figure 1.4-1

Python allows using underscores (_) to make a number more readable. With non-decimal numbers (prefixed with any of these: 0x, 0o, 0b), an underscore can be put after the number prefix, but a valid digit according to the number prefix has to be put after the underscore. This tells us that Python checks only for digits at the right side of an underscore. Figure 1.4-1a is therefore interpreted instead as:

a = [(0xf) or (x in (1, 2, 3))] # The hexadecimal number `0xf` is the same thing as the decimal number `15`
print(a)

Figure 1.4-2

Boolean short-circuiting (will be expanded in another chapter) results in only 0x_f (0xf or 15) being evaluated, so it doesn't matter whether x is defined or not.


Also confusing is the 'pistol operator' to assign the first and only element of a one-element list to a variable:

>>> list_obj = [42]
>>> the_meaning_of_life ,= list_obj
>>> the_meaning_of_life
42

Figure 1.5-1

Explanation to Figure 1.5-1

Python allows sortable ordered iterables as left operands for unpacking assignments, examples:

>>> (a, b) = [1, 2]
>>> a
1
>>> b
2
>>> [a, b] = [3, 4]
>>> a
3
>>> b
4

Figure 1.5-2.1a

>>> (a,) = 1
>>> a
1
>>> [a,] = 2
>>> a
2

Figure 1.5-2.1b

Comma-separated assignment—

a, b, c, d = 1, 2, 3, 4

Figure 1.5-2.2a

—just translates to—

(a, b, c, d) = 1, 2, 3, 4

Figure 1.5-2.2b

—so, Figure 1.5-1 is interpreted as:

>>> list_obj = [42]
>>> (the_meaning_of_life,) = list_obj
>>> the_meaning_of_life
42

Figure 1.5-2.3


There are also times when you can put a number right before a letter without using a space. For example:

>>> 2if 1else 3
2

Figure 1.6

There are exceptions though:

  • 0o because it's considered the prefix for an octal number
  • 0x because it's considered the prefix for a hexadecimal number
  • '0b' because it's considered the prefix for a binary number
  • '0x_else' because it's parsed as '0x_e lse', because e is part of the hexadecimal set
  • '0x_and' because it's parsed as '0x_a nd', because a is part of the hexadecimal set

Semicolons

Newlines can be replaced by semicolons (;), as in this code example:

print('a');print('b');True

Figure 1.7

But beware, scoping keywords (atleast that's what I call them, e.g. while, if, elif, else, for, def, and class) cannot come after a semicolon:

print('Not valid');while True:

Figure 1.8a

  File "<stdin>", line 1
    print('Not valid');while True:
                       ^^^^^
SyntaxError: invalid syntax

Figure 1.8b

While some, like import and from, are allowed:

>>> print('A');import math;from random import randint
A
>>> math
<module 'math' (built-in)>
>>> randint
<bound method Random.randint of <random.Random object at [hex address]>>

Figure 1.9

If you only have one level of indent within a block, you can use semicolons instead of newlines and indents. Note that any statement separated by semicolons in a one-line scope is part of the same scope.

>>> n = 5
>>> if n%4:print('Not a',end=' ');print('leap year')
Not a leap year
>>> 

Figure 1.10

#Other Newline Replacements

Often, newlines can be replaced instead with a BUILD_* expression with other expressions, like—

# Normal way
a = 3
b = 4
# Esoteric way
[a:=3, b:=4]

Figure 1.11

—or, replacing for loops—

# Normal way
d = {}
for i in range(5):
    d[i] = i+1
    print(i*3, d[i]*2)
# Esoteric way
[d:={},[[print(i*3,d[i]*2)for d[i]in[i+1]]for i in range(5)]] # Add `and None` in the REPL

Figure 1.12

—and the same can be done with while loops.

# Normal way
l = []
k = 500
while k != 4:
    l.append(k)
    if k%2:
        k += 2*k+1 # Short for k=3*k+1
    else:
        k //= 2

print(l)
# Esoteric way
print([k:=500,list((lib:=__import__('itertools')).takewhile(lambda x:x!=4,([k, k:=(3*k+1if k%2else k//2)][0]for _ in lib.count())))][1])

Figure 1.13

Clone this wiki locally