Python短码技巧汇总
* * * *
拉格朗日计划
* * * *
Python短码技巧汇总

1/7 三元运算符

可以把
a if condition else b
改写为
[b,a][condition]
两者的区别是,前者只有在condition为False时才会执行b,而后者会先执行a,b,再根据condition的值选择,因此需要保证a,b的有效性不受condition的影响。例子:
x=0 if i<len(a) else a[i]
x=[0,a[i]][i<len(a)]
前者总是能执行,后者在i的值大于或等于a的长度时会报错。类似的写法还有
condition and a or b
a*condition or b
两者都稍长,但都只要求a有效。

2/7 链式表达式


同一变量的多个限制条件,比如
a>0 and a<5
可以用更接近自然语言的方式写作
0<a<5
常数也可以使用同样的方式链接,例如
a>0 and a<5 and b>0 and b<5
可以直接写作:
0<a<5>b>0
in运算符也可以作链接,比如
a in b and b<c
可以写作
a in b<c
类似的还有多变量赋值,比如
i=0;j=0
可以改写为
i=j=0
变量赋值还可以嵌套到循环或控制条件、以及函数参数中,比如
while 1:x=1;...
x=1;f(x);g(x)
可以改写为
while x:=1:...
f(x:=1);g(x)

3/7 控制和循环


单分支的控制输出语句,比如
if f(x)>1:print(x)
可以改写为:
f(x)>1==print(x)
有时条件本身不支持链式比较,则可以写作如下稍长的版本:
f(x)>1and print(x)
特别地,涉及字符串显示与否的,比如
s="a" if n>3 else ""
可以改写为
s="a"*(n>3)
多分支的控制语句,比如
a=b if X else c
可以改写用列表来选择分支,比如
a=[c,b][X]
循环变量仅用于控制次数时,例如
for i in range(100):XXX
可以改写为(注意句末需有分号)
exec("XXX;"*100)

4/7 序列技巧


在列表末尾添加元素,比如
a.append(b)
可以改写为:
a+=[b]
字典的构建和查询,有时可用字符串或列表的查询代替,比如
{“a”:1,"b":2}["a"]
可以改写为
"ab".find("a")+1
序列构建时可以用*运算代替内建方法, 例如
list(L);tuple(T);set(S)
可以改写为
[*L];(*T);{*S}
类似地,用如下的dict comprehension构建字典可代替构建器
{i:ord(i+67)for i in range(26)}
而用map处理序列的代码则可以短于list comprehension,例如
[int(c) for c in s]
可以改写为
[*map(int,s)]
序列的解构可以直接用unpacking,例如
a,b,c=[1,2,3]
由不规则数字组成的、在标准ASCII可打印字符序号范围内的数字,可以用字符串数组代替,例如
[48,96,33][i]
可以改写为
b'0`!'[i]

5/7 语法细节


括号后的空格可以省去,保留字和数字之间的空格也可以省去,例如
a=[[3 for i in range(2)] for j in range(2)]
可以写作
a=[[3for i in range(2)]for j in range(2)]
一系列连续的单行表示式之间可以用分号代替换行,这在涉及到缩进时可以节省字符,例如
if XXX:
 YYY
 ZZZ
可以改写为
if XXX:YYY;ZZZ
需要被多次用到的函数可以直接用较短的变量引用之,例如
print(1);print(2);print(3)
可以改写为
p=print;p(1);p(2);p(3)
类似地,需要反复使用的包可以考虑用
from XXX import *
或者
import XXX,YYY as y
后者在使用YYY.method时可以用y.method代替。

6/7 输入输出


有些问题需要从sys.argv中读取参数,一般来说用for循环是最短的:
for a in sys.argv[1:]:XXX
不过在有些特殊情况下可以尝试下面的写法,若能在while后使用变量赋值x:=...,那么就有进一步缩短代码的可能
while 1:f(sys.argv.pop(1))
将列表中的每一项单独都打印在一行上的最短代码是(注意不要遗漏句末的逗号)
*map(print,L),
最后,格式化的输出可以尽量使用fstring。

7/7 代码压缩


以下代码可以压缩字符数,源码长度x超过50个字符时,可以将其压缩为50+(x-50)/2个字符
s="..."

def compress2t1(code):
  print(len(code))
  if len(code)%2!=0:
      code=code+" "
  print(code.encode().decode('u16',"ignore"))

compress2t1(s)
将需要压缩的源码拼成一个字符串,放在变量s中运行上面的代码,再将输出的乱码(注意输出的末尾可能有无法显示的字符,不要遗漏)粘贴到下面单引号中的...处,那么运行以下代码的效果就和运行源码相同。
exec(bytes('...','u16')[2:])
在拼接s时需要注意换行用\n表示,源码中的引号需要改为单引号或用\"转义,同理源码中的\n字符串需要转义为\\n。最后,源码中不能出现连续三个同样的字符(比如连续三个右括号),如有这样的情况可以在这些字符中间插入空格。

下面的代码则在源码长度x超过73个字符时,可以将其压缩为73+(x-73)/3个字符,因此源码长度超过144个字符时才会比上面的2:1压缩更优,但它只能处理ASCII码大于31的字符(也就是源码不能换行),因此使用的场合很有限:
t="..."

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

def compress3t1(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)

compress3t1(t)
用法与上文完全相同,将需要压缩的源码拼成一个字符串,放在变量t中运行上面的代码,再将输出的乱码粘贴到下面单引号中的...处运行
exec(bytes(ord(c)%i+32for c in'...'for i in b'efg'))