身份证判断年龄

本文最后更新于:2024年8月11日 晚上

实现基于身份证的年龄计算

思考

随便编的材料背景:

当今世界,电子海洛因仍然危害青少年身心健康成长,引发广大家长的不满。请写出一个程序(python only)
要求使用游戏产品的人输入他的身份证号,并对其是否年满18周岁进行判断。
如果已满18岁,输出False,如果未满18岁,输出True。
为了方便后续的使用,请尽可能使用函数封装模块,并配备注释。

需要考虑的问题:

  1. 直接使用身份证与现在的年份差不大精确,需要考虑月和日
  2. 需要对输入的身份证进行格式上的识别,因此需要使用到身份证的验证方法
  3. 如果用户输入错误的身份证应当提醒并重新让他输入

核心部分

最初的,我们先实现用户输入身份证号并校验的功能,重点在校验函数的设计上。

校验函数应该保证传入的身份证是正确的,否则会给后面的年龄计算带来麻烦,因此要校验以下几个点:

  1. 身份证是18位的
  2. 身份证末尾有时是X,用户输入可能为x或X
  3. 身份证满足一个校验公式,最后一位是校验码

因此考虑到用户可能随意输入奇怪的东西,将校验分为两个函数,第一个函数用来确保输入符合身份证格式,第二个函数对身份证的真实性进行检验。如下

1
2
3
4
5
6
7
def check_guardian(ic: str) -> bool:
if len(ic) == 18 and \
(ic[-1] == 'X' or ic[-1] == 'x' and ic[0:17].isdigit() or ic.isdigit()):
return is_real(ic)
else:
print('WARNING:身份证格式错误{code:-3}')
return False

《!》对输入字符串的前17位是数字进行校验,并对尾号是否是X再校验。如果通过则调用真实性检验函数,否则返回一个警告。真实性检验函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
def is_real(ic: str) -> bool:
# ic_list = [eval(x) for x in ic[0:-1]]
lam = lambda x: eval(x[0][0]) * x[1]
weight = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
contrast = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8',
5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'}
su = sum([lam(x) for x in zip(ic[0:17], weight)])
end = 'X' if ic[-1].isalpha() else ic[-1]
if contrast[su % 11] == end:
return True
else:
print("WARNING:身份证异常{code:-2}")
return False

计算方法见官方的身份证校验公式

《!》注释内容是荒废的方案。判断最终结果的时候发现如果用户输入的X大小写不同,会增加判断负担,因此在判断前提前把最后一位X替换成大写,否则一个字典是无法接受同一个键有两个值的。使用lambda函数是实践论的最后结果,至于为什么是x[0][0]在原理上我的水平很难给出结论。但是这么写比较简洁罢了。

年龄判断

有了正确的保卫函数,用户输入的内容必然是经过筛选后的基本符合格式的,因此直接抓取身份证的年-月-日部分,加上dateime模块得到当日日期即可。但是直接用年份差作为年龄不够严谨,因此先使用年份差,如果>19则直接判断,如果是18则应当判断当天月份>出生月?,如果为F则年龄应该-1,直接判断为未成年即可,同理,月相同则判断当日是否超过出生日。

<!>datetime模块的today函数返回一个数组(year, month, day),直接调用即可

考虑到这,我想也把身份证的年月日组合成一个数组,方便用下标调用,提高阅读性(当然也可能没有),因此设计得最终函数有点复杂。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def check_birthday(idcard_code: str) -> int:
import datetime
today = datetime.datetime.today() # datetime(year, month, day, hour, min, sec, seconds)
y_bir = {name: value for name, value in
zip(('year', 'month', 'day'),
(int(idcard_code[7:11]), int(idcard_code[11:13]), int(idcard_code[13:15])))}
age = today.year - y_bir['year']
if age > 19:
flag = 1
elif 18 > age > 0:
flag = 1
elif y_bir['month'] > today.month or y_bir['month'] == today.month and y_bir['day'] > today.day:
flag = 1
else:
print('WARNING:身份证异常{code:-1}')
flag = -1
return flag

《!》使用了zip函数把两个数组变成含二元数组对的列表,然后直接使用字典解析方法生成一个字典,之后直接按照键调用对用的年-月-日即可完成计算。在if逻辑上花费了一点心思,一开始是先判断 age>18然后再对=18部分做判断,后改为先判断<18在判断=18,等等,最终选定了现在的方案。

主体部分

不能只让用户输入一次身份证,如果输入错误了,应该弹出提示,然后让用户重新输入。因此我直接使用while函数和一个check_time来限定是否跳出循环。如果输入正确的身份证号,则让check_time=False然后结束循环,并进入到年龄判断的部分,如果没有则反复要求输入,但后来增加了次数限制(使用i然后自加来限制)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while not check_time and i < 5:
your_id = input("请输入你的18位身份证号-->")
print('您的输入的身份证号是-->{}'.format(your_id))
if check_guardian(your_id):
fly = check_birthday(your_id)
check_time = True if fly == 1 else False
i += 1
if i >= 5:
print('错误次数过多')
else:
if fly == 1:
print('您已满18岁')
else:
print('你未满18岁')

《!》之所以使用not是因为我想在身份证正确的时候函数都返回True,这样的话check_time就会重置为True

这就结束了吗?

显然,用户不可能随时准备着编译器来给我们打开python文件,而且一次输入一个身份证号,对于批量验证来说太慢了,因此我们考虑加入一个CMD输入文件路径然后批量验证的,以及一个从CMD输入身份证号进行判断的办法。

CMD

我们使用自带的模块argparse来完成CMD的捕获,如下:

1
2
3
4
5
6
7
8
9
10
import argparse

parser = argparse.ArgumentParser(description='使用CMD传入身份证进行判断')
parser.add_argument('--test_file', '-test', dest='file_path', type=str, default='./id_test.txt',
help='使用测试文档输入,默认使用同目录下的id_test.txt')
parser.add_argument('--cmd', '-c', dest='use_cmd', type=bool, default=False,
help='是否使用了CMD输入,默认为False,需要主动填写进入文档输入模式')
parser.add_argument('--id_card', '-id', dest='idcard', type=str, default='0',
help='输入的身份证号')
args = parser.parse_args()

因为要考虑与现有代码结构的融合,又因为要求从CMD捕获身份证号,因此不能重复弹出输入身份证的提示,加入一个use_cmd来让程序知道是否使用input()。以及捕获一个输入批量测试的文本路径。

以下是使用文本路径时的解析函数:

1
2
3
4
5
6
7
8
9
10
def file_process(file_path):
import os
if os.path.exists(file_path):
with open(file_path, 'r') as f:
sett = f.readlines()
sett.append(sett.pop() + '\n')
return [x.strip('\n') for x in sett]
else:
print('WARNING:测试文件不存在{code:-4}')
return ['666666199801016666']

使用了OS模块来判定该文件是否存在,不存在的返回一个默认值,弹出警告。存在的话就直接对内容进行处理,然后组成列表输出。然后稍微修改一下原来的核心代码即可。

最终结果

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
"""材料背景:
当今世界,电子海洛因仍然危害青少年身心健康成长,引发广大家长的不满。请写出一个程序(python only),
要求使用游戏产品的人输入他的身份证号,并对其是否年满18周岁进行判断。
如果已满18岁,输出False,如果未满18岁,输出True。
为了方便后续的使用,请尽可能使用函数封装模块,并配备注释。
"""

import argparse

parser = argparse.ArgumentParser(description='使用CMD传入身份证进行判断')
parser.add_argument('--test_file', '-test', dest='file_path', type=str, default='./id_test.txt',
help='使用测试文档输入,默认使用同目录下的id_test.txt')
parser.add_argument('--cmd', '-c', dest='use_cmd', type=bool, default=False,
help='是否使用了CMD输入,默认为False,需要主动填写进入文档输入模式')
parser.add_argument('--id_card', '-id', dest='idcard', type=str, default='0',
help='输入的身份证号')
args = parser.parse_args()


def file_process(file_path):
import os
if os.path.exists(file_path):
with open(file_path, 'r') as f:
sett = f.readlines()
sett.append(sett.pop() + '\n')
return [x.strip('\n') for x in sett]
else:
print('WARNING:测试文件不存在{code:-4}')
return ['666666199801016666']


def is_real(ic: str) -> bool:
# ic_list = [eval(x) for x in ic[0:-1]]
lam = lambda x: eval(x[0][0]) * x[1]
weight = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
contrast = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8',
5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'}
su = sum([lam(x) for x in zip(ic[0:17], weight)])
end = 'X' if ic[-1].isalpha() else ic[-1]
if contrast[su % 11] == end:
return True
else:
print("WARNING:身份证异常{code:-2}")
return False


def check_guardian(ic: str) -> bool:
if len(ic) == 18 and \
(ic[-1] == 'X' or ic[-1] == 'x' and ic[0:17].isdigit() or ic.isdigit()):
return is_real(ic)
else:
print('WARNING:身份证格式错误{code:-3}')
return False


def check_birthday(idcard_code: str) -> int:
import datetime
today = datetime.datetime.today() # datetime(year, month, day, hour, min, sec, seconds)
y_bir = {name: value for name, value in
zip(('year', 'month', 'day'),
(int(idcard_code[7:11]), int(idcard_code[11:13]), int(idcard_code[13:15])))}
age = today.year - y_bir['year']
if age > 19:
flag = 1
elif 18 > age > 0:
flag = 1
elif y_bir['month'] > today.month or y_bir['month'] == today.month and y_bir['day'] > today.day:
flag = 1
else:
print('WARNING:身份证异常{code:-1}')
flag = -1
return flag


if __name__ == '__main__':
use_cmd = False
check_time = False
your_id = '666666199801016666'
fly = 0
i = 0
if args.use_cmd:
for your_id in file_process(args.file_path):
if check_guardian(your_id):
fly = check_guardian(your_id)
if fly == 1:
print('身份证号--{}--已满18岁'.format(your_id))
elif fly == -1:
print('身份证号--{}--WARNING发生未知错误'.format(your_id))
else:
print('身份证号--{}--未满18岁')
else:
while not check_time and i < 5:
your_id = args.idcard if eval(args.idcard) else input("请输入你的18位身份证号-->")
print('您的输入的身份证号是-->{}'.format(your_id))
args.idcard = '0'
if check_guardian(your_id):
fly = check_birthday(your_id)
check_time = True if fly == 1 else False
i += 1
if i >= 5:
print('错误次数过多')
else:
if fly == 1:
print('您已满18岁')
else:
print('你未满18岁')

排障

list(str)会导致字符串意外分割问题

—>解决方案:

使用 list.insert(n,str)在n位插入字符串的方法

使用str.split()的切割法,也能返回字符串

列表解析式问题

对于f(x,y) for x in A for y in B意味着

1
2
3
for x in A:
for y in B:
f(x,y)

列表删除问题

pop实际上就是栈的弹出,弹出一个元素,意味着删除,同时也返回了这个元素

因此对列表最后一个元素的重命名操作可以这么写

list.append(list.pop()+'\n'):给最后一个元素加上换行符

Zip函数问题

zip(A,B)本质是把A、B两个元素一一对应的部分形成一个元组,但是必须加上dict或者list在指明它最后的形式,否则只会返回函数位置。如

1
2
3
4
5
6
A = '1,2,3,4'
B = '5,6,7,8'
C = list(zip(A, B))
print(C)
#返回
#[('1', '5'), (',', ','), ('2', '6'), (',', ','), ('3', '7'), (',', ','), ('4', '8')]

身份证判断年龄
https://qlozin.top/2022/08/06/身份证识别/
作者
Qlozan
发布于
2022年8月7日
更新于
2024年8月11日
许可协议