import time
import sys
def animate_string(input_string, delay_second=0.01):
current_string = ""
for target_char in input_string:
if target_char == '\n':
# Print a newline and reset current_string
print()
current_string = ""
continue
current_char = 'a'
while current_char != target_char:
a_code = ord(current_char)
# previous string + current letter
sys.stdout.write('\r' + current_string + current_char)
sys.stdout.flush()
time.sleep(delay_second)
# Move to the next char (used ASCII)
current_char = chr(ord(current_char) + 1)
# Handle bound issue
if current_char == '{': # After 'z'
current_char = 'A'
elif current_char == '[': # After 'Z'
current_char = target_char
# Add the target character to the current string
current_string += target_char
# Print the updated current string
sys.stdout.write('\r' + current_string)
sys.stdout.flush()
# Print a newline at the end to finish the output cleanly
print()
animate_string("fjweiofjewoifjweofjewofjewffewjfiowjfwaifjewipfajeehgpwth235r2fjweiofjewoifjweofjewofjewffewjfiowjfwaifjewipfajeehgpwth235r2fjweiofjewoifjweofjewofjewffewjfiowjfwaifjewipfajeehgpwth235r2fjweiofjewoifjweofjewofjewffewjfiowjfwaifjewipfajeehgpwth235r2", 0.0002)
这是我的代码。我正在以一种很酷的方式打印字符串。它适用于少量字符,但当字符串变得足够长以到达终端末尾时,它开始一遍又一遍地打印同一行。为什么会发生这种情况以及如何解决这个问题?
正如评论之一所指出的,回车符 (
"\r"
) 将光标移动到当前行的开头。 当文本需要显示的终端列多于终端宽度时,文本将跨多行显示。 因此,当向终端写入“长”字符串时,光标将在与输出开始的行不同的行上结束。 任何后续的 "\r"
然后将光标移动到该 final 行的开头。
假设终端宽度为 50 列,并且您尝试为 60 个字符的字符串设置动画
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345678"
。
当
target_char
包含第 41 个字符 (O
) 时,这是第一个后续 sys.stdout.write
调用的结果(带行号):
1 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNa
^ ^ cursor is here
cursor will be returned here by "\r"
当
target_char
包含第 51 个字符 (Y
) 时,这是第一个后续 sys.stdout.write
调用的结果(带行号):
1 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
2 a
^^ cursor is here
cursor will be returned here by "\r"
下一次调用
sys.stdout.write
(当current_char
包含b
时)会将光标移动到第二行的开头并仅覆盖a
,结果是:
1 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
2 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
3 b
^^ cursor is here
cursor will be returned here by "\r"
从现在开始,每个
sys.stdout.write
调用都会在输出中添加一个新行。
一种解决方案是在“循环”动画期间仅覆盖单个字符,而不是整行。 使用
"\b"
将光标向后移动一列最初似乎可以工作,但由于某种原因,不会跨行“向后换行”。 但是,使用 ANSI 控制序列来设置光标位置确实有效:
import os
import sys
import time
def animate_string(input_string, delay_second=0.001):
stdout = sys.stdout
if not stdout.isatty():
# Standard output is not a terminal; just output the string
stdout.write(f"{input_string}\n")
return
# Get the size of the terminal to control line breaks correctly
cols, rows = os.get_terminal_size(stdout.fileno())
# Assume we are at the start of a new line
current_col = 1
for target_char in input_string:
if target_char == "\n":
# Write a newline and reset `current_col`
stdout.write(target_char)
current_col = 1
continue
# Escape sequence that sets the horizontal position of the cursor to `current_col`
# Setting this to "\b" does not work for long strings
undo_seq = f"\x1b[{current_col}G"
current_char = "a"
time.sleep(delay_second)
stdout.write(current_char)
stdout.flush()
while current_char != target_char:
# Move to the next char (used ASCII)
current_char = chr(ord(current_char) + 1)
# Handle bound issue
if current_char == "{": # After 'z'
current_char = "A"
elif current_char == "[": # After 'Z'
current_char = target_char
time.sleep(delay_second)
# "Undo" sequence followed by current letter
stdout.write(f"{undo_seq}{current_char}")
stdout.flush()
if current_col >= cols:
# Detect line wrapping performed by the terminal
current_col = 1
else:
current_col += 1
# Print a newline at the end to finish the output cleanly
stdout.write("\n")
os.get_terminal_size
来获取终端宽度。 我们跟踪 current_col
中的当前列,并使用 CHA
ANSI 控制序列 在“循环”动画期间重置光标位置。
stdout.isatty
检查标准输出是否是终端,如果不是,我们只需将输入写出即可。
最后,我稍微移动了
time.sleep
调用,以使时间更加一致。
这个解决方案并不完美,依赖于以下假设:
对于稍微更高级的光标控制,这个问题及其答案,以及有关 ANSI 转义序列的维基百科条目,可能是一个很好的起点。 您可能也会对 Python 标准库中的
termios
和 tty
模块感兴趣,尽管它们并非随处可用。