Ctrl+P again to print, arrows/tab to navigate results, enter to confirm

    Packers

    A 2:1 packer

    This is Python's fabled 2:1 packer, for your convenience. For chars scoring, it can reduce code by a factor of two.

    Spoiler

    Decompressor

    Note: the overhead is 25 characters so your uncompressed code must be over 50 characters to get a benefit.

    exec(bytes('...','u16')[2:])
    

    Compressor

    Note: the code size must be divisible by 2 for the compressor to work. If not, adding a trailing space should suffice.

    print('CODE'.encode().decode('u16'))
    

    A 3:1 packer

    There also exists a 3:1 packer, but it only works with bytes above ASCII character 31.

    Spoiler

    Decompressor

    Note: the overhead is 49 characters so your uncompressed code must be over 73 characters to get a benefit over unpacked code and over 144 characters to get a benefit over the 2:1 packer (assuming a compression ratio of strictly 3:1).

    exec(bytes(ord(c)%i+32for c in'...'for i in b'efg'))
    

    Compressor

    def crt(a, n):
    	s, p = 0, 1
    	for x in n:
    		p *= x
    	for x, y in zip(a, n):
    		q = p // y
    		s += q * x * pow(q, -1, y)
    	return s % p
    
    code = '''CODE'''
    
    compressed = ''
    for i in range(0, len(code), 3):
    	a = [ord(c) - 32 for c in code[i:i+3]]
    	compressed += chr(crt(a, [101, 102, 103]))
    
    print(compressed)
    

    Boolean access

    Instead of:

    a if condition else b
    

    Use:

    [b,a][condition]
    

    Only works if a and b can both be safely evaluated, regardless of which one is picked.

    Shorter if...else

    As seen above, [b,a][condition] may be used instead of an if...else, but only if a and b cause no side effects. A laxer alternative is

    condition and a or b
    

    It saves one byte, but has the restriction that a must be truthy. There is also

    a*condition or b
    

    but with this one, a must be truthy, in addition to not causing any side effects.

    Chained comparison

    Use comparison chaining to save bytes on and:

    0<a and a<5 and 0<b and b<5
    # vs
    0<a<5 and 0<b<5
    # vs
    0<a<5>b>0
    

    You can chain in as well. This is less useful, but nice to know:

    a in b and b<c
    # vs
    a in b<c
    

    The next tip on short circuiting also utilizes comparison chaining.

    Short circuiting ==

    if f(x)>1:print(x)
    

    can be rewritten implementing the short circuit:

    f(x)>1==print(x)
    

    Multiple assignment

    If you need to assign multiple variables to the same value, it's better to combine them into one statement:

    a=1;b=1
    # vs
    a=b=1
    

    However, assigning to an object (such as a list) may result in some weird behavior, since all the variables share the same instance:

    a=b=[]
    a.append(1)
    print(a,b)
    >>> [1] [1]
    

    Appending to a list

    By creating a tuple, you can do += instead of append, saving 6 bytes:

    a.append(b)
    # vs
    a+=b,
    

    This can be be used with slicing to replace insert:

    a.insert(i,b)
    # vs
    a[:i]+=b,
    

    Walrus operator

    Python 3.8 introduced assignment expressions (A.K.A. the walrus operator). Its main use in golf is to allow assigning and using a variable at the same time:

    while 1:x=1;...
    # vs
    while x:=1:...
    
    x=1;f(x);g(x)
    # vs
    f(x:=1);g(x)
    

    Using byte strings as integer lists

    Python's byte strings can act like integer lists, sometimes:

    [48,96,33]
    # vs
    b'0`!'
    

    This can, for example, help with iterating or indexing a list:

    # Iteration
    for x in 48,96,33:
    for x in b'0`!':
    
    # Indexing
    [48,96,33][i]
    b'0`!'[i]
    

    Unpacking assignment

    Here is a list of tricks for potentially saving bytes on variable assignment. All are based on the powerful feature of iterable unpacking:

    t=a;a=b;b=t
    # vs
    a,b=b,a
    
    a=L[0];b=L[1];c=L[2]
    # vs
    a,b,c=L
    
    x=1;L=[1]
    # vs
    x,=L=[1]
    
    x=1;L=[]
    # vs
    x,*L=1,
    
    L=list(range(5))
    # vs
    *L,=range(5)
    
    x=L.pop(0)
    # vs
    x,*L=L
    
    x=L.pop()
    # vs
    *L,x=L
    

    Print list separated by \n

    AFAIK, the shortest way to print a list (or any iterator) with each element on a new line is:

    *map(print,L),
    

    Some longer alternatives include:

    [*map(print,L)]
    *_,=map(print,L)
    0in map(print,L)
    print(*L,sep='\n')
    for x in L:print(x)
    

    Splat on iterables

    Splat makes functions like list, set, and tuple a lot shorter:

    list(L)  -> [*L]
    tuple(T) -> (*T,)
    set(S)   -> {*S}
    

    It saves on concatenation as well:

    [1,2]+[L]+[3,4] -> [1,2,*L,3,4]
    (1,2)+L+(3,4)   -> (1,2,*L,3,4)
    

    A sys.argv trick

    Some of the holes on code.golf require input through ARGV. Nine out of ten times,

    for a in sys.argv[1:]:f(a)
    

    will be the shortest option. But let's take a look at this alternative:

    while 1:f(sys.argv.pop(1))
    

    Both of these contain the same amount of bytes, except the second one is less useful as it is not stored in a variable. The advantage with the second option is that the while 1 might have room to fit a walrus operator (as discussed in this tip). Under these specific circumstances, it can usually save one byte.

    Removing multiple imports

    Suppose you want functions x and y from modules a and b. It may just so happen that a itself imports b, wherein you can write

    import a
    a.x()
    a.b.y()
    

    This will work so long as a does not del b in its own code. If "b" appears in a.__all__, you can also use

    from a import*
    x()
    b.y()
    

    Bytes formatting

    The % operator works on bytes too - b'%r'%x and b'%d'%x are really useful.

    Hash

    In Python 3, the hash function is still deterministic on tuples - so you can get an easy hash like this: hash((*b'%r'%x,)).