面向对象编程
把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。
面向对象思想的三大要素:封装、继承、多态。
类和对象
类是对象的蓝图和模板,而对象是类的实例。类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。
定义类
用class关键字定义类,然后在类中用函数定义方法描述对象的动态特征:
class Student(object):
def __init__(self,name,age):
self.name=name
self.age=age
def study(self,course_name):
print('%s正在学习%s.' % (self.name,course_name))
def watch_tv(self):
if self.age<18:
print('%s正在观看《今日说法-法外狂徒版》.'%self.name)
else:
print('%s正在观看《今日说法-律师版》.'%self.name)
上面代码块我们定义了一个Student类,写在类中的函数,我们通常称之为(对象的)方法,这些方法就是对象可以接收的消息。里面的__init__是一个特殊方法,用于创建对象时进行初始化。
创建和使用对象
def main():
#创建学生对象
stu1=Student('张三',14)
#调用对象的study方法
stu1.study('如何成为法外狂徒')
#调用对象的watch_tv方法
stu1.watch_tv()
stu2=Student('罗老师',666)
stu2.study('如何完虐法外狂徒')
stu2.watch_tv()
if __name__=='__main__':
main()
'''
输出结果:
张三正在学习如何成为法外狂徒.
张三正在观看《今日说法-法外狂徒版》.
罗老师正在学习如何完虐法外狂徒.
罗老师正在观看《今日说法-律师版》.
'''
访问权限
在Python中,属性和方法的访问权限有两种,公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头
class Test:
def __init__(self, foo):
self.__foo=foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)
if __name__ == "__main__":
main()
Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来“妨碍”对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。
class Test:
def __init__(self,foo):
self.__foo=foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test=Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__=='__main__':
main()
'''
输出结果:
hello
__bar
hello
'''
在实际开发中,并不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻
@property装饰器
将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作,可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便
class Person(object):
def __init__(self,name,age):
self._name=name
self._age=age
#getter方法
@property
def name(self):
return self._name
#getter方法
@property
def age(self):
return self._age
#setter方法
@age.setter
def age(self,age):
self._age=age
def play(self):
if self._age<18:
print('%s正在玩飞行棋'%self._name)
else:
print('%s正在玩斗地主'%self._name)
def main():
person=Person('张三',14)
person.play()
person.age=22
person.play()
person.name = '罗老师'
if __name__ == '__main__':
main()
'''
输出结果:
张三正在玩飞行棋
张三正在玩斗地主
Traceback (most recent call last):
File "H:/代码库/python/test/test.py", line 27, in <module>
main()
File "H:/代码库/python/test/test.py", line 25, in main
person.name = '罗老师' # AttributeError: can't set attribute
AttributeError: can't set attribute
'''
上面的代码块出现了赋值错误,原因是person.name没有调用setter方法
__slots__限定
限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量,__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
class Person(object):
__slots__ = ('_name', '_age', '_gender')
def __init__(self,name,age):
self._name=name
self._age=age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self,age):
self._age=age
def play(self):
if self._age<18:
print('%s正在玩飞行棋'%self._name)
else:
print('%s正在玩斗地主'%self._name)
def main():
person=Person('张三',14)
person.play()
person._gender='男'
person._weight=120
if __name__ == '__main__':
main()
'''
输出结果:
张三正在玩飞行棋
Traceback (most recent call last):
File "H:/代码库/python/test/test.py", line 27, in <module>
main()
File "H:/代码库/python/test/test.py", line 25, in main
person._weight=120
AttributeError: 'Person' object has no attribute '_weight'
'''
上面代码块开头就限定了Person对象只能绑定_name,_age,_gender属性,所以当要新增_weight时会出现报错
静态方法和类方法
我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题。
from math import sqrt
class Triangle(object):
def __init__(self,a,b,c):
self._a=a
self._b=b
self._c=c
@staticmethod
def is_valid(a,b,c):
return a+b>c and b+c>a and a+c>b
def perimter(self):
return self._a+self._b+self._c
def area(self):
half=self.perimter()/2
return sqrt(half * (half - self._a) * (half - self._b) * (half - self._c))
def main():
a,b,c=3,4,5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a,b,c):
t=Triangle(a,b,c)
print(t.perimter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法工程三角形')
if __name__ == '__main__':
main()
'''
输出结果:
12
6.0
'''
在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象
from time import time,localtime,sleep
class Clock(object):
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
@classmethod
def now(cls):
ctime=localtime(time())
return cls(ctime.tm_hour,ctime.tm_min,ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
return '%02d:%02d:%02d' % (self._hour, self._minute, self._second)
def mian():
# 通过类方法创建对象并获取系统时间
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
mian()
'''
输出结果:
16:41:25
16:41:26
16:41:27
16:41:28
16:41:29
16:41:30
'''
类之间的关系
- is-a关系:也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
- has-a关系:通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
- use-a关系:通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。
继承和多态
让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为里氏替换原则。
class Person(object):
"""人"""
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍.' % self._name)
def watch_tv(self):
if self._age >= 18:
print('%s正在观看《今日说法-学生版》.' % self._name)
else:
print('%s只能观看《今日说法-教师版》.' % self._name)
class Student(Person):
"""学生"""
#继承Person类
def __init__(self, name, age, grade):
super().__init__(name, age)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, grade):
self._grade = grade
def study(self, course):
print('%s的%s正在学习%s.' % (self._grade, self._name, course))
class Teacher(Person):
"""老师"""
#继承Person类
def __init__(self, name, age, title):
super().__init__(name, age)
self._title = title
@property
def title(self):
return self._title
@title.setter
def title(self, title):
self._title = title
def teach(self, course):
print('%s的%s正在讲%s.' % (self._name, self._title, course))
def main():
stu = Student('张三', 15, '初中学历')
stu.study('数学')
stu.watch_tv()
t = Teacher('罗老师', 38, '博导')
t.teach('Python程序设计')
t.watch_tv()
if __name__ == '__main__':
main()
'''
输出结果:
初中学历的张三正在学习数学.
张三只能观看《今日说法-教师版》.
罗老师的博导正在讲Python程序设计.
罗老师正在观看《今日说法-学生版》.
'''
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
from abc import ABCMeta,abstractmethod
class Pet(object,metaclass=ABCMeta):
def __init__(self,nickname):
self._nickname=nickname
@abstractmethod
def make_voice(self):
pass
class Dog(Pet):
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
'''
输出结果:
旺财: 汪汪汪...
凯蒂: 喵...喵...
大黄: 汪汪汪...
'''
上面的代码块中将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。Dog和Cat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。
文件读写和异常
在实际开发中,常常需要对程序中的数据进行持久化操作,而实现数据持久化最直接简单的方式就是将数据保存到文件中。通过Python内置的open函数,我们可以指定文件名、操作模式、编码信息等来获得操作文件的对象,接下来就可以对文件进行读写操作了。这里所说的操作模式是指要打开什么样的文件(字符文件还是二进制文件)以及做什么样的操作(读、写还是追加)。
操作模式 | 具体含义 |
---|---|
'r' | 读取 (默认) |
'w' | 写入(会先截断之前的内容) |
'x' | 写入,如果文件已经存在会产生异常 |
'a' | 追加,将内容写入到已有文件的末尾 |
'b' | 二进制模式 |
't' | 文本模式(默认) |
'+' | 更新(既可以读又可以写) |
文本操作模式流程图

读取文件
读取文本文件时,需要在使用open函数时指定好带路径的文件名(可以使用相对路径或绝对路径)并将文件模式设置为'r'(如果不指定,默认值也是'r'),然后通过encoding参数指定编码(如果不指定,默认值是None,那么在读取文件时使用的是操作系统默认的编码),如果不能保证保存文件时使用的编码方式与encoding参数指定的编码方式是一致的,那么就可能因无法解码字符而导致读取失败。
def main():
f=open('test.txt','r',encoding='utf-8')
print(f.read())
f.close()
if __name__ == '__main__':
main()
'''
输出结果:
Hello world!
Show time!
'''
在上面的代码中,如果open函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理。
def main():
f = None
try:
f = open('test.txt', 'r', encoding='utf-8')
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
finally:
if f:
f.close()
if __name__ == '__main__':
main()
'''
输出结果:
Hello world!
Show time!
'''
将那些在运行时可能会出现状况的代码放在try代码块中,在try代码块的后面可以跟上一个或多个except来捕获可能出现的异常状况。在上面读取文件的过程中,文件找不到会引发FileNotFoundError,指定了未知的编码会引发LookupError,而如果读取文件时无法按指定方式解码会引发UnicodeDecodeError,我们在try后面跟上了三个except分别处理这三种不同的异常状况。最后我们使用finally代码块来关闭打开的文件,释放掉程序中获取的外部资源,由于finally块的代码不论程序正常还是异常都会执行到(甚至是调用了sys模块的exit函数退出Python环境,finally块都会被执行,因为exit函数实质上是引发了SystemExit异常),因此我们通常把finally块称为“总是执行代码块”,它最适合用来做释放外部资源的操作。如果不愿意在finally代码块中关闭文件对象释放资源,也可以使用上下文语法,通过with关键字指定文件对象的上下文环境并在离开上下文环境时自动释放文件资源。
def main():
try:
with open('test.txt', 'r', encoding='utf-8') as f:
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
if __name__ == '__main__':
main()
'''
输出结果:
Hello world!
Show time!
'''
除了使用文件对象的read方法读取文件之外,还可以使用for-in循环逐行读取或者用readlines方法将文件按行读取到一个列表容器中
import time
def main():
# 一次性读取整个文件内容
with open('test.txt','r',encoding='utf-8') as f:
print(f.read())
# 通过for-in循环逐行读取
with open('test.txt',mode='r') as f:
for line in f:
print(line,end='')
time.sleep(0.5)
print()
# 读取文件按行读取到列表中
with open('test.txt') as f:
lines=f.readlines()
print(lines)
if __name__ == '__main__':
main()
'''
输出结果:
Hello world!
Show time!
Hello world!
Show time!
['Hello world!\n', 'Show time!']
'''
写入文件
使用open函数时指定好文件名并将文件模式设置为'w'即可写入文件,如果需要对文件内容进行追加式写入,应该将模式设置为'a'。如果要写入的文件不存在会自动创建文件而不是引发异常。
from math import sqrt
def main():
str='正在写入文件......\n已经写入文件......'
try:
#写入文件
with open('test.txt','w',encoding='utf8') as f:
f.write(str)
#读取文件
with open('test.txt', 'r', encoding='utf-8') as f:
print(f.read())
except IOError as ex:
print(ex)
print('写文件时发生错误!')
if __name__ == '__main__':
main()
'''
输出结果:
正在写入文件......
已经写入文件......
'''
读写二进制文件
模式b可读写二进制文件,下面代码实现复制图片功能
def main():
try:
with open('test.png','rb') as f1:
data=f1.read()
print(type(data))
with open('testcopy.png','wb') as f2:
f2.write(data)
except FileNotFoundError as e:
print('指定的文件无法打开.')
except IOError as e:
print('读写文件时出现错误.')
print('程序执行结束.')
if __name__ == '__main__':
main()
'''
输出结果:
<class 'bytes'>
程序执行结束.
'''

读写json文件
JSON是“JavaScript Object Notation”的缩写,它本来是JavaScript语言中创建对象的一种字面量语法,现在已经被广泛的应用于跨平台跨语言的数据交换,原因很简单,因为JSON也是纯文本,任何系统任何编程语言处理纯文本都是没有问题的。目前JSON基本上已经取代了XML作为异构系统间交换数据的事实标准。
json模块主要有四个比较重要的函数,分别是:
- dump:将Python对象按照JSON格式序列化到文件中
- dumps:将Python对象处理成JSON格式的字符串
- load:将文件中的JSON数据反序列化成对象
- loads:将字符串的内容反序列化成Python对象
序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)。
import json
def main():
mydict = {
'name': '张三',
'age': 38,
'qq': 957658,
'friends': ['李四', '王五'],
'cars': [
{'brand': 'BYD', 'max_speed': 180},
{'brand': 'Audi', 'max_speed': 280},
{'brand': 'Benz', 'max_speed': 320}
]
}
try:
with open('test.json', 'w', encoding='utf-8') as f:
json.dump(mydict, f)
with open('test.json', 'r', encoding='utf-8') as f:
print(json.loads(f.read()))
except IOError as e:
print(e)
if __name__ == '__main__':
main()
'''
输出结果:
{'name': '张三', 'age': 38, 'qq': 957658, 'friends': ['李四', '王五'], 'cars': [{'brand': 'BYD', 'max_speed': 180}, {'brand': 'Audi', 'max_speed': 280}, {'brand': 'Benz', 'max_speed': 320}]}
'''
正则表达式
正则字符表
符号 | 解释 | 示例 | 说明 |
---|---|---|---|
. | 匹配任意字符 | b.t | 可以匹配bat / but / b#t / b1t等 |
\w | 匹配字母/数字/下划线 | b\wt | 可以匹配bat / b1t / b_t等 但不能匹配b#t |
\s | 匹配空白字符(包括\r、\n、\t等) | love\syou | 可以匹配love you |
\d | 匹配数字 | \d\d | 可以匹配01 / 23 / 99等 |
\b | 匹配单词的边界 | \bThe\b | |
^ | 匹配字符串的开始 | ^The | 可以匹配The开头的字符串 |
$ | 匹配字符串的结束 | .exe$ | 可以匹配.exe结尾的字符串 |
\W | 匹配非字母/数字/下划线 | b\Wt | 可以匹配b#t / b@t等 但不能匹配but / b1t / b_t等 |
\S | 匹配非空白字符 | love\Syou | 可以匹配love#you等 但不能匹配love you |
\D | 匹配非数字 | \d\D | 可以匹配9a / 3# / 0F等 |
\B | 匹配非单词边界 | \Bio\B | |
[] | 匹配来自字符集的任意单一字符 | [aeiou] | 可以匹配任一元音字母字符 |
[^] | 匹配不在字符集中的任意单一字符 | [^aeiou] | 可以匹配任一非元音字母字符 |
* | 匹配0次或多次 | \w* | |
+ | 匹配1次或多次 | \w+ | |
? | 匹配0次或1次 | \w? | |
{N} | 匹配N次 | \w{3} | |
{M,} | 匹配至少M次 | \w{3,} | |
{M,N} | 匹配至少M次至多N次 | \w{3,6} | |
| | 分支 | foo|bar | 可以匹配foo或者bar |
(?#) | 注释 | ||
(exp) | 匹配exp并捕获到自动命名的组中 | ||
(? <name>exp) | 匹配exp并捕获到名为name的组中 | ||
(?:exp) | 匹配exp但是不捕获匹配的文本 | ||
(?=exp) | 匹配exp前面的位置 | \b\w+(?=ing) | 可以匹配I'm dancing中的danc |
(?<=exp) | 匹配exp后面的位置 | (?<=\bdanc)\w+\b | 可以匹配I love dancing and reading中的第一个ing |
(?!exp) | 匹配后面不是exp的位置 | ||
(?<!exp) | 匹配前面不是exp的位置 | ||
*? | 重复任意次,但尽可能少重复 | a.*b a.*?b | 将正则表达式应用于aabab,前者会匹配整个字符串aabab,后者会匹配aab和ab两个字符串 |
+? | 重复1次或多次,但尽可能少重复 | ||
?? | 重复0次或1次,但尽可能少重复 | ||
{M,N}? | 重复M到N次,但尽可能少重复 | ||
{M,}? | 重复M次以上,但尽可能少重复 |
如果需要匹配的字符是正则字符,可以用\进行转义
python对正则表达式的支持
re模块支持正则表达式相关操作,下面是re模块中的核心函数。
函数 | 说明 |
---|---|
compile(pattern, flags=0) | 编译正则表达式返回正则表达式对象 |
match(pattern, string, flags=0) | 用正则表达式匹配字符串 成功返回匹配对象 否则返回None |
search(pattern, string, flags=0) | 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None |
split(pattern, string, maxsplit=0, flags=0) | 用正则表达式指定的模式分隔符拆分字符串 返回列表 |
sub(pattern, repl, string, count=0, flags=0) | 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数 |
fullmatch(pattern, string, flags=0) | match函数的完全匹配(从字符串开头到结尾)版本 |
findall(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表 |
finditer(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器 |
purge() | 清除隐式编译的正则表达式的缓存 |
re.I / re.IGNORECASE | 忽略大小写匹配标记 |
re.M / re.MULTILINE | 多行匹配标记 |
实际开发中也可以用正则表达式对象的方法替代对这些函数的使用,如果一个正则表达式需要重复的使用,可以先通过compile函数编译正则表达式并创建出正则表达式对象。
re模块的正则表达式相关函数中都有一个flags参数,它代表了正则表达式的匹配标记,可以通过该标记来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等。如果需要为flags参数指定多个值,可以使用按位或运算符进行叠加,如flags=re.I | re.M。
验证用户名和QQ是否有效
import re
def main():
username=input('输入用户名:')
qq=input('输入QQ号码:')
# match函数的第一个参数是正则表达式字符串或正则表达式对象
# 第二个参数是要跟正则表达式做匹配的字符串对象
# r参数为原始字符串,无需转义字符\
m1=re.match(r'^[0-9a-zA-Z_]{6,20}$',username)
if not m1:
print('输入有效用户名')
m2=re.match(r'^[1-9]\d{4,11}$',qq)
if not m2:
print('输入有效QQ号码')
if m1 and m2:
print('输入信息有效')
if __name__ == '__main__':
main()
'''
输出结果:
输入用户名:wakamizu
输入QQ号码:123456
输入信息有效
'''
List of learning reference documents:
- https://github.com/lovevantt/Python-100-Days
- https://www.liaoxuefeng.com/wiki/1016959663602400