用Python实现彩色终端文本效果
我正在尝试在我的Python程序中实现文字颜色循环,也就是说,我想让每个输入的字符颜色都能循环变化(还有其他效果)。到目前为止,我的进展是拼凑出来的,参考了一个ansi颜色的示例,欢迎大家提出改进建议。
我也知道一些库,但从来没有使用过,比如:termcolor、colorama和curses。
在这个拼凑的过程中,我发现一些属性(比如反向闪烁等)没有起作用,而且效果也不完美,主要是因为我对以下这些代码行理解得不够透彻:
cmd.append(format % (colours[tmpword]+fgoffset))
c=format % attrs[tmpword] if tmpword in attrs else None
如果有人能稍微解释一下,我会很感激。这段代码可以运行并且有一些效果,但还不够好。我修改了代码,这样就不需要把颜色命令和字符串分开,可以直接包含在一起。
#!/usr/bin/env python
'''
"arg" is a string or None
if "arg" is None : the terminal is reset to his default values.
if "arg" is a string it must contain "sep" separated values.
if args are found in globals "attrs" or "colors", or start with "@" \
they are interpreted as ANSI commands else they are output as text.
@* commands:
@x;y : go to xy
@ : go to 1;1
@@ : clear screen and go to 1;1
@[colour] : set foreground colour
^[colour] : set background colour
examples:
echo('@red') : set red as the foreground color
echo('@red ^blue') : red on blue
echo('@red @blink') : blinking red
echo() : restore terminal default values
echo('@reverse') : swap default colors
echo('^cyan @blue reverse') : blue on cyan <=> echo('blue cyan)
echo('@red @reverse') : a way to set up the background only
echo('@red @reverse @blink') : you can specify any combinaison of \
attributes in any order with or without colors
echo('@blink Python') : output a blinking 'Python'
echo('@@ hello') : clear the screen and print 'hello' at 1;1
colours:
{'blue': 4, 'grey': 0, 'yellow': 3, 'green': 2, 'cyan': 6, 'magenta': 5, 'white': 7, 'red': 1}
'''
'''
Set ANSI Terminal Color and Attributes.
'''
from sys import stdout
import random
import sys
import time
esc = '%s['%chr(27)
reset = '%s0m'%esc
format = '1;%dm'
fgoffset, bgoffset = 30, 40
for k, v in dict(
attrs = 'none bold faint italic underline blink fast reverse concealed',
colours = 'grey red green yellow blue magenta cyan white'
).items(): globals()[k]=dict((s,i) for i,s in enumerate(v.split()))
bpoints = ( " [*] ", " [!] ", )
def echo(arg=None, sep=' ', end='\n', rndcase=True, txtspeed=0.03, bnum=0):
cmd, txt = [reset], []
if arg:
if bnum != 0:
sys.stdout.write(bpoints[bnum-1])
# split the line up into 'sep' seperated values - arglist
arglist=arg.split(sep)
# cycle through arglist - word seperated list
for word in arglist:
if word.startswith('@'):
### First check for a colour command next if deals with position ###
# go through each fg and bg colour
tmpword = word[1:]
if tmpword in colours:
cmd.append(format % (colours[tmpword]+fgoffset))
c=format % attrs[tmpword] if tmpword in attrs else None
if c and c not in cmd:
cmd.append(c)
stdout.write(esc.join(cmd))
continue
# positioning (starts with @)
word=word[1:]
if word=='@':
cmd.append('2J')
cmd.append('H')
stdout.write(esc.join(cmd))
continue
else:
cmd.append('%sH'%word)
stdout.write(esc.join(cmd))
continue
if word.startswith('^'):
### First check for a colour command next if deals with position ###
# go through each fg and bg colour
tmpword = word[1:]
if tmpword in colours:
cmd.append(format % (colours[tmpword]+bgoffset))
c=format % attrs[tmpword] if tmpword in attrs else None
if c and c not in cmd:
cmd.append(c)
stdout.write(esc.join(cmd))
continue
else:
for x in word:
if rndcase:
# thankyou mark!
if random.randint(0,1):
x = x.upper()
else:
x = x.lower()
stdout.write(x)
stdout.flush()
time.sleep(txtspeed)
stdout.write(' ')
time.sleep(txtspeed)
if txt and end: txt[-1]+=end
stdout.write(esc.join(cmd)+sep.join(txt))
if __name__ == '__main__':
echo('@@') # clear screen
#echo('@reverse') # attrs are ahem not working
print 'default colors at 1;1 on a cleared screen'
echo('@red hello this is red')
echo('@blue this is blue @red i can ^blue change @yellow blah @cyan the colours in ^default the text string')
print
echo()
echo('default')
echo('@cyan ^blue cyan blue')
print
echo()
echo('@cyan this text has a bullet point',bnum=1)
print
echo('@yellow this yellow text has another bullet point',bnum=2)
print
echo('@blue this blue text has a bullet point and no random case',bnum=1,rndcase=False)
print
echo('@red this red text has no bullet point, no random case and no typing effect',txtspeed=0,bnum=0,rndcase=False)
# echo('@blue ^cyan blue cyan')
#echo('@red @reverse red reverse')
# echo('yellow red yellow on red 1')
# echo('yellow,red,yellow on red 2', sep=',')
# print 'yellow on red 3'
# for bg in colours:
# echo(bg.title().center(8), sep='.', end='')
# for fg in colours:
# att=[fg, bg]
# if fg==bg: att.append('blink')
# att.append(fg.center(8))
# echo(','.join(att), sep=',', end='')
#for att in attrs:
# echo('%s,%s' % (att, att.title().center(10)), sep=',', end='')
# print
from time import sleep, strftime, gmtime
colist='@grey @blue @cyan @white @cyan @blue'.split()
while True:
try:
for c in colist:
sleep(.1)
echo('%s @28;33 hit ctrl-c to quit' % c,txtspeed=0)
echo('%s @29;33 hit ctrl-c to quit' % c,rndcase=False,txtspeed=0)
#echo('@yellow @6;66 %s' % strftime('%H:%M:%S', gmtime()))
except KeyboardInterrupt:
break
except:
raise
echo('@10;1')
print
还得提一下,我完全不知道这一行代码是干嘛的 :) - 我看到它把颜色放进了一个字典对象里,但具体是怎么做到的我就搞不懂了。我还不习惯这种Python的语法。
for k, v in dict(
attrs = 'none bold faint italic underline blink fast reverse concealed',
colours = 'grey red green yellow blue magenta cyan white'
).items(): globals()[k]=dict((s,i) for i,s in enumerate(v.split()))
1 个回答
这段代码看起来有点复杂,但我们来专注于你提到的那几行:
cmd.append(format % (colours[tmpword]+fgoffset))
这行代码的意思是,把一个字符串添加到名为 cmd
的列表里。这个字符串是通过把 format
变量里的内容和 (colours[tmpword]+fgoffset)
的结果结合起来生成的。这里的 colours[tmpword]
是从颜色表中取出一个颜色代码,而 fgoffset
则是一个数字,它会和这个颜色代码相加。
而 format
字符串里有 '1;%dm'
,这表示它期待一个整数来替换里面的 "%d"。这个格式的用法是从 C 语言的 printf 函数继承来的。至于你的颜色表 colours
,它的构建方式有点复杂,我建议直接在 "globals" 中设置它的值。假设每种颜色的值都是正确的,那么把它和 fgoffset
相加可能会生成一些超出范围的颜色代码(我记得是超过 15 的)。
接下来是你有疑问的第二行:
c=format % attrs[tmpword] if tmpword in attrs else None
这里的 if
是 Python 的三元运算符,类似于 C 语言里的 expr?:val1: val2
。
这段代码可以理解为:
如果 tmpword
在 attrs
中:
c = format % attrs[tmpword]
否则:
c = format % None
需要注意的是,这个三元运算符的优先级比 %
运算符低。也许你会更喜欢这样写:
c= (format % attrs[tmpword]) if tmpword in attrs else ''
这样会更清晰一些。