python输入单个字符_关于输入:Python从用户读取单个字符

有没有从用户输入中读取单个字符的方法?例如,他们在终端按下一个键,然后返回(类似于getch())。我知道Windows中有一个函数,但是我想要一个跨平台的函数。

在Windows上,我遇到了和这个问题相同的问题。解决方案是用msvcrt.getwch替换msvcrt.getch,如文中建议的那样。

这里有一个链接指向一个站点,说明如何在Windows、Linux和OSX中读取单个字符:http://code.activestate.com/recipes/134892/

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38class _Getch:

"""Gets a single character from standard input. Does not echo to the

screen."""

def __init__(self):

try:

self.impl = _GetchWindows()

except ImportError:

self.impl = _GetchUnix()

def __call__(self): return self.impl()

class _GetchUnix:

def __init__(self):

import tty, sys

def __call__(self):

import sys, tty, termios

fd = sys.stdin.fileno()

old_settings = termios.tcgetattr(fd)

try:

tty.setraw(sys.stdin.fileno())

ch = sys.stdin.read(1)

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

return ch

class _GetchWindows:

def __init__(self):

import msvcrt

def __call__(self):

import msvcrt

return msvcrt.getch()

getch = _Getch()

代码似乎足够短,您可以将其包含在内,但+1用于快速找到一个好的(跨平台)答案。

它能很好地处理非拉丁字母(如西里尔字母)吗?我有一个问题,不知道是不是我的错。

@Ilya:看看支持非ASCII字符的getpass()是如何实现的,例如,它在Windows上使用getwch()。

这真的很有帮助。如果不是执行return self.impl()操作,而是创建一个for循环并调用self.impl()3次,那么也可以从键盘上获得箭头键和其他转义序列。

我不喜欢像某些if语句那样使用ImportError异常;为什么不调用platform.system()来检查操作系统?

为什么使用它会使print语句不再正确地返回输出回车?

@Seismoid:请求原谅通常被认为更好,请参见stackoverflow.com/questions/12265451/…

不适用于OS X:"old_settings=termios.tcgetattr(fd)""termios.error:(25,'设备的ioctl不合适')"

@phlya我的理解是这个代码只接受1个字节。但是,UTF-8允许用1-4字节表示单个字符。这似乎是这个解决方案的一个大问题。

为什么这些类会覆盖__call__而不是普通方法??????????

msvcrt.getch()不适用于Pycharm。

这些仅仅是函数的伪类的使用是可怕的。为什么不为每一组模块和一个确定并返回要使用的函数的函数设置一个简单的函数呢?

@德克约特这个原则在这里并不适用。首先查询操作系统不会引入竞争条件。

想加我的2美分。在Unix实现中,当切换到tty原始模式时,输出看起来很糟糕。我建议采用以下方法来保持输出一致:old_settings = termios.tcgetattr(fd); tty.setraw(sys.stdin.fileno()); raw_settings = termios.tcgetattr(fd); raw_settings[1]=old_settings[1]; termios.tcsetattr(fd, termios.TCSADRAIN, raw_settings)。此代码保持输出标志与原始标志相同,因此当您将某些内容打印到屏幕时不会感到意外。

不适用于MacOS。我将脚本粘贴到一个文件中,然后从第二个文件调用它——调用立即返回,而不等待控制台输入。

ActiveStates的这个配方似乎包含了一个"posix"的小错误,它会阻止ctrl-c正常工作。请参阅下面我的答案和改进的代码。stackoverflow.com/a/48136131/404271

1sys.stdin.read(1)

基本上从stdin读取1个字节。

如果您必须使用不等待的方法,您可以使用前面答案中建议的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37class _Getch:

"""Gets a single character from standard input. Does not echo to the screen."""

def __init__(self):

try:

self.impl = _GetchWindows()

except ImportError:

self.impl = _GetchUnix()

def __call__(self): return self.impl()

class _GetchUnix:

def __init__(self):

import tty, sys

def __call__(self):

import sys, tty, termios

fd = sys.stdin.fileno()

old_settings = termios.tcgetattr(fd)

try:

tty.setraw(sys.stdin.fileno())

ch = sys.stdin.read(1)

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

return ch

class _GetchWindows:

def __init__(self):

import msvcrt

def __call__(self):

import msvcrt

return msvcrt.getch()

getch = _Getch()

(摘自http://code.activestate.com/recipes/134892/)

我觉得很奇怪sys.stdin.read(1)等待一个,lol。不过,谢谢您的提交。

一个字符还是一个字节?这不一样。

@Evan,这是因为默认情况下,python处于行缓冲模式。

请注意,此代码阻止您使用^C或^D!

@evanfosmark:不一定是sys.stdin.read(1)等待,而是终端程序决定何时将其他字符发送到程序,直到它看到'-否则,您如何能够按backspace并更正您键入的内容?(严肃的回答是-教python程序实现行控制、保留缓冲区、处理退格,但是这是一个不同的世界,当你仅仅"读取字符"时,你可能不想去购买它,并且可能会使你的行处理不同于系统上的所有其他程序。)

我不喜欢像某些if语句那样使用ImportError异常;为什么不调用platform.system()来检查操作系统?

地震震颤

两个答案中逐字引用的ActiveState配方设计过度。可以归结为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23def _find_getch():

try:

import termios

except ImportError:

# Non-POSIX. Return msvcrt's (Windows') getch.

import msvcrt

return msvcrt.getch

# POSIX system. Create and return a getch that manipulates the tty.

import sys, tty

def _getch():

fd = sys.stdin.fileno()

old_settings = termios.tcgetattr(fd)

try:

tty.setraw(fd)

ch = sys.stdin.read(1)

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

return ch

return _getch

getch = _find_getch()

另一个值得尝试的是readchar库,它部分基于其他答案中提到的activestate配方。

安装:

1pip install readchar

用途:

1

2

3

4

5import readchar

print("Reading a char:")

print(repr(readchar.readchar()))

print("Reading a key:")

print(repr(readchar.readkey()))

在Windows和Linux上测试了python 2.7。

在Windows上,仅支持映射到字母或ASCII控制代码的键(backspace、enter、esc、t、ctrl+letter)。在GNU/Linux上(可能取决于确切的终端?)您还可以获得insert、elete、pg up、pg dn、home、end和f nakbd密钥…但是,将这些特殊键与esc分离时会遇到一些问题。

警告:就像大多数(全部)一样?在这里,信号键CtrL+CbaBd、CtrL+DbaBbbd和CtrL+z被捕获并返回(分别为'\x03'、'\x04'和'\x1a';您的程序可能很难中止。

也可以在Linux上使用python 3。比getch好得多,因为readchar允许在等待键时(通过线程或异步)打印到stdout。

在kubuntu linux上测试过,这是一个非常完美的pythonic。谢谢您!

在Win10+python 3.5上测试:错误:根:"in"要求字符串作为左操作数,而不是字节回溯(最近调用的最后一个):file"..main.py",第184行,包装结果=func(*args,**kwargs)file"c:githubpython demodemodayhello.py",第41行,在readch中,例如print(readchar.readchar())file"c:usersipcjsappdatalocalprogramspythonpython35libs‌&误8203;ite packages

eadcha‌&误8203;r

eadchar_windows.p‌&误8203;y",第14行,位于readchar中,而ch位于x00xe0":typeerror:"in"需要字符串作为左操作数,而不是字节

@请向维护人员报告该错误

这是最好的答案。为VS C++库添加一个依赖性只是为了这个功能是疯狂的。

另一种方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26import os

import sys

import termios

import fcntl

def getch():

fd = sys.stdin.fileno()

oldterm = termios.tcgetattr(fd)

newattr = termios.tcgetattr(fd)

newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO

termios.tcsetattr(fd, termios.TCSANOW, newattr)

oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)

fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)

try:

while 1:

try:

c = sys.stdin.read(1)

break

except IOError: pass

finally:

termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)

fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)

return c

从这个博客文章。

似乎对我不起作用-调用后立即返回空字符串。在使用python 3.6的Linux上。

@如果要阻止(等待输入),请删除| os.O_NONBLOCK。否则,你可以把它放在一个循环中(最好在循环中睡一会儿以避免旋转)。

我认为在这一点上它变得非常笨拙,在不同的平台上调试是一个很大的混乱。

如果你在做比这更复杂的事情,并且需要视觉效果,或者如果你要使用终端的话,你最好使用类似于pyglet,pygame,cocos2d的东西。

诅咒是标准的:http://docs.python.org/library/curses.html

我强烈反对这一点。要使用诅咒,您必须初始化清除屏幕的终端。如果用户只想使用curses来获取用户输入而不使用,则这是不需要的行为。

如果按下ctrl+c或ctrl+d,则基于此处的代码将正确引发键盘中断和eoferror。

应该在Windows和Linux上工作。可以从原始源获得OS X版本。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45class _Getch:

"""Gets a single character from standard input. Does not echo to the screen."""

def __init__(self):

try:

self.impl = _GetchWindows()

except ImportError:

self.impl = _GetchUnix()

def __call__(self):

char = self.impl()

if char == '\x03':

raise KeyboardInterrupt

elif char == '\x04':

raise EOFError

return char

class _GetchUnix:

def __init__(self):

import tty

import sys

def __call__(self):

import sys

import tty

import termios

fd = sys.stdin.fileno()

old_settings = termios.tcgetattr(fd)

try:

tty.setraw(sys.stdin.fileno())

ch = sys.stdin.read(1)

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

return ch

class _GetchWindows:

def __init__(self):

import msvcrt

def __call__(self):

import msvcrt

return msvcrt.getch()

getch = _Getch()

此代码等待。

(目前)排名靠前的答案(带有ActiveState代码)过于复杂。当一个函数足够时,我看不出使用类的理由。下面是两个实现,它们完成了相同的事情,但具有更可读的代码。

这两种实现:

在python 2或python 3中工作得很好

在Windows、OSX和Linux上工作

只读取一个字节(即,它们不等待换行)

不依赖任何外部库

是独立的(函数定义之外没有代码)

版本1:可读简单

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20def getChar():

try:

# for Windows-based systems

import msvcrt # If successful, we are on Windows

return msvcrt.getch()

except ImportError:

# for POSIX-based systems (with termios & tty support)

import tty, sys, termios # raises ImportError if unsupported

fd = sys.stdin.fileno()

oldSettings = termios.tcgetattr(fd)

try:

tty.setcbreak(fd)

answer = sys.stdin.read(1)

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, oldSettings)

return answer

版本2:避免重复导入和异常处理:

[编辑]我错过了ActiveState代码的一个优势。如果您计划多次读取字符,那么该代码可以避免在类似Unix的系统上重复Windows导入和importError异常处理的成本(可以忽略不计)。虽然您可能更关注代码的可读性,而不是忽略不计的优化,但这里有一个替代方案(它类似于Louis的答案,但getchar()是独立的),它的功能与ActiveState代码相同,并且更具可读性:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27def getChar():

# figure out which function to use once, and store it in _func

if"_func" not in getChar.__dict__:

try:

# for Windows-based systems

import msvcrt # If successful, we are on Windows

getChar._func=msvcrt.getch

except ImportError:

# for POSIX-based systems (with termios & tty support)

import tty, sys, termios # raises ImportError if unsupported

def _ttyRead():

fd = sys.stdin.fileno()

oldSettings = termios.tcgetattr(fd)

try:

tty.setcbreak(fd)

answer = sys.stdin.read(1)

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, oldSettings)

return answer

getChar._func=_ttyRead

return getChar._func()

执行上述getchar()版本的示例代码:

1

2

3

4

5

6

7

8

9

10

11from __future__ import print_function # put at top of file if using Python 2

# Example of a prompt for one character of input

promptStr ="Please give me a character:"

responseStr ="Thank you for giving me a '{}'."

print(promptStr, end="

>")

answer = getChar()

print("

")

print(responseStr.format(answer))

我在打印消息的同时等待一个键(多线程)时遇到了tty.setraw()的问题。长话短说,我发现使用tty.setcbreak()可以得到一个字符,而不会破坏其他所有正常的内容。这个答案说来话长

很好,我希望有一种更干净的方法

这可能是上下文管理器的用例。除了Windows操作系统的允许之外,我的建议是:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34#!/usr/bin/env python3

# file: 'readchar.py'

"""

Implementation of a way to get a single character of input

without waiting for the user to hit .

(OS is Linux, Ubuntu 14.04)

"""

import tty, sys, termios

class ReadChar():

def __enter__(self):

self.fd = sys.stdin.fileno()

self.old_settings = termios.tcgetattr(self.fd)

tty.setraw(sys.stdin.fileno())

return sys.stdin.read(1)

def __exit__(self, type, value, traceback):

termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_settings)

def test():

while True:

with ReadChar() as rc:

char = rc

if ord(char) <= 32:

print("You entered character with ordinal {}."\

.format(ord(char)))

else:

print("You entered character '{}'."\

.format(char))

if char in"^C^D":

sys.exit()

if __name__ =="__main__":

test()

您还可以在__enter__中返回self,并使用返回sys.stdin.read(1)的read方法,然后在一个上下文中读取多个字符。

这里的答案很有信息性,但是我也希望有一种方法可以异步地获取按键,并在单独的事件中触发按键,所有这些都是线程安全的跨平台方式。Pygame对我来说也太臃肿了。所以我做了以下内容(在python 2.7中,但我怀疑它很容易移植),我想在这里分享一下,以防它对其他人有用。我把它存储在一个名为keypress.py的文件中。好的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395class _Getch:

"""Gets a single character from standard input. Does not echo to the

screen. From http://code.activestate.com/recipes/134892/"""

def __init__(self):

try:

self.impl = _GetchWindows()

except ImportError:

try:

self.impl = _GetchMacCarbon()

except(AttributeError, ImportError):

self.impl = _GetchUnix()

def __call__(self): return self.impl()

class _GetchUnix:

def __init__(self):

import tty, sys, termios # import termios now or else you'll get the Unix version on the Mac

def __call__(self):

import sys, tty, termios

fd = sys.stdin.fileno()

old_settings = termios.tcgetattr(fd)

try:

tty.setraw(sys.stdin.fileno())

ch = sys.stdin.read(1)

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

return ch

class _GetchWindows:

def __init__(self):

import msvcrt

def __call__(self):

import msvcrt

return msvcrt.getch()

class _GetchMacCarbon:

"""

A function which returns the current ASCII key that is down;

if no ASCII key is down, the null string is returned. The

page http://www.mactech.com/macintosh-c/chap02-1.html was

very helpful in figuring out how to do this.

"""

def __init__(self):

import Carbon

Carbon.Evt #see if it has this (in Unix, it doesn't)

def __call__(self):

import Carbon

if Carbon.Evt.EventAvail(0x0008)[0]==0: # 0x0008 is the keyDownMask

return ''

else:

#

# The event contains the following info:

# (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]

#

# The message (msg) contains the ASCII char which is

# extracted with the 0x000000FF charCodeMask; this

# number is converted to an ASCII character with chr() and

# returned

#

(what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]

return chr(msg & 0x000000FF)

import threading

# From https://stackoverflow.com/a/2022629/2924421

class Event(list):

def __call__(self, *args, **kwargs):

for f in self:

f(*args, **kwargs)

def __repr__(self):

return"Event(%s)" % list.__repr__(self)

def getKey():

inkey = _Getch()

import sys

for i in xrange(sys.maxint):

k=inkey()

if k<>'':break

return k

class KeyCallbackFunction():

callbackParam = None

actualFunction = None

def __init__(self, actualFunction, callbackParam):

self.actualFunction = actualFunction

self.callbackParam = callbackParam

def doCallback(self, inputKey):

if not self.actualFunction is None:

if self.callbackParam is None:

callbackFunctionThread = threading.Thread(target=self.actualFunction, args=(inputKey,))

else:

callbackFunctionThread = threading.Thread(target=self.actualFunction, args=(inputKey,self.callbackParam))

callbackFunctionThread.daemon = True

callbackFunctionThread.start()

class KeyCapture():

gotKeyLock = threading.Lock()

gotKeys = []

gotKeyEvent = threading.Event()

keyBlockingSetKeyLock = threading.Lock()

addingEventsLock = threading.Lock()

keyReceiveEvents = Event()

keysGotLock = threading.Lock()

keysGot = []

keyBlockingKeyLockLossy = threading.Lock()

keyBlockingKeyLossy = None

keyBlockingEventLossy = threading.Event()

keysBlockingGotLock = threading.Lock()

keysBlockingGot = []

keyBlockingGotEvent = threading.Event()

wantToStopLock = threading.Lock()

wantToStop = False

stoppedLock = threading.Lock()

stopped = True

isRunningEvent = False

getKeyThread = None

keyFunction = None

keyArgs = None

# Begin capturing keys. A seperate thread is launched that

# captures key presses, and then these can be received via get,

# getAsync, and adding an event via addEvent. Note that this

# will prevent the system to accept keys as normal (say, if

# you are in a python shell) because it overrides that key

# capturing behavior.

# If you start capture when it's already been started, a

# InterruptedError("Keys are still being captured")

# will be thrown

# Note that get(), getAsync() and events are independent, so if a key is pressed:

#

# 1: Any calls to get() that are waiting, with lossy on, will return

# that key

# 2: It will be stored in the queue of get keys, so that get() with lossy

# off will return the oldest key pressed not returned by get() yet.

# 3: All events will be fired with that key as their input

# 4: It will be stored in the list of getAsync() keys, where that list

# will be returned and set to empty list on the next call to getAsync().

# get() call with it, aand add it to the getAsync() list.

def startCapture(self, keyFunction=None, args=None):

# Make sure we aren't already capturing keys

self.stoppedLock.acquire()

if not self.stopped:

self.stoppedLock.release()

raise InterruptedError("Keys are still being captured")

return

self.stopped = False

self.stoppedLock.release()

# If we have captured before, we need to allow the get() calls to actually

# wait for key presses now by clearing the event

if self.keyBlockingEventLossy.is_set():

self.keyBlockingEventLossy.clear()

# Have one function that we call every time a key is captured, intended for stopping capture

# as desired

self.keyFunction = keyFunction

self.keyArgs = args

# Begin capturing keys (in a seperate thread)

self.getKeyThread = threading.Thread(target=self._threadProcessKeyPresses)

self.getKeyThread.daemon = True

self.getKeyThread.start()

# Process key captures (in a seperate thread)

self.getKeyThread = threading.Thread(target=self._threadStoreKeyPresses)

self.getKeyThread.daemon = True

self.getKeyThread.start()

def capturing(self):

self.stoppedLock.acquire()

isCapturing = not self.stopped

self.stoppedLock.release()

return isCapturing

# Stops the thread that is capturing keys on the first opporunity

# has to do so. It usually can't stop immediately because getting a key

# is a blocking process, so this will probably stop capturing after the

# next key is pressed.

#

# However, Sometimes if you call stopCapture it will stop before starting capturing the

# next key, due to multithreading race conditions. So if you want to stop capturing

# reliably, call stopCapture in a function added via addEvent. Then you are

# guaranteed that capturing will stop immediately after the rest of the callback

# functions are called (before starting to capture the next key).

def stopCapture(self):

self.wantToStopLock.acquire()

self.wantToStop = True

self.wantToStopLock.release()

# Takes in a function that will be called every time a key is pressed (with that

# key passed in as the first paramater in that function)

def addEvent(self, keyPressEventFunction, args=None):

self.addingEventsLock.acquire()

callbackHolder = KeyCallbackFunction(keyPressEventFunction, args)

self.keyReceiveEvents.append(callbackHolder.doCallback)

self.addingEventsLock.release()

def clearEvents(self):

self.addingEventsLock.acquire()

self.keyReceiveEvents = Event()

self.addingEventsLock.release()

# Gets a key captured by this KeyCapture, blocking until a key is pressed.

# There is an optional lossy paramater:

# If True all keys before this call are ignored, and the next pressed key

# will be returned.

# If False this will return the oldest key captured that hasn't

# been returned by get yet. False is the default.

def get(self, lossy=False):

if lossy:

# Wait for the next key to be pressed

self.keyBlockingEventLossy.wait()

self.keyBlockingKeyLockLossy.acquire()

keyReceived = self.keyBlockingKeyLossy

self.keyBlockingKeyLockLossy.release()

return keyReceived

else:

while True:

# Wait until a key is pressed

self.keyBlockingGotEvent.wait()

# Get the key pressed

readKey = None

self.keysBlockingGotLock.acquire()

# Get a key if it exists

if len(self.keysBlockingGot) != 0:

readKey = self.keysBlockingGot.pop(0)

# If we got the last one, tell us to wait

if len(self.keysBlockingGot) == 0:

self.keyBlockingGotEvent.clear()

self.keysBlockingGotLock.release()

# Process the key (if it actually exists)

if not readKey is None:

return readKey

# Exit if we are stopping

self.wantToStopLock.acquire()

if self.wantToStop:

self.wantToStopLock.release()

return None

self.wantToStopLock.release()

def clearGetList(self):

self.keysBlockingGotLock.acquire()

self.keysBlockingGot = []

self.keysBlockingGotLock.release()

# Gets a list of all keys pressed since the last call to getAsync, in order

# from first pressed, second pressed, .., most recent pressed

def getAsync(self):

self.keysGotLock.acquire();

keysPressedList = list(self.keysGot)

self.keysGot = []

self.keysGotLock.release()

return keysPressedList

def clearAsyncList(self):

self.keysGotLock.acquire();

self.keysGot = []

self.keysGotLock.release();

def _processKey(self, readKey):

# Append to list for GetKeyAsync

self.keysGotLock.acquire()

self.keysGot.append(readKey)

self.keysGotLock.release()

# Call lossy blocking key events

self.keyBlockingKeyLockLossy.acquire()

self.keyBlockingKeyLossy = readKey

self.keyBlockingEventLossy.set()

self.keyBlockingEventLossy.clear()

self.keyBlockingKeyLockLossy.release()

# Call non-lossy blocking key events

self.keysBlockingGotLock.acquire()

self.keysBlockingGot.append(readKey)

if len(self.keysBlockingGot) == 1:

self.keyBlockingGotEvent.set()

self.keysBlockingGotLock.release()

# Call events added by AddEvent

self.addingEventsLock.acquire()

self.keyReceiveEvents(readKey)

self.addingEventsLock.release()

def _threadProcessKeyPresses(self):

while True:

# Wait until a key is pressed

self.gotKeyEvent.wait()

# Get the key pressed

readKey = None

self.gotKeyLock.acquire()

# Get a key if it exists

if len(self.gotKeys) != 0:

readKey = self.gotKeys.pop(0)

# If we got the last one, tell us to wait

if len(self.gotKeys) == 0:

self.gotKeyEvent.clear()

self.gotKeyLock.release()

# Process the key (if it actually exists)

if not readKey is None:

self._processKey(readKey)

# Exit if we are stopping

self.wantToStopLock.acquire()

if self.wantToStop:

self.wantToStopLock.release()

break

self.wantToStopLock.release()

def _threadStoreKeyPresses(self):

while True:

# Get a key

readKey = getKey()

# Run the potential shut down function

if not self.keyFunction is None:

self.keyFunction(readKey, self.keyArgs)

# Add the key to the list of pressed keys

self.gotKeyLock.acquire()

self.gotKeys.append(readKey)

if len(self.gotKeys) == 1:

self.gotKeyEvent.set()

self.gotKeyLock.release()

# Exit if we are stopping

self.wantToStopLock.acquire()

if self.wantToStop:

self.wantToStopLock.release()

self.gotKeyEvent.set()

break

self.wantToStopLock.release()

# If we have reached here we stopped capturing

# All we need to do to clean up is ensure that

# all the calls to .get() now return None.

# To ensure no calls are stuck never returning,

# we will leave the event set so any tasks waiting

# for it immediately exit. This will be unset upon

# starting key capturing again.

self.stoppedLock.acquire()

# We also need to set this to True so we can start up

# capturing again.

self.stopped = True

self.stopped = True

self.keyBlockingKeyLockLossy.acquire()

self.keyBlockingKeyLossy = None

self.keyBlockingEventLossy.set()

self.keyBlockingKeyLockLossy.release()

self.keysBlockingGotLock.acquire()

self.keyBlockingGotEvent.set()

self.keysBlockingGotLock.release()

self.stoppedLock.release()

其思想是,您可以简单地调用keyPress.getKey(),它将从键盘读取一个键,然后返回它。好的。

如果你想要更多的东西,我做了一个KeyCapture物体。您可以通过类似于keys = keyPress.KeyCapture()的方式创建一个。好的。

然后你可以做三件事:好的。

addEvent(functionName)接受任何接受一个参数的函数。然后,每次按下一个键时,这个函数都会以该键的字符串作为输入来调用。这些是在一个单独的线程中运行的,因此您可以阻塞它们中的所有内容,并且它不会破坏keycapturer的功能,也不会延迟其他事件。好的。

get()以与以前相同的阻塞方式返回密钥。现在需要这样做,因为密钥现在是通过KeyCapture对象捕获的,所以keyPress.getKey()会与该行为冲突,并且它们都会丢失一些密钥,因为一次只能捕获一个密钥。另外,假设用户按"a",然后按"b",你称之为get(),用户按"c"。该get()调用将立即返回"a",如果您再次调用它,它将返回"b",然后返回"c"。如果您再次调用它,它将一直阻塞,直到按下另一个键。这可以确保您不会错过任何键,如果需要的话,以阻塞的方式。所以这和之前的keyPress.getKey()有点不同好的。

如果你想让getKey()返回,get(lossy=True)就像get()一样,只是在调用get()之后,它只返回按下的键。所以在上面的例子中,get()会一直阻塞,直到用户按"c",然后如果您再次调用它,它会阻塞,直到另一个键被按下。好的。

getAsync()有点不同。它是为做大量处理的事情而设计的,然后偶尔回来检查哪些键被按下。因此,getAsync()返回自上次调用getAsync()以来按下的所有键的列表,从按下的最旧键到最近按下的键。它也不会阻塞,这意味着如果自上次调用getAsync()以来没有按下任何键,则返回空的[]。好的。

要真正开始捕获密钥,您需要使用上面制作的keys对象调用keys.startCapture()。startCapture是非阻塞的,只需启动一个只记录按键的线程,以及另一个处理按键的线程。有两个线程来确保记录按键的线程不会丢失任何键。好的。

如果要停止捕获密钥,可以调用keys.stopCapture(),它将停止捕获密钥。但是,由于捕获密钥是一个阻塞操作,线程捕获密钥可能在调用stopCapture()之后再捕获一个密钥。好的。

为了防止出现这种情况,可以将可选参数传入函数的startCapture(functionName, args)中,该函数执行类似于检查键是否等于"c",然后退出的操作。很重要的一点是,这个功能以前很少做,例如,在这里睡觉会导致我们错过钥匙。好的。

但是,如果在该函数中调用stopCapture(),则将立即停止密钥捕获,而不再尝试捕获,并且将立即返回所有get()调用,如果尚未按下任何键,则不返回任何调用。好的。

另外,由于get()和getAsync()存储所有以前按过的键(直到您检索它们),您可以调用clearGetList()和clearAsyncList()来忘记以前按过的键。好的。

请注意,get()、getAsync()和事件是独立的,因此如果按下某个键:1。一个正在等待的、有损接通的get()呼叫将返回。那把钥匙。其他等待呼叫(如果有)将继续等待。2。该密钥将存储在GET密钥队列中,这样,在关闭lossy的情况下,get()将返回尚未由get()返回的最旧密钥。三。所有事件都将以该键作为输入进行激发4。该键将存储在getAsync()键列表中,在该列表中,将返回该lis twill,并在下次调用getAsync()时设置为空列表。好的。

如果所有这些都太多,下面是一个示例用例:好的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76import keyPress

import time

import threading

def KeyPressed(k, printLock):

printLock.acquire()

print"Event:" + k

printLock.release()

time.sleep(4)

printLock.acquire()

print"Event after delay:" + k

printLock.release()

def GetKeyBlocking(keys, printLock):

while keys.capturing():

keyReceived = keys.get()

time.sleep(1)

printLock.acquire()

if not keyReceived is None:

print"Block" + keyReceived

else:

print"Block None"

printLock.release()

def GetKeyBlockingLossy(keys, printLock):

while keys.capturing():

keyReceived = keys.get(lossy=True)

time.sleep(1)

printLock.acquire()

if not keyReceived is None:

print"Lossy:" + keyReceived

else:

print"Lossy: None"

printLock.release()

def CheckToClose(k, (keys, printLock)):

printLock.acquire()

print"Close:" + k

printLock.release()

if k =="c":

keys.stopCapture()

printLock = threading.Lock()

print"Press a key:"

print"You pressed:" + keyPress.getKey()

print""

keys = keyPress.KeyCapture()

keys.addEvent(KeyPressed, printLock)

print"Starting capture"

keys.startCapture(CheckToClose, (keys, printLock))

getKeyBlockingThread = threading.Thread(target=GetKeyBlocking, args=(keys, printLock))

getKeyBlockingThread.daemon = True

getKeyBlockingThread.start()

getKeyBlockingThreadLossy = threading.Thread(target=GetKeyBlockingLossy, args=(keys, printLock))

getKeyBlockingThreadLossy.daemon = True

getKeyBlockingThreadLossy.start()

while keys.capturing():

keysPressed = keys.getAsync()

printLock.acquire()

if keysPressed != []:

print"Async:" + str(keysPressed)

printLock.release()

time.sleep(1)

print"done capturing"

从我做的简单测试来看,它对我来说很好,但是如果我错过了什么,我也会很高兴地接受其他人的反馈。好的。

我也把这个贴在这里了。好的。好啊。

其中一个答案中的注释提到了cbreak模式,这对Unix实现很重要,因为您通常不希望getchar使用^c(KeyboardError)(与大多数其他答案一样,当您将终端设置为原始模式时会使用)。

另一个重要的细节是,如果您希望读取一个字符而不是一个字节,那么应该从输入流中读取4个字节,因为这是一个字符在UTF-8(python 3+)中所包含的最大字节数。仅读取一个字节将对多字节字符(如键盘箭头)产生意外结果。

以下是我为Unix更改的实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31import contextlib

import os

import sys

import termios

import tty

_MAX_CHARACTER_BYTE_LENGTH = 4

@contextlib.contextmanager

def _tty_reset(file_descriptor):

"""

A context manager that saves the tty flags of a file descriptor upon

entering and restores them upon exiting.

"""

old_settings = termios.tcgetattr(file_descriptor)

try:

yield

finally:

termios.tcsetattr(file_descriptor, termios.TCSADRAIN, old_settings)

def get_character(file=sys.stdin):

"""

Read a single character from the given input stream (defaults to sys.stdin).

"""

file_descriptor = file.fileno()

with _tty_reset(file_descriptor):

tty.setcbreak(file_descriptor)

return os.read(file_descriptor, _MAX_CHARACTER_BYTE_LENGTH)

尝试使用:http://home.wlu.edu/~levys/software/kbhit.py它是非阻塞的(这意味着你可以有一个while循环,在不停止的情况下检测到一个按键)和跨平台的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92import os

# Windows

if os.name == 'nt':

import msvcrt

# Posix (Linux, OS X)

else:

import sys

import termios

import atexit

from select import select

class KBHit:

def __init__(self):

'''Creates a KBHit object that you can call to do various keyboard things.'''

if os.name == 'nt':

pass

else:

# Save the terminal settings

self.fd = sys.stdin.fileno()

self.new_term = termios.tcgetattr(self.fd)

self.old_term = termios.tcgetattr(self.fd)

# New terminal setting unbuffered

self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)

termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)

# Support normal-terminal reset at exit

atexit.register(self.set_normal_term)

def set_normal_term(self):

''' Resets to normal terminal. On Windows this is a no-op.

'''

if os.name == 'nt':

pass

else:

termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)

def getch(self):

''' Returns a keyboard character after kbhit() has been called.

Should not be called in the same program as getarrow().

'''

s = ''

if os.name == 'nt':

return msvcrt.getch().decode('utf-8')

else:

return sys.stdin.read(1)

def getarrow(self):

''' Returns an arrow-key code after kbhit() has been called. Codes are

0 : up

1 : right

2 : down

3 : left

Should not be called in the same program as getch().

'''

if os.name == 'nt':

msvcrt.getch() # skip 0xE0

c = msvcrt.getch()

vals = [72, 77, 80, 75]

else:

c = sys.stdin.read(3)[2]

vals = [65, 67, 66, 68]

return vals.index(ord(c.decode('utf-8')))

def kbhit(self):

''' Returns True if keyboard character was hit, False otherwise.

'''

if os.name == 'nt':

return msvcrt.kbhit()

else:

dr,dw,de = select([sys.stdin], [], [], 0)

return dr != []

使用此方法的示例:

1

2

3

4

5

6

7

8

9

10import kbhit

kb = kbhit.KBHit()

while(True):

print("Key not pressed") #Do something

if kb.kbhit(): #If a key is pressed:

k_in = kb.getch() #Detect what key was pressed

print("You pressed", k_in,"!") #Do something

kb.set_normal_term()

或者可以使用PYPI中的getch模块。但这会阻塞while循环

这是非阻塞的,读取一个键并将其存储在keypress.key中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17import Tkinter as tk

class Keypress:

def __init__(self):

self.root = tk.Tk()

self.root.geometry('300x200')

self.root.bind('', self.onKeyPress)

def onKeyPress(self, event):

self.key = event.char

def __eq__(self, other):

return self.key == other

def __str__(self):

return self.key

在您的程序中

1

2

3

4

5

6

7

8

9

10keypress = Keypress()

while something:

do something

if keypress == 'c':

break

elif keypress == 'i':

print('info')

else:

print("i dont understand %s" % keypress)

这对命令行应用程序有效吗?

是@thorsummer

@thorsummoner:这个代码有很多问题——所以不,它不能用于命令行应用程序。

它为命令行应用程序运行,前提是Windows管理器正在运行。

所以基本上它不适用于命令行应用程序…

不,它不会在无头操作系统中运行。但它确实在命令行窗口中运行。

用Pygame试试这个:

1

2

3

4

5

6

7

8import pygame

pygame.init() // eliminate error, pygame.error: video system not initialized

keys = pygame.key.get_pressed()

if keys[pygame.K_SPACE]:

d ="space key"

print"You pressed the", d,"."

这是一个很好的想法,但在命令行上不起作用:pygame.error: video system not initialized

ActiveState的配方似乎包含一个"posix"系统的小错误,它可以防止Ctrl-C中断(我使用的是mac)。如果我在脚本中放入以下代码:

1

2while(True):

print(getch())

我永远无法用Ctrl-C终止脚本,我必须杀死终端才能逃脱。

我相信下面这句话是原因,而且太残忍了:

1tty.setraw(sys.stdin.fileno())

除此之外,并不需要tty包,termios包就足够了。

下面是适用于我的改进代码(Ctrl-C将中断),额外的getche函数将在键入时回送字符:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31if sys.platform == 'win32':

import msvcrt

getch = msvcrt.getch

getche = msvcrt.getche

else:

import sys

import termios

def __gen_ch_getter(echo):

def __fun():

fd = sys.stdin.fileno()

oldattr = termios.tcgetattr(fd)

newattr = oldattr[:]

try:

if echo:

# disable ctrl character printing, otherwise, backspace will be printed as"^?"

lflag = ~(termios.ICANON | termios.ECHOCTL)

else:

lflag = ~(termios.ICANON | termios.ECHO)

newattr[3] &= lflag

termios.tcsetattr(fd, termios.TCSADRAIN, newattr)

ch = sys.stdin.read(1)

if echo and ord(ch) == 127: # backspace

# emulate backspace erasing

# https://stackoverflow.com/a/47962872/404271

sys.stdout.write('\b \b')

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, oldattr)

return ch

return __fun

getch = __gen_ch_getter(False)

getche = __gen_ch_getter(True)

参考文献:

https://pypi.python.org/pypi/getch网站

python中的curses包只需几条语句就可以用于从终端输入字符,进入"原始"模式。Curses的主要用途是接管屏幕进行输出,这可能不是您想要的。此代码段使用print()语句,这是可用的,但您必须知道curses是如何更改附加到输出的行尾的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28#!/usr/bin/python3

# Demo of single char terminal input in raw mode with the curses package.

import sys, curses

def run_one_char(dummy):

'Run until a carriage return is entered'

char = ' '

print('Welcome to curses', flush=True)

while ord(char) != 13:

char = one_char()

def one_char():

'Read one character from the keyboard'

print('

? ', flush= True, end = '')

## A blocking single char read in raw mode.

char = sys.stdin.read(1)

print('You entered %s

' % char)

return char

## Must init curses before calling any functions

curses.initscr()

## To make sure the terminal returns to its initial settings,

## and to set raw mode and guarantee cleanup on exit.

curses.wrapper(run_one_char)

print('Curses be gone!')

如果我做一些复杂的事情,我会用诅咒来读钥匙。但很多时候我只需要一个简单的python 3脚本,它使用标准库,可以读取箭头键,所以我这样做:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22import sys, termios, tty

key_Enter = 13

key_Esc = 27

key_Up = '\033[A'

key_Dn = '\033[B'

key_Rt = '\033[C'

key_Lt = '\033[D'

fdInput = sys.stdin.fileno()

termAttr = termios.tcgetattr(0)

def getch():

tty.setraw(fdInput)

ch = sys.stdin.buffer.raw.read(4).decode(sys.stdin.encoding)

if len(ch) == 1:

if ord(ch) < 32 or ord(ch) > 126:

ch = ord(ch)

elif ord(ch[0]) == 27:

ch = '\033' + ch[1:]

termios.tcsetattr(fdInput, termios.TCSADRAIN, termAttr)

return ch

内置的原始输入应该有帮助。

1

2

3

4for i in range(3):

print ("So much work to do!")

k = raw_input("Press any key to continue...")

print ("Ok, back to work.")

原始输入正在等待输入键

我的python3解决方案,不依赖于任何PIP包。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29# precondition: import tty, sys

def query_yes_no(question, default=True):

"""

Ask the user a yes/no question.

Returns immediately upon reading one-char answer.

Accepts multiple language characters for yes/no.

"""

if not sys.stdin.isatty():

return default

if default:

prompt ="[Y/n]?"

other_answers ="n"

else:

prompt ="[y/N]?"

other_answers ="yjosiá"

print(question,prompt,flush= True,end="")

oldttysettings = tty.tcgetattr(sys.stdin.fileno())

try:

tty.setraw(sys.stdin.fileno())

return not sys.stdin.read(1).lower() in other_answers

except:

return default

finally:

tty.tcsetattr(sys.stdin.fileno(), tty.TCSADRAIN , oldttysettings)

sys.stdout.write("

")

tty.tcdrain(sys.stdin.fileno())

我相信这是一个最优雅的解决方案。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17import os

if os.name == 'nt':

import msvcrt

def getch():

return msvcrt.getch().decode()

else:

import sys, tty, termios

fd = sys.stdin.fileno()

old_settings = termios.tcgetattr(fd)

def getch():

try:

tty.setraw(sys.stdin.fileno())

ch = sys.stdin.read(1)

finally:

termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

return ch

然后在代码中使用它:

1

2if getch() == chr(ESC_ASCII_VALUE):

print("ESC!")

你可能感兴趣的:(python输入单个字符)