14. 练习:登录注册系统

Hi,大家好。我是茶桁。

上一节课,我们详细的介绍了文件读写的流程和原理,并用 Python 进行实际操作了一下。

那么这节课呢,我们利用之前所学的内容,尝试做一个小练习:建立一个登录注册系统。上节课我们在结尾的时候讲练习内容贴了出来,还记得要求吗?

1
2
3
4
5
6
实现功能:
1. 用户输入用户名和密码以及确认密码
2. 用户名不能重复
3. 两次密码要一致
4. 用户用已经注册的账户登录
5. 密码如果错误 3 次,锁定,无法再登录。

那么这节课呢,因为都是一些讲过的知识点,所以在整个实现过程中我就不详细讲解了,我们重点在于介绍思想和流程。那让我们开始吧。

注册功能

我们先把大的结构写出来,一个注册功能,那首先需要接收两个参数:用户名、密码。

并且,为了防止用户注册时候输错密码导致无法登录,还需要让用户确认一遍密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 先实现注册功能
# 封装一个函数 完成注册功能
def register():

# 用户输入用户名
username = input('欢迎注册,请输入用户名:')

# 用户输入密码
password = input('请输入您的密码: ')

# 请确认您的密码
re_password = input('请再输入一次您的密码: ')

print(username, password, re_password)


register()

---
du 111111 111111

下一步我们来细化这个框架,我们把用户名先放在一边,来挑一挑密码的刺:

首先,我们需要让密码保持安全性,那么我们对位数,组合就要有要求。这里我们简单点,必须输入 6 位以上吧。

然后,在确认密码的时候,肯定两次密码要一致才行。

OK,让我们补全这几个逻辑关系:

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
# 先实现注册功能
# 封装一个函数 完成注册功能
def register():

# 用户输入用户名
username = input('欢迎注册,请输入用户名:')

# 用户名需要检测是否已经存在

# 用户输入密码
password = input('请输入您的密码: ')

# 检测密码长度不能低于 6 位
if len(password) >= 6:
# 请确认您的密码
re_password = input('请再输入一次您的密码: ')
# 检测密码和确认密码是否一致
if re_password == password:
# 用户名和密码都正确,可以写入文件
pass
else:
print('两次输入的密码不同,请重新输入。', username, password, re_password)

# 密码长度不够
else:
print('密码格式不正确:', username, password)

register()

---
两次输入的密码不同,请重新输入。 du 123456 1234567

可以看得出来,我确认密码的时候输入了其他密码,判断逻辑没有问题。

继续来细化,现在的问题是,当我们输入第一次密码的时候判断有问题,或者两次密码输入不一致的时候,我们没有办法跳到一开始让用户重新输入,那么下面我们就来解决这个问题:

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
# 先实现注册功能
# 封装一个函数 完成注册功能
def register():

# 用户输入用户名
username = input('欢迎注册,请输入用户名:')

# 用户名需要检测是否已经存在

# 利用循环,都正确的时候结束循环。
while True:
# 用户输入密码
password = input('请输入您的密码: ')

# 检测密码长度不能低于 6 位
if len(password) >= 6:
# 请确认您的密码
re_password = input('请再输入一次您的密码: ')
# 检测密码和确认密码是否一致
if re_password == password:
# 用户名和密码都正确,可以写入文件
print('恭喜你,注册成功', username, password, re_password)
# 结束循环
break
else:
print('两次输入的密码不同,请重新输入。', username, password, re_password)

# 密码长度不够
else:
print('密码格式不正确:', username, password)

register()

---
密码格式不正确: du 12345
两次输入的密码不同,请重新输入。 du 123456 1234567
恭喜你,注册成功 du 123456 123456

外面套一层while循环,这样就解决了。

只有当密码正确输入的时候,循环才会结束,否则就会跳到循环的最开始,重新进行输入。

接着,我们就来看看当输入正确之后,我们如何写入文件呢?这个是我们上一节课刚讲解的课程,现在让我们来实现一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 先实现注册功能
# 封装一个函数 完成注册功能
def register():
...
if re_password == password:
# 用户名和密码都正确,可以写入文件
# 打开文件,写入数据
with open('./data/user.txt', 'a+', encoding='UTF-8') as fp:
fp.write(f'{username}:{password}\n')
print(f'注册成功,用户名:{username}')
# 结束循环
break
...
register()

---
注册成功,用户名:du

这样,我们就将用户名和密码以username:password的格式以一行的形式存储到了一个user.txt文件内。并且,因为我们使用的a+的模式,所以每次打开指针都会是放在文件的末尾进行添加。

来,让我们看看是否成功了:

可以看到,没问题。这样,我们在之后读取的时候就可以直接读取单行,并且以key:value的形式拿到我们说要的用户名和密码。用于验证key是否存在,value是否正确。

说到验证key是否存在,似乎我们还没写这一段验证代码,既然文件里已经有数据了,让我们把这段代码补全吧, 我们在register()这个自定义函数外面写一段读取文件的代码,写在外面是因为,我们读取文件这个动作,和整个注册动作不能说毫无关系,只能说是没有关联。

1
2
3
4
5
6
with open('./data/user.txt', 'r', encoding="utf-8") as fp:
res = fp.readlines()
print(res)

---
['admin:123456\n', 'du:654321\n']

还记得吗?readlines()这个函数的应用,不记得的,看我教程的上一节内容复习下。

看似我们确实成功的读取到了全部内容,可是我们细想一下,这样想有没有问题?还记得我们上一节课上对r这个模式的说明吗?r是读取已有文件,但是如果文件不存在,那就会报错。

可是我们在整个程序运行过程中,不能因为这个原因就不让用户继续往下走了对吧?那么我们怎么去修改呢?来,我们一起尝试下:

1
2
3
4
5
6
7
8
# 读取所有的注册信息
with open('./data/user.txt', 'a+', encoding="utf-8") as fp:
fp.seek(0,0)
res = fp.readlines()
print(res)

---
['admin:123456\n', 'du:654321\n']

我们利用a+的特性,如果文件不存在则会创建,如果有,这将指针放在文件末尾。这样的话,这一段内容就没有问题了?为什么不用w+? 还不是因为w+太霸道,虽然新人胜旧人,但也不能就直接把旧人干掉吧。

但是a+是将指针放在文件末尾的,我们直接使用的话,什么内容也读不到,所以我们使用可seek(0,0)来将指针放在了文件头的位置。

好了,继续。我们只将文件读取出来没用,需要将其中的数据放在一个变量里供我们检测,所以下一步,我们就需要创建一个变量,并且接收文件中的所有数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 先实现注册功能

# 专门定义数据变量,存放已经注册的用户信息
userlist = []
pwdlist = []

# 读取所有的注册信息
with open('./data/user.txt', 'a+', encoding="utf-8") as fp:
fp.seek(0,0) # 调整指针到文件头
res = fp.readlines() # 按照每一行读取所有用户数据
for i in res: # 循环读取每一行数据
r = i.strip() # 处理每一行尾部的换行符
mydict = r.split(':') # 分隔用户名和密码
userlist.append(mydict[0])
pwdlist.append(mydict[1])
print(userlist, pwdlist)

---
['admin', 'du'] ['123456', '654321']

这样,我们拿到用户名和密码,并且分别放入了两个list变量中。

接下来,我们对于注册那一步就需要进行判断了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 封装一个函数 完成注册功能
def register():
...
# 用户输入用户名
username = input('欢迎注册,请输入用户名:')

# 用户名需要检测是否已经存在
if username in userlist:
print('当前用户已经存在,请更换用户名。')
else:
# 利用循环,都正确的时候结束循环。
while True:
# 用户输入密码
password = input('请输入您的密码: ')
...

# register()

在代码中,我们判断用户名是否存在于列表中,如果存在,打印已经存在,如果不存在,则继续往下执行。将之前输入密码的while循环放入了else判断逻辑中。

看似问题解决了,不过新的问题又来了。虽然我们现在判断了用户是否存在,并且用户不存在的话可以继续往下执行,可是如果存在呢?我们需要的是让用户重新输入用户名,但是现在是打印完之后就结束了。所以,我们还得继续修改代码,这回简单了,和password的处理用一样的方法就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def register():
# 执行循环, 用户名操作
while True:
# 用户输入用户名
username = input('欢迎注册,请输入用户名:')

# 用户名需要检测是否已经存在
if username in userlist:
print('当前用户已经存在,请更换用户名。')
else:
# 利用循环,都正确的时候结束循环。
while True:
...
if len(password) >= 6:
...
if re_password == password:
...
# 结束循环
break
else:
...
# register()

我们将register()内的所有判断代码放入了一个循环中,那么在if之后,没有向下执行else内的逻辑的时候,就会跳转到最开始的while循环重新执行。

不过这样似乎还是不行,那就是几遍else逻辑全部执行完毕,内部的break也是跳出里面那层while循环,外层循环只能无限的执行下去了。

我们目前所要做的,就是在所有的代码顺利执行到密码正确确认的时候,整个函数执行就全部终止了。那有什么办法吗?

我们看到了while True, 然后开启了无限循环。那是不是说,如果True那里不为真,就无法进入循环了?

嗯,既然这样,我们设定一个变量,在需要终止函数的时候,设定变量为Flase就行了。

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
# 封装一个函数 完成注册功能
def register():
# 定义一个变量,用于控制外循环
site = True
# 执行循环, 用户名操作
while site:
...
if username in userlist:
print('当前用户已经存在,请更换用户名。')
else:
# 利用循环,都正确的时候结束循环。
while True:
...
if len(password) >= 6:
...
if re_password == password:
...
# 结束循环
# 结束外循环
site = False
# 结束内循环
break
else:
...

# register()

这样,在密码确认成功之后,site设定为falsewhile循环判断条件为假,无法再次进入循环。执行代码,输出注册成功:用户名:du2, 并且跳出了循环不再继续向下执行。说明我们这一步已经没问题了。

那到这里,我们一个简易的可运行的注册方法就完成了,当然,这段代码中其实还有很多可以完善的空间,比如说,判断用户名是否存在之后,还要判断用户名是否合法,密码是否合法等等。还有就是我们一般接收账户密码的变量应该使用key:value形式的字典,不过大多数时候,我们还要考虑效率问题。分别保存成两份是不错的选择,那么这个时候,我们就需要用到获取当前字典中所有key值的方法了。以上代码在关键部位可以这样改:

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
# 专门定义数据变量,存放已经注册的用户信息
userdict = {}
userlist = []

# 读取所有的注册信息
with open('./data/user.txt', 'a+', encoding="utf-8") as fp:
fp.seek(0,0) # 调整指针到文件头
res = fp.readlines() # 按照每一行读取所有用户数据
for i in res: # 循环读取每一行数据
r = i.strip() # 处理每一行尾部的换行符
mydict = r.split(':')
userdict.update({mydict[0]:mydict[1]})
userlist = userdict.keys()

# 封装一个函数 完成注册功能
def register():
# 定义一个变量,用于控制外循环
site = True
# 执行循环, 用户名操作
while site:
# 用户输入用户名
username = input('欢迎注册,请输入用户名:')

# 用户名需要检测是否已经存在
if username in userlist:
print('当前用户已经存在,请更换用户名。')
else:
...
register()

总之,代码的实现是需要一步一步细细琢磨的,而且实现的方式也不是只有一种。要考虑逻辑,效率等等因素。文章最后会给到我的源码文件,现在大家先一步步的跟着我往下走吧。

登录功能

在实现登录功能之前,我们来分析一下这个功能要做的事情:

  1. 需要使用已经注册的用户信息登录
  2. 密码输入错误 3 次之后,锁定账户信息(不能再使用这个账户进行登录操作)

这是两个最基本的功能,既然要核对用户信息和密码,那和注册一样,我们还是需要读取user.txt文件,拿到里面的信息后传给相应的变量,用于检测。那好,我们先把注册那边实现的部分功能拿过来,就无需自己再写一遍了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 搞定登录功能
# 专门定义数据变量,存放已经注册的用户信息
userdict = {}
userlist = []

# 读取所有的注册信息
with open('./data/user.txt', 'a+', encoding="utf-8") as fp:
fp.seek(0,0) # 调整指针到文件头
res = fp.readlines() # 按照每一行读取所有用户数据
for i in res: # 循环读取每一行数据
r = i.strip() # 处理每一行尾部的换行符
mydict = r.split(':')
userdict.update({mydict[0]:mydict[1]})
userlist = userdict.keys()
print(f'userdict:{userdict},\nuserlist:{userlist}')

---
userdict:{'admin': '123456', 'du': '654321', 'du2': '123456', 'zhang': '123456', 'wang': '123456'},
userlist:dict_keys(['admin', 'du', 'du2', 'zhang', 'wang']),

执行过后可以看到,三个变量已经分别存储了一个字典和两个列表,也就是字典存储了健值对的用户密码,以及将用户和密码再分别存储到两个列表内。

数据准备好了,接下来,我们开始定义函数吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 封装登录函数
def login():

# 获取用户登录时输入的用户名
username = input('欢迎登录,请输入您的用户名:')

# 检测当前用户名是否存在
if username in userlist:
# 让用户输入密码
pass
else:
# 用户名不存在
print('用户名错误,亲重新输入')
# 检测用户是否属于锁定状态

那么大的框架就定义好了。现在让我们逐步开始细化这些代码。

先不看让用户继续输入密码的逻辑,我们先来看看用户名不存在的逻辑该怎么做。

当用户名不存在的时候,我们肯定是先要告知提示用户,接下来应该是让用户重新输入一次用户名。那怎么做呢?之前实现注册那边实际上已经有经验了,用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
# 封装登录函数
def login():
# 自定义变量, 控制登录外循环
isLogin = True

# 创建循环
while isLogin:

# 获取用户登录时输入的用户名
username = input('欢迎登录,请输入您的用户名:')

# 检测当前用户名是否存在
if username in userlist:
while True:
# 让用户输入密码
pwd = input('请输入您的密码:')
# 检测用户输入的密码是否正确
if pwd == userdict[username]:
print(pwd)
isLogin = False # 结束外循环
break # 结束内循环
else:
print('您的密码输入有误。')
else:
# 用户名不存在
print('用户名错误,亲重新输入')

# 检测用户是否属于锁定状态

login()

在细化的代码中,我们用了和注册函数一样的方法,创建了一个外循环和一个内循环。原因就是我们分别需要判断用户名和密码,都需要返回来重新输入。

之前注册的循环已经讲的比较详细了,所以这里我们就不细致的讲解了,小伙伴们自己好好琢磨一下逻辑关系。

那到这里,我们还缺什么呢?看看需求,我们似乎还需要判断用户输入密码错误的次数对吧?

好的,让我们继续,从定义变量开始:

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
# 封装登录函数
def login():

...
# 定义变量,用来记录用户输入密码错误次数
errorNum = 3

# 创建循环
while isLogin:
...
if username in userlist:
while True:
...
if pwd == userdict[username]:
...
else:
# 密码错误,修改变量次数
errorNum -= 1
# 判断当前密码错误次数
if errorNum == 0:
print('给你机会你不中用啊细狗。账户已锁定,请联系管理人员并上供品。')
isLogin = False
break
else:
print(f'您的密码输入有误, 您还能再尝试{errorNum}次。')
else:
...

# 检测用户是否属于锁定状态

login()

我们定义了一个errorNum的变量,因为我们只能尝试最多三次,所以我们给这个变量设定了一个 3 的值。

在之后的循环中,没尝试错误一次,我们就让这个变量-1操作,直到变为 0 为止。

然后,就是我们需要定义一个黑名单把用户名写入了。

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
# 封装登录函数
def login():
...
# 创建循环
while isLogin:
if username in userlist:
while True:
...
if pwd == userdict[username]:
...
else:
# 密码错误,修改变量次数
errorNum -= 1
# 判断当前密码错误次数
if errorNum == 0:
print('给你机会你不中用啊细狗。账户已锁定,请联系管理人员并上供品。')
# 锁定当前账户,把锁定的用户拉入黑名单
with open('./data/black.txt', 'a+', encoding='UTF-8') as fp:
fp.write(username+'\n')
isLogin = False
break
else:
print(f'您的密码输入有误, 您还能再尝试{errorNum}次。')
else:
...

login()

---
您的密码输入有误, 您还能再尝试 2
您的密码输入有误, 您还能再尝试 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
37
38
39
40
# 搞定登录功能
# 专门定义数据变量,存放已经注册的用户信息
...
blackUserList = [] # 定义一个小黑屋专用变量

# 读取所有的注册信息
with open('./data/user.txt', 'a+', encoding="utf-8") as fp:
...

# 读取所有黑名单用户
with open('./data/black.txt', 'a+', encoding='utf-8') as fp:
fp.seek(0,0)
res = fp.readlines()
for i in res:
r = i.strip() #
blackUserList.append(r)

# 封装登录函数
def login():
...
# 创建循环
while isLogin:

# 获取用户登录时输入的用户名
username = input('欢迎登录,请输入您的用户名:')

# 检测当前用户名是否存在
if username in blackUserList:
print('您的账户已经被锁定,并且还未给管理员上供品。')
isLogin = False # 结束外循环
elif username in userlist:
while True:
...
else:
# 用户名不存在
...

# 检测用户是否属于锁定状态

login()

功能合并

现在,我们把注册和登录功能都分别完成了,那么我们现在就剩下最后一步,将两个功能合并在一起。

注册和登录的函数是现成的,那么现在我们要做的事情就是:

  1. 将读取数据的两个with方法封装到一个函数内
  2. 定义一个主函数,进入进程后就执行
  3. 告知用户选择登录还是注册
  4. 在执行登录注册函数之前,初始化数据(加载读取数据的函数)

OK,让我们来实现吧:

首先是封装读取数据的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义一个读取所有用户数据的函数
def readAllUsers():
# 读取所有的注册信息
with open('./data/user.txt', 'a+', encoding="utf-8") as fp:
global userdict
global userlist
fp.seek(0,0) # 调整指针到文件头
res = fp.readlines() # 按照每一行读取所有用户数据
for i in res: # 循环读取每一行数据
r = i.strip() # 处理每一行尾部的换行符
mydict = r.split(':')
userdict.update({mydict[0]:mydict[1]})
userlist = userdict.keys()

# 读取所有黑名单用户
with open('./data/black.txt', 'a+', encoding='utf-8') as fp:
global blackUserList
fp.seek(0,0)
res = fp.readlines()
for i in res:
r = i.strip() #
blackUserList.append(r)

注意我们这里用了global, 因为我们需要使用外部定义的变量,所以必须要讲变量改成全局变量,改动才会有效。

注册登录函数不用改动,直接原封不动的粘贴过来就可以了。再来我们就需要直接进入主进程进行选择:

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
# 判断当前脚本是否作为一个主进程脚本在执行
if __name__ == '__main__':
'''
这里的代码,只有在使用 Python 解释器直接运行时才会执行
如果当前脚本作为了模块被其他文件导入后使用,那么这个地方的代码不会执行
因此这个地方的代码,适合写当前脚本中的一些测试,这样不会影响其他脚本
'''
# 调用初始化方法,加载数据
readAllUsers()

isBegin = True
while isBegin:
myStr = '''
======================
** 登录(0) 注册(1)**
======================
'''
print(myStr)

# 让用户选择对应的操作
num = input('请输入对应的序号,体验功能:')
if num == '0':
login()
isBegin = False
elif num == '1':
register()
isBegin = False
else:
print('后续功能还在开发中。')
isBegin = False

这样当我们输入python3 file.py的时候,就会直接进入这个主程序内。然后执行加载数据的函数,打印用户选择内容,然后等待用户选择并执行后续操作,如图:

而这其中我们设定一个变量isBegin来控制循环,每次执行完一次函数就会结束循环,然后需要重新执行。主要原因就是因为加载数据的方法我们写在了while循环外面,所以当我们注册之后再去登录,新创建的用户并不在用户列表内,无法完成登录。所以干脆结束掉之后重新加载数据,就可以执行登录了。

能不能改善呢?可以。只是今天的课程目的已经完毕了,所以当作留给大家的练习题吧。如何优化整个程序让其更合理高效,大家试试看。然后记得在评论区给我留言。

好了,这节课到这里就结束了,相关代码可以在我的仓库里去找到。

下一节课中,我们会讲解模块,系统的内置模块。我们下次见。

14. 练习:登录注册系统

https://hivan.me/Exercise-register-system/

作者

Hivan Du

发布于

2023-08-10

更新于

2024-01-16

许可协议

评论