1、Python语言概述
1.1、转义字符
1.1.1、常用的转义字符
\t | 一个制表位,实现对齐的功能 |
\n | 换行符 |
\\ | 一个\ |
\' | 一个' |
\" | 一个" |
\r | 一个回车 |
1.2、注释
1.2.1、单行注释
# + 其他
1.2.2、多行注释
三个单引号 ''' + xxx + ''' 或 三个双引号 """ + xxx + """
1.2.3、文件编码声明注释
# coding:编码
# coding:utf-8
## 用途:用于指定文件的编码格式(放在文件开头)
2、变量
2.1、变量基本原理
变量三要素 = 类型 + 名称 + 值
2.2、格式化输出
2.2.1、%操作
age = 80
score = 70.5
gender = 'male'
name = 'John'
print("info: %s %d %s %.1f" % (name, age, gender, score))
2.2.2、format()函数
print("info: {} {} {} {} ".format(name, age, gender, score))
2.2.3、f-strings
print(f"info: {name} {age} {score} {gender}")
2.3、加法运算
- 左右两边都是数值型时,则做加法运算。
- 左右两边都是字符串时,则做拼接运算。
2.4、数据类型
2.4.1、通过使用 type() 来查看数据的数据类型
type(object)
2.4.2、整数类型
2.4.2.1、进制
2.4.2.2、Python中的整型占多少个字节
字节(byte):计算机中基本存储单位。
位(bit):计算机中的最小存储单位。
1byte = 8 bit
- 字节数随着数字的增大而增大(Python整型是变长的)。
- 每次的增量是4个字节。
2.5、浮点类型
- 其实也就是小数。
- 科学计数法表示:
# 5.12乘以10的2次方 5.12e2 # 5.12除以10的2次方 5.12E-2
- 浮点类型计算后,存在精度的损失,可以使用 Decimal 类进行精确计算。
b = 8.1 / 3 # 2.7 # 解决方法 from decimal import Decimal b = Decimal("8.1") / Decimal("3")
2.6、布尔类型
2.6.1、知识点
- 布尔类型也叫bool类型,取值为 True 和 False。
- bool类型适用于逻辑运算,一般用于流程控制。
- bool在运算中,True为1,False为0。
- 在Python中,非0值被视为真值,0值被视为假值。
2.7、字符串类型
2.7.1、知识点
- 使用引号(单双引号都可以)包括起来,创建字符串。
- 在字符串前面加 'r' 可以使整个字符串不会被转义。
2.7.2、字符串的驻留机制
- 相同值的变量指向同一个内存地址。
2.8、数据类型转换
2.8.1、隐式类型转换
- 在运算的时候,数据类型会向高精度自动转换。
2.8.2、显式类型转换
- 借助一些函数来做转换。
3、运算符
3.1、运算符介绍
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- 赋值运算符
- 位运算符
3.2、算术运算符
3.2.1、知识点
3.3、比较运算符
3.3.1、知识点
- 比较运算符的结果要么是 True,要么是 False。
3.4、逻辑运算符
3.4.1、知识点
假设:a=10,b=20
3.5、赋值运算符
3.5.1、知识点
- 赋值运算符就是将某个运算后的值,赋给指定的变量。
3.6、三元运算符
3.6.1、知识点
- 语法是:max = a if a > b else b
3.7、运算符的优先级
3.8、标识符的命名规则和命名规范
- 凡是可以自己起名字的地方都叫标识符。
3.9、关键字
- 被Python语言赋予了特殊含义,用作专门用途的字符串(单词)。这些关键字不要用做标识符了。
3.10、键盘输入
- 需要接收用户输入的数据,可以使用键盘输入语句来获取。
- 一般使用:input()函数。
- 从控制台接收到的数据类型是 字符串类型。
4、进制(先跳过,后续补充!)
5、程序控制结构
三大流程控制语句:
- 顺序控制。
- 分支控制。
- 循环控制。
5.1、顺序控制
5.1.1、知识点
- 程序从上到下逐行地执行,中间没有任何判断和跳转。
5.2、分支控制if-else
5.2.1、单分支
if 条件表达式:
代码块(可以有多条语句)
5.2.2、双分支
if 条件表达式:
执行代码块1
else:
执行代码块2
5.2.3、多分支
if 条件表达式:
执行代码块1
elif:
执行代码块2
......
else:
执行代码块n+1
5.3、嵌套分支
- 在一个分支结构中又嵌套了另一个分支结构。
5.4、for循环控制
5.4.1、知识点
for <变量> in <范围/序列>:
<循环操作语句>
5.5、while循环控制
while 判断条件:
循环操作语句
5.6、多重循环
思路如下:
- 1、矩形:
# i控制层数 for i in range(1, 6): # j控制每层输出的*的个数 for j in range(1, 6): # 这里的end=''表示输出不换行 print("*", end='') # 表示每层输出后换行 print('')
- 直角三角形:
# i控制层数 for i in range(1, 6): # j控制每层输出的*的个数 for j in range(i): # 这里的end=''表示输出不换行 print("*", end='') # 表示每层输出后换行 print('')
- 金字塔:
# i控制层数 for i in range(1, 6): for k in range(5-i): print(' ', end='') # j控制每层输出的*的个数 for j in range(2*i-1): # 这里的end=''表示输出不换行 print("*", end='') # 表示每层输出后换行 print('')
- 空心金字塔:
# i控制层数 for i in range(1, 6): for k in range(5-i): print(' ', end='') # j控制每层输出的*的个数 for j in range(2*i-1): # 这里的end=''表示输出不换行 if j == 0 or j == 2*i-1-1 or i==5: print("*", end='') else: print(' ', end='') # 表示每层输出后换行 print('')
最终代码如下:
total_level = int(input())
# i控制层数
for i in range(1, total_level+1):
for k in range(total_level-i):
print(' ', end='')
# j控制每层输出的*的个数
for j in range(2*i-1):
# 这里的end=''表示输出不换行
if j == 0 or j == 2*i-1-1 or i==total_level:
print("*", end='')
else:
print(' ', end='')
# 表示每层输出后换行
print('')
5.7、break语句
- 终止循环用的语句。
5.8、continue语句
- continue语句用于for或while循环所嵌套的代码中。
- continue语句用于结束本次循环,继续执行循环的下一个轮次。
5.9、return语句
- return使用在函数,表示跳出所在的函数。
- 注意,break语句只是结束当前循环,而return语句直接跳出了这个函数。
6、函数
6.1、知识点
- 普通传参:
def xxx(args): xxx
- 传入多个的位置参数:
def xxx(*args): xxx
- 传入关键字可变参数:
def xxx(**args): xxx
6.2、函数的传参机制
6.3、函数的递归调用
- 简单来讲,递归就是函数自己调用自己,每次调用时传入不同的值。
- 递归有助于编程者解决复杂问题,同时可以让代码变得简洁。
6.3.1、递归练习1
# 例子:
def test(n):
if n > 2:
test(n - 1)
print("n=", n)
test(4)
# 输出如下:
n = 2
n = 3
n = 4
6.3.2、递归练习2
# 例子:
def test(n):
if n > 2:
test(n - 1)
else:
print("n=", n)
test(4)
# 输出如下:
n = 2
6.3.3、递归练习3(阶乘问题)
def factorial(n):
if n == 1:
return 1
else:
return factorial(n-1)*n
print(factorial(4))
# 结果:
-----------
n = 4
return f(3)*4
n = 3
return f(2)*3
n = 2
return f(1)*2
n = 1
return 1
-----------
24
6.3.4、递归的重要规则
6.3.5、递归练习
(1)请使用递归的方式求出斐波那契数1,1,2,3,5,8,13...给你一个整数n,求出它的值是多少?
def fbn(n):
if n == 1 or n == 2:
return 1
else:
return fbn(n-1) + fbn(n-2)
print(fbn(3))
# 结果:
2
(2)猴子吃桃子问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,并再多吃一个。当到第十天时,想再吃时(即还没吃),发现只有一个桃子了。问:最初有几个桃子?
# day == 10, 有桃子数1
# day == 9, day9的桃子数 - (day9的桃子数/2 + 1)= day10的桃子数
--> day == 9, 有桃子数:(day10桃子数+1)*2
def peach(day):
if day == 10:
return 1
else:
return (peach(day+1)+1) * 2
print(peach(1))
# 结果
# 1534
(3)求函数值,已知 f(1)=3; f(n)=2 * f(n-1) + 1;请使用递归的思想,求出 f(n) 的值。
def f(n):
if n == 1:
return 3
else:
return 2 * f(n-1) + 1
print(f(10))
# 2047
6.4、函数作为参数传递
在编写函数中,参数可以传一个函数。
需注意:
- 函数作为参数传递,传递的不是数据,而是业务处理逻辑。
- 一个函数,可以接收多个函数作为参数传入。
6.5、lambda匿名函数(临时函数)
6.5.1、函数的定义
- def 关键字,可以定义带有名称的函数,可以重复使用。
- lambda关键字,可以定义匿名函数(无名称),匿名函数只能使用一次。
- 匿名函数用于临时创建一个函数,只使用一次的场景。
6.5.2、匿名函数基本语法
- lambda 形参列表:函数体(一行代码)。
- lambda关键字,表示定义匿名函数。
- 形参列表:比如 num1, num2 表示接收两个参数。
- 函数体:完成的功能,只能写一行,不能写多行代码。
6.5.3、应用实例
编写一个函数,可以接收一个匿名函数和两个数,通过匿名函数计算,返回两个数的最大值。
def f1(fun, num1, num2):
'''
功能:调用fun,返回num1和num2的最大值
: fun: 接收函数(匿名的)
:num1:
: num2:
: return:
'''
return fun(num1, num2)
max_val = f1(lambda a,b: a if a > b else b, 12, 10)
print(max_val)
# 12
6.6、全局变量和局部变量
- 全局变量:在整个程序范围内都可以访问,定义在函数外,拥有全局作用域的变量。
- 局部变量:只能在其被声明的函数范围内访问,定义在函数内部,拥有局部作用域的变量。
注意:
(1)未在函数内部重新定义n1,那么默认使用全局变量n1。
(2)在函数内部重新定义了n1,那么根据就近原则,使用的就是函数内部重新定义的n1。
(3)在函数内部使用global 关键字,可以标明指定使用全局变量。
7、数据容器
7.1、为什么需要数据容器
便于添加删减数据、便于代码的维护。
7.2、什么是数据容器
7.2.1、基本介绍
- 数据容器是一种数据类型,有些地方也简称为容器。
- 数据容器可以存放多个数据,每一个数据也被称为一个元素。
- 存放的数据 / 元素可以是任意类型。
- 简单地说,数据容器就是一种可以存放多个数据 / 元素的数据类型。
7.2.2、分类
- 列表(list)
- 元组(tuple)
- 字符串(str)
- 集合(set)
- 字典(dict)
7.3、列表 - List
7.3.1、基本介绍
- 列表可以存放多个不同类型数据,即:列表就是一列数据(多个数据)。
- 列表也是一种数据类型。
7.3.2、列表的定义
7.3.3、列表的使用
7.3.4、列表的遍历
简单的说,就是将列表的每一个元素依次取出,进行处理的操作,就是遍历 / 迭代。
7.3.5、注意事项和使用细节
- 如果我们需要一个空列表,可以通过 [] ,或者 list() 方式来定义。
- 列表的元素可以有多个,而且数据类型没有限制,允许有重复元素,并且是有序的。
- 列表的索引 / 下标是从0开始的。
- 列表索引必须在指定范围内使用,否则会报错。
- 索引也可以从尾部开始,最后一个元素的索引为 -1,往前一位为 -2,以此类推。
- 通过 列表[索引]=新值 对数据进行更新,使用 列表.append(值) 方法来添加元素,使用 del 语句来删除列表的元素,注意不能超过有效索引范围。
- 列表是可变序列,即:列表的元素是可以修改的,修改后,列表变量指向地址不变,只是数据内容变化。简单来讲,整个列表的地址没变,但是值变更后的list[index]的地址变了。
-
list1 = [1,2,3] list2 = list1 list2[0] = 10 print(list1) print(list2) # 结果 [10,2,3] [10,2,3]
7.3.6、列表的常用操作
7.3.7、列表生成式
- 列表生成式就是“生成列表的公式”。
- 基本语法:
7.4、元素 - tuple
7.4.1、基本介绍
- 元组(tuple)可以存放多个不同类型数据,元组是不可变序列(不能用append()。insert()这样的方法,有获取某个索引值的方法,但不可以重新赋值)。
- 元组也是一种数据类型。
7.4.2、元组的定义
7.4.3、元组的使用
7.4.4、元组的遍历
简单的说,就是将元组的每一个元素依次取出,进行处理的操作,就是遍历 / 迭代。
7.4.5、注意事项和使用细节
- 如果我们需要一个空元组,可以通过 () ,或者 tuple() 方式来定义。
- 元组的元素可以有多个,而且数据类型没有限制(甚至可以嵌套元组),允许有重复元素,并且是有序的。
- 元组的 索引/下标 是从0开始的。
- 元组索引必须在指定范围内使用,否则报错。
- 元组是不可变序列。
- 可以修改元组内 list的内容(修改增加删除等)。
- 索引也可以从尾部开始。
- 定义只有一个元素的元组,需要带上逗号,否则就不是元组类型。
tuple_h = (100,)
7.4.6、元组常用操作
7.5、字符串 - str
7.5.1、基本介绍
- 在Python中处理文本数据是使用 str 对象,也成为字符串。字符串是由 Unicode 码位构成的不可变序列。
7.5.2、字符串支持索引
7.5.3、字符串的遍历
将字符串的每个元素依次取出,进行处理的操作,就是遍历 / 迭代。
7.5.4、注意事项和使用细节
- 字符串索引必须在指定范围内使用,否则报错。索引可以从前开始,也可以从后开始。
- 字符串是不可变序列,不能修改。
- 在Python中,字符串长度没有固定限制,取决于计算机内存大小。
7.5.5、字符串常用操作
7.5.6、字符串比较
7.6、切片 - slice
7.6.1、基本介绍
7.6.2、基本语法
7.6.3、注意事项和使用细节
- 切片语法:序列[起始索引:结束索引:步长],起始索引如果不写,默认为0;结束索引如果不写,默认为截取到结尾;步长如果不写,默认为1。
- 切片语法:序列[起始索引:结束索引:步长],步长为负数,表示反向取,同时注意起始索引和结束索引也要反向标记。
- 切片操作并不会影响原序列,而是返回了一个序列。
7.7、集合 - set
7.7.1、基本介绍
7.7.2、集合的定义
7.7.3、注意事项和使用细节
- 集合是由不重复元素组成的无序容器(自动去重)。
- 集合不支持索引。
- 既然集合不支持索引,所以对集合进行遍历不支持while,只支持for。
- 创建空集合只能用 set() ,不能用 {},{} 创建的是空字典。
7.7.4、集合常用操作
7.7.5、集合生成式
答案:
韩韩、顺顺、平平(乱序)
7.8、字典 - dict
7.8.1、基本介绍
7.8.2、字典的定义
7.8.3、注意事项和使用细节
- 字典的Key(关键字)通常是字符串或数字,Value可以是任意数据类型。
- 字典不支持索引,会报 keyError。
- 既然字典不支持索引,所以对字典遍历不支持 while,只支持 for,注意直接对字典进行遍历,遍历得到的是key。三种遍历方式如下:
- 创建空字典可以通过 {} ,或者 dict()。
- 字典的key必须是唯一的,如果你指定了多个相同的key,后面的键值对会覆盖前面的。
7.8.4、字典常用操作
7.8.5、字典生成式
7.9、数据容器 - 小结
7.10、list、tuple、set和dict的传参机制
8、排序和查找
8.1、排序的介绍
- 排序是将多个数据,按照指定的顺序进行排列的过程。
- 排序的分类:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序。
8.2、冒泡排序法
8.2.1、基本介绍
8.2.2、冒泡排序法范例
def bubble_sort(my_list)
for i in range(0, len(my_list)):
for j in range(0, len(my_list) - i):
if my_list[j] > my_list[j + 1]:
my_list[j], my_list[j + 1] = my_list[j + 1], my_list[j]
8.3、查找
8.3.1、基本介绍
在Python中,我们应当掌握两种常见的查找方法:
- 顺序查找;
- 二分查找;
- 插值查找;
- 斐波那契查找;
- 树表查找;
- 分块查找;
- 哈希查找
8.3.2、顺序查找
def seq_search(my_list, find_val):
find_index = []
for i in range(len(my_list)):
if my_list[i] == find_val:
find_index.append(i)
return find_index
8.3.3、二分查找
思路分析:
def binary_search(my_list, find_val):
# 该列表一定是有大小顺序的
# 先找到左右两端的索引
left_index = 0
right_index = len(my_list) - 1
# 定义找到数的下标
find_index = -1
while left_index <= right_index:
mid_index = (left_index + right_index) // 2
if my_list[mid_index] > find_value:
right_index = mid_index - 1
elif my_list[mid_index] < find_value:
left_index = mid_index + 1
else:
find_index = mid_index
break
return find_index
9、断点调试(Debug)
9.1、基本介绍
10、模块和包
10.1、模块的基本介绍
模块(Module)是什么?
- 模块是一个py文件,后缀名 .py。
- 模块可以定义函数、类和变量,模块里也可能包含可执行的代码。
模块的作用有哪些?
- 当函数、类和变量很多时,可以很好的进行管理。
- 开发中,程序员可以根据业务需要,把同一类型的功能代码,写到一个模块文件中,即方便管理,也方便调用。
- 一个模块就是一个工具包,供程序员开发使用,提高开发效率。
- Python自带标准模块库。
10.2、导入模块 - import
10.2.1、基本语法
10.2.2、实例演示
(1)导入一个或多个模块
(2)导入模块的指定功能
(3)导入模块的全部功能
(4)给导入的模块或者功能取别名
10.3、自定义模块
10.3.1、基本说明
- 自定义模块:在实际开发中,python提供的标准库模块不能满足开发需求,程序员需要一些个性化的模块,就可以进行自定义模块的实现。
10.3.2、注意事项
- 使用 __name__ 可以避免模块中测试代码的执行。
def hi(): print("1") if __name__ == "__main__": hi()
- 使用 __all__ 可以控制 import* 时,哪些功能被导入,注意:import 模块 方式,不受 __all__的限制。
__all__ = ['ok']
表示如果其他文件使用的是 from xxx import *,则只能导入ok函数。如果你用的是 import xxx ,则限制不了。
10.4、包
10.4.1、应用场景
- 一个实际的项目,可能需要很多的模块,当模块文件越来越多,如果我们将所有的模块文件都放在同一个文件夹,就会带来很多问题,不利于管理和调用。这个时候我们就会用 包。
10.4.2、基本介绍
10.4.3、注意事项和使用细节
(1)导入包基本语法
(2)导入包的模块的指定函数、类、变量
(3)__init__.py 通过 __all__ 控制允许导入的模块
(4)包可以有多个层级
有三种导入形式:
# 方式1:
import hsp_package.hsp_package2.module03
hsp_package.hsp_package2.module03.cal(10, 30)
# 方式2:
from hsp_package.hsp_package2.module03 import cal
cal(10, 30)
# 方式3:
from hsp_package.hsp_package2 import module03
module03.cal(10, 30)
(5)快捷键:alt + enter / shift + alt + enter 可以快捷的导入(PyCharm):
10.5、第三方库
10.5.1、基本介绍
10.5.2、基本使用
10.5.3、指定源
11、面向对象编程(基础部分)
11.1、类与对象
11.1.1、类与实例的关系
11.1.2、快速入门 - 面向对象的方式解决养猫问题
# feed_cat_oop.py
# 定义一个毛类:age、name、color是属性,或者称为成员变量;
# Cat类 就是你自己定义的一个新类型;
# 定义Cat类
class Cat:
age = None
name = None
color = None
# 通过 Cat 类,创建实例
cat1 = Cat()
# 通过对象名.属性名 可以给各个属性赋值
cat1.name = "小白"
cat1.age = 2
cat1.color = "白色"
# 通过对象名.属性名,可以访问到属性
print(f"cat1的信息为: name: {cat1.name} age: {cat1.age} color: {cat1.color}")
总结:通过上面的OOP方式解决问题,可以更好的管理小猫的属性。
11.1.3、类和对象的区别和联系
- 类是抽象的、概念的,代表一类事物,比如人类、猫类...,即 它是数据类型。
- 对象是具体的,实际的,代表一个具体事物,即 是实例。
- 类是对象的模板,对象是类的一个个体,对应一个实例。
11.1.4、对象在内存中存在形式
11.1.5、属性 / 成员变量
基本介绍:
- 类中定义的属性(变量),我们也称为:成员变量。
- 属性是类的一个组成部分,一般是字符串、数值,也可是其他类型(list、dict等),比如前面定义 Cat类 的 name、age 就是属性。
注意事项和细节:
(1)属性的定义语法同变量,实例:属性名 = 值,如果没有值,可以赋值 None。
(2)如果给属性指定的有值,那么创建的对象,属性就有值。
11.1.6、类的定义和使用
如何定义类:
如何创建对象:
如何访问属性:
练习:
a = Person()
a.age = 10
a.name = "jack"
b = a
print(b.name) # jack
b.age = 100
b = None
print(a.age) # 100
print(b.age) # 报错
11.2、对象的布尔值
11.3、成员方法
11.3.1、基本介绍
11.3.2、成员方法的定义和使用
成员方法的定义:
11.3.3、案例演示
class Person:
name = None
age = None
def hi(self):
print("hi!")
def cal01(self):
result1 = 0
for i in range(1, 1001):
result1 += i
print(result1)
def cal02(self, n):
result2 = 0
for i in range(1, n+1):
result2 += i
print(result2)
def get_sum(self, a, b):
result3 = a + b
print(result3)
p = Person()
p.hi()
p.cal01()
p.cal02(10)
p.get_sum(1,3)
# hi!
# 500500
# 55
# 4
11.3.4、使用细节
- Python也支持对象动态的添加方法。
def hi():
print("hi!!!")
class Person:
name = None
age = None
p = Person()
p2 = Person()
# 动态的给p对象添加方法m1,注意,只是针对p对象添加方法
# m1是你新增加的方法的名称,由程序员指定名字
# 即m1方法和函数hi关联起来,当调用m1方法时,会执行hi函数
p.m1 = hi
p.m1()
# hi!!!
11.4、self
11.4.1、先看一段代码,并分析输出的信息是什么?
输出加菲猫。
问题分析:如果我们希望在成员方法内,访问对象的属性 / 成员变量,怎么办? >>> self
输出波斯猫。
11.4.2、二说self
- 通过@staticmethod,可以将方法转为静态方法。
- 如果是一个静态方法,可以不带self。
- 静态方法的调用形式有变化。
3)self表示当前对象本身,简单地说,哪个对象调用,self就代表哪个对象。
4)当我们通过对象调用方法时,self会隐式的传入。
5)在方法内部,要访问成员变量和成员方法,需要使用self。
class Dog:
name = "藏獒"
age = 2
def eat(self):
print(f"{self.name}" 饿了。。。)
def cry(self, name):
print(f"{name} is crying")
print(f"{self.name} is crying")
self.eat()
dog = Dog()
dog.cry("金毛")
# 金毛 is crying
# 藏獒 is crying
# 藏獒 饿了。。。
-----------------
dog = Dog()
dog.name = "狮子"
dog.cry("金毛")
# 金毛 is crying
# 狮子 is crying
# 狮子 饿了。。。
11.4.3、课堂练习题
class Person:
name = None
age = None
def compare_to(self, other):
return self.name == other.name and self.age == other.age
# 测试
p1 = Person()
p1.name = "tom"
p1.age = 18
p2 = Person()
p2.name = "tim"
p2.age = 3
print(p1.compare_to(p2))
11.5、对象作为参数传递
对象的传参机制:
1)这里我们讨论的对象,是通过我们自定义的类,创建的对象,比如 Cat类 -> cat对象。
2)示例如下:
地址不变,值改变。
11.6、作用域
面向对象中,变量的作用域是需要掌握的知识点。
1、在面向对象编程中,主要的变量就是成员变量(属性)和局部变量。
2、我们说的局部变量,一般是指在成员方法中定义的变量。
3、作用域的分类:属性作用域为整个类,比如Cat类:cry eat等方法使用属性。
4、局部变量:也就是方法中定义的变量,作用域是在它的方法中。
5、属性和局部变量可以重命名,访问时带上self,表示访问的属性,没有带self,则是访问局部变量。
11.7、构造方法
11.7.1、看一个需求
11.7.2、基本介绍
基本介绍:
- 构造方法(构造器)基本语法:
def __init__(self, 参数列表):
代码...
(1)在初始化对象时,会自动执行 __init__ 方法。
class Person:
# 构造方法
def __init__(self):
print("__init__ 执行了")
p1 = Person()
# __init__ 执行了
(2)在初始化对象时,将传入的参数,自动传递给 __init__ 方法。
class Person:
name = None
age = None
# 构造方法
# 构造方法是完成对象的初始化任务
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("james", 20)
(3)构造方法是py预定义的,名称是 __init__ ,注意 init 前后都有两个 _ 。
11.7.3、注意事项和使用细节
# 为了代码简介,我们也可以通过 __init__ 动态的生成对象属性
class Person:
def __init__(self, name, age):
print(f"__init__ 执行了... 得到了{name} {age}")
# 将接收到的name和age 赋给 当前对象的name和age属性
# Python支持动态生成对象属性
self.name = name
self.age = age
p1 = Person("Tim", 30)
11.8、本章作业
class A01:
def max(self, lst):
return max(lst)
lst = [-1.1, 2.9, -1.9, 67.9]
a = A01()
print(a.max(lst))
class Book:
def __init__(self, name, price):
self.name = name
self.price = price
def update_price(self):
if self.price > 150:
self.price = 150
elif 100 < self.price <= 150:
self.price = 100
def info(self):
print(f"书的信息 {self.name} {self.price}")
book = Book("xiaohu", 1900)
book.info()
book.update_price()
book.info()
class Circle:
def __init__(self, r):
self.r = r
def zhouchang(self):
length = 2 * 3.14 * self.r
print(f"周长是{length}")
def mianji(self):
area = 3.14 * self.r * self.r
print(f"面积是{area}")
circle = Circle(10)
circle.zhouchang()
circle.mianji()
class Cal:
def __init__(self, num1, num2):
self.num1 = num1
self.num2 = num2
def jia(self):
print(f"这两个数的和是{self.num1 + self.num2}")
def cha(self):
print(f"这两个数的差是{self.num1 - self.num2}")
def cheng(self):
print(f"这两个数的积是{self.num1 * self.num2}")
def chu(self):
if self.num2 != 0:
print(f"这两个数的商是{self.num1 / self.num2}")
else:
print("除数为0!!!")
cal = Cal(5, 9)
cal.jia()
cal.cha()
cal.cheng()
cal.chu()
12、面向对象编程(进阶部分)
12.1、面向对象编程三大特征
封装、继承和多态。
12.2、面向对象编程 - 封装
12.2.1、封装介绍
- 封装(encapsulation)就是把抽象出的数据 [属性] 和对数据的操作 [方法] 封装在一起,数据被保护在内部。
- 程序只有通过被授权的操作,才能对数据进行访问。
12.2.2、封装的理解和好处
- 隐藏实现细节:方法(绘制柱状图)<--- 调用(传入参数...)
- 可以对数据进行验证(比如age:1~120、password长度要求等),保证安全合理
- 可以保护数据隐私(比如salary),要求授权才可以访问
12.2.3、私有成员
12.2.4、如何将 属性/方法 进行私有化
类中的变量或方法以双下划线 __ 开头命名,则该变量或方法为私有的,私有的变量或方法,只能在本类内部使用,类的外部无法使用。
12.2.5、如何访问私有的属性/方法:提供公共的方法,用于对私有成员的操作
12.2.6、快速入门
class Clerk:
# 公共属性
name = None
# 私有属性
__job = None
__salary = None
# 构造方法
def __init__(self, name, job, salary):
self.name = name
self.__job = job
self.__salary = salary
# 提供公共方法,对私有属性进行访问操作(根据实际业务编写)
def set_job(self, job):
self.__job = job
def get_job(self):
return self.__job
def __hi(self):
print("hi!")
# 提供公共方法,操作私有方法即可
def f1(self):
self.__hi()
clerk = Clerk("tiger", "Python工程师", 20000)
# 如果是公共属性,在类的外部可以直接访问
print(clerk.name)
# 如果是私有属性,在类的外部就不能直接访问
# print(clerk.__job)
print(clerk.get_job())
clerk.set_job("Java工程师")
print(clerk.get_job())
# 私有方法不能再类的外部直接调用
# clerk.__hi()
# 通过公共方法,调用私有方法
clerk.f1()
12.2.7、注意事项和使用细节
- Python语言的动态特征,会出现 伪私有属性的情况
言外之意就是,不可以直接用clerk.__job = xxx来访问,而是要用公有方法来私有访问。
12.2.8、封装的练习题
class Account:
__name = None
__balance = None
__pwd = None
def set_name(self, name):
# 长度为2-4位
if 2 <= len(name) <= 4:
self.__name = name
else:
print("名字的长度不在2-4位之间,请修改!")
def set_balance(self, balance):
if balance > 20:
self.__balance = balance
else:
print("设置的余额小于或等于20,请重新设置!")
def set_pwd(self, pwd):
if len(pwd) == 6:
self.__pwd = pwd
else:
print("密码必须是6位!")
def query_info(self, name, pwd):
if name == self.__name and pwd == self.__pwd:
return f"账户信息 {self.__name} {self.__balance}"
else:
print("请输入正确的名字和密码!")
account1 = Account()
account1.set_pwd("123456")
account1.set_name("Alex")
account1.set_balance(10000)
account1.query_info("Alex", "123456")
print(account1.query_info("Alex", "123456"))
12.3、面向对象编程 - 继承
12.3.1、为什么需要继承
分析问题:
- Pupil 和 Graduate 有很多相同的属性和方法。
- 目前如果单独分开写,代码复用性差。
- 也不利于管理。
12.3.2、继承基本介绍
12.3.2.1、继承基本介绍
- 继承可以解决代码复用,让我们的编程更加靠近人类思维。
- 当多个类存在相同的属性(成员变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法。
12.3.2.2、继承的示意图
12.3.2.3、继承的基本语法
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
- 派生类就会自动拥有基类定义的属性和方法。
- 基类习惯上也叫父类。
- 派生类习惯上也叫子类。
12.3.3、快速入门
class Student:
name = None
age = None
__score = None
def __init__(self, name, age):
self.name = name
self.age = age
def show_info(self):
print(f"name={self.name}, age={self.age}, score={self.__score}")
def set_score(self, score):
self.__score = score
class Pupil(Student):
def testing(self):
print("..小学生正在考数学..")
class Graduate(Student):
def testing(self):
print("..大学生正在考高数..")
student1 = Pupil("Alex", 10)
student1.testing()
student1.set_score(80)
student1.show_info()
student2 = Graduate("Bob", 25)
student2.testing()
student2.set_score(90)
student2.show_info()
12.3.4、继承的深入讨论 / 细节问题
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有的属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问。
- Python编程语言中,”object“是所有其它类的基类,通过ctrl+h,可以查看类的继承关系。
- Python支持多重继承。
- 在多重继承中,如果有同名的成员,遵守从左到右的继承优先原则(即:写左边的父类优先级高,写在右边的父类优先级低)。
12.3.5、继承的练习题
son.name=大头儿子 son.age=39 son.hobby=旅游
class Computer:
def __init__(self, CPU, Memory, disk):
self.CPU = CPU
self.Memoery = Memory
self.disk = disk
def get_details(self):
return f"CPU={self.CPU}, Memory={self.Memoery}, disk={self.disk}"
class PC(Computer):
def __init__(self, CPU, Memory, disk, brand):
# 通过 super().xx 方式可以去调用父类的方法
super().__init__(CPU, Memory, disk)
self.brand = brand
def gen_info(self):
# print(f"CPU={self.CPU}, Memory={self.Memoery}, disk={self.disk}, brand={self.brand}")
print(f"{self.get_details()}, brand={self.brand}")
class NotePad(Computer):
def __init__(self, CPU, Memory, disk, color):
super().__init__(CPU, Memory, disk)
self.color = color
def gen_info(self):
print(f"CPU={self.CPU}, Memory={self.Memoery}, disk={self.disk}, color={self.color}")
pc1 = PC("intel", "1024MiB", "2T", "Thinkpad")
pc1.gen_info()
notepad1 = NotePad("intel", "1024MiB", "2T", "black")
notepad1.gen_info()
12.4、调用父类成员
12.4.1、基本介绍 & 实例
12.4.1.1、基本介绍
如果子类和父类出现同名的成员,可以通过父类名、super()访问父类的成员
12.4.1.2、基本语法
1)访问父类成员方式1
2)访问父类成员方式2
12.4.2、注意事项和使用细节
- 子类不能直接访问父类的私有成员。
- 访问不限于直接父类,而是建立 从子类向上级父类的查找关系 A->B->C...
- 建议使用 super() 的方式,因为如果使用 父类名 方式,一旦父类变化,类名同意需要修改,比较麻烦。
12.5、重写
12.5.1、基本介绍 & 实例
12.5.1.1、基本介绍
重写又称覆盖(override),即子类继承父类的属性和方法后,根据业务需要,再 重新定义同名的属性或方法。
12.5.2、课堂练习题
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
return f"名字{self.name} 年龄{self.age}"
class Student(Person):
def __init__(self, name, age, id, score):
super().__init__(name, age)
self.id = id
self.score = score
def say(self):
return f"{super().say()} 编号{self.id} 分数{self.score}"
person1 = Person("Alex", 19)
student1 = Student("Mike", 25, 7, 100)
print(person1.say())
print(student1.say())
12.6、类型注解 - type hint
12.6.1、基本介绍
12.6.1.1、为什么需要类型注解:
- 随着项目越来越大,代码也就会越来越多,在这种情况下,如果没有类型注解,很容易不记得某一个方法的参数类型是什么。
- 一旦传入了错误类型的参数,Python是解释性语言,只有运行时候才能发现问题,这对大型项目来说是一个巨大的灾难。
12.6.1.2、类型注解作用和说明
def fun1(a:str):
for ele in a:
print(ele)
fun1("100")
12.6.2、变量的类型注解
12.6.2.1、基本语法
变量:类型
12.6.2.2、基础数据类型注解
12.6.2.3、实例对象类型注解
12.6.2.4、容器类型注解
12.6.2.5、容器详细类型注解
12.6.2.6、在注释中使用注解
12.6.3、函数(方法)的类型注解
12.6.3.1、基本语法
12.6.3.2、示例
12.6.3.3、说明
12.6.4、Union类型
12.6.4.1、基本语法
12.6.4.2、示例
使用Union,需要导入:from typng import Union
12.7、面向对象编程 - 多态
12.7.1、先看一个问题
12.7.1.1、使用传统方法来解决
# 先使用传统的方式完成
class Food:
name = None
def __init__(self, name):
self.name = name
class Fish(Food):
# 特有的属性和方法
pass
class Bone(Food):
# 特有的属性和方法
pass
class Animal:
name = None
def __init__(self, name):
self.name = name
class Dog(Animal):
pass
class Cat(Animal):
pass
class Master:
name = None
def __init__(self, name):
self.name = name
# 给猫喂鱼
def feed_cat(self, cat:Cat, fish:Fish):
print(f"主人{self.name}给动物:{cat.name}喂的食物是{fish.name}")
# 给狗为骨头
def feed_dog(self, dog:Dog, bone:Bone):
print(f"主人{self.name}给动物:{dog.name}喂的食物是{bone.name}")
# 测试
master = Master("Jack")
cat = Cat("alex")
fish = Fish("黄花鱼")
dog = Dog("bob")
bone = Bone("鸡脆骨")
master.feed_dog(dog, bone)
master.feed_cat(cat, fish)
问题分析:如果 动物/食物 的种类很多,怎么办?
12.7.1.2、传统方法带来的问题是什么?如何解决?
- 问题是:代码的复用性不高,而且不利于代码的维护和功能扩展。
- 解决方案:引出我们要讲的多态。
12.7.2、多态介绍 & 特别说明
12.7.2.1、怎么理解多态
12.7.2.2、实例演示
class Animal:
def cry(self):
pass
class Cat(Animal):
def cry(self):
print("小猫喵喵叫")
class Dog(Animal):
def cry(self):
print("小狗汪汪叫")
class Pig(Animal):
def cry(self):
print("小猪噜噜叫")
# 注意,在Python面向对象编程中,子类对象可以传递给父类类型
def func(animal: Animal):
animal.cry()
# 创建3个对象
cat = Cat()
dog = Dog()
pig = Pig()
# 调用函数
func(cat)
func(dog)
func(pig)
12.7.2.3、多态的好处
12.7.2.4、特别说明Python的多态特点
12.7.3、二说主人喂动物问题
# 使用多态特性来优化
class Food:
name = None
def __init__(self, name):
self.name = name
class Fish(Food):
# 特有的属性和方法
pass
class Bone(Food):
# 特有的属性和方法
pass
class Grass(Food):
pass
class Animal:
name = None
def __init__(self, name):
self.name = name
class Dog(Animal):
pass
class Cat(Animal):
pass
class Horse(Animal):
pass
class Master:
name = None
def __init__(self, name):
self.name = name
# # 给猫喂鱼
# def feed_cat(self, cat:Cat, fish:Fish):
# print(f"主人{self.name}给动物:{cat.name}喂的食物是{fish.name}")
# # 给狗为骨头
# def feed_dog(self, dog:Dog, bone:Bone):
# print(f"主人{self.name}给动物:{dog.name}喂的食物是{bone.name}")
# 主人给动物喂食物
def feed(self, animal: Animal, food: Food):
print(f"主人{self.name}给动物:{animal.name}喂的食物是{food.name}")
# 测试
master = Master("Jack")
cat = Cat("alex")
fish = Fish("黄花鱼")
dog = Dog("bob")
bone = Bone("鸡脆骨")
horse = Horse("Mike")
grass = Grass("青草")
master.feed(cat, fish)
master.feed(dog, bone)
master.feed(horse, grass)
12.7.4、isinstance函数
12.7.4.1、基本说明
12.7.5、练习题
注意,当调用对象成员的时候,会和对象本身动态关联。一句话就是,自己有的,就用自己的。
class Employee:
__name = None
__salary = None
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
def set_name(self, name):
self.__name = name
def set_salary(self, salary):
self.__salary = salary
def get_name(self):
return self.__name
def get_salary(self):
return self.__salary
def get_annual(self):
return self.__salary * 12
class Worker(Employee):
def __init__(self, name, salary):
super().__init__(name, salary)
def work(self):
# print(f"工人:{self.get_name()}正在工作。。。")
print(f"工人: {super().get_name()}正在工作。。。")
class Manager(Employee):
__bonus = None
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.__bonus = bonus
def set_bonus(self, bonus):
self.__bonus = bonus
def get_bonus(self):
return self.__bonus
def get_annual(self):
# return self.__salary * 12 + self.__bonus
return super().get_annual() + self.__bonus
def manage(self):
print(f"经理: {super().get_name()}正在管理。。。")
# 编写函数 show_emp_annual(e: Employee),实现获取任何员工对象的年工资。 --> 多态
def show_emp_annual(e: Employee):
print(f"{e.get_name()}的年工资是{e.get_annual()}")
# 编写函数 working(e: Employee),如果是普通员工,则调用work方法,如果是经理,则调用manage方法。
def working(e: Employee):
# 如果是普通员工
if isinstance(e, Worker):
e.work()
elif isinstance(e, Manager):
e.manage()
else:
print(f"无法确定工作状态...")
worker1 = Worker("king", 10000)
manager1 = Manager("James", 18000, 30000)
show_emp_annual(worker1)
show_emp_annual(manager1)
working(worker1)
working(manager1)
12.8、魔术方法
12.8.1、基本介绍
12.8.1.1、什么是魔术方法
12.8.1.2、常见的魔术方法
12.8.2、__str__
12.8.2.1、基本介绍
12.8.2.2、应用实例
class Monster:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# 请输出 Monster[name, age, gender]对象的属性信息
# 可以根据需要重写 __str__
'''
1. 在默认情况下,调用的是父类object的__str__
2. 父类object的__str__返回的是:类型+地址
'''
def __str__(self):
return f"{self.name}, {self.age}, {self.gender}"
m = Monster("jack", 18, "male")
print(m) # 默认输出类型 + 对象的地址
12.8.3、__eq__
12.8.3.1、基本介绍
12.8.3.2、应用实例
class Person:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# 重写
def __eq__(self, other):
# 判断 other 是不是 Person
if isinstance(other, Person):
return self.name == other.name and self.age == other.age and self.gender == other.gender
return False
# 没有重写 __eq__ 前, == 比较的是内存地址
class Dog:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
p1 = Person("smith", 20, "male")
p2 = Person("smith", 20, "male")
d1 = Dog("smith", 20, "male")
print(f"p1==p2: {p1 == p2}")
print(f"p1==d1: {p1 == d1}")
12.8.4、其他几个魔术方法
12.9、Class对象和静态方法
12.9.1、Class对象
类本身也是对象,即:Class对象
class Monster:
name = "蝎子精"
age = 300
def hi(self):
print(f'hi() {self.name} - {self.age}')
print(Monster)
# 通过Class对象,可以引用属性(没有创建实例对象也可以引用 / 访问)
print(f"Monster.name: {Monster.name} Monster.age {Monster.age}")
# 通过类名如何调用非静态成员方法
Monster.hi(Monster)
12.9.2、静态方法
- @staticmethod 将方法转换为静态方法
-
静态方法不会接收隐式的第一个参数,要声明一个静态方法,语法:
class C: @staticmethod def f(arg1, arg2, argN): ...
- 静态方法既可以由类调用(如: C.f()),也可以由实例中调用(如 C().f())
12.10、抽象类
需求:
- 当父类的某些方法,需要声明,但是又不确定如何实现时,怎么办?
- 不需要实例化父类对象,父类主要的是用于设计和指定规范,让其他类来继承并实现,怎么办?
- 解决方案 -> 抽象类
12.10.1、抽象类的介绍和快速入门
- 默认情况下,Python不提供抽象类,Python附带一个模块,该模块为定义抽象基类提供了基础,该模块名称为abc。
- 当我们需要抽象基类时,让类集成ABC(abc模块的ABC类),使用 @abstractmethod 生命抽象方法(@abstractmethod 用于声明抽象方法的装饰器,在 abc 模块中),那么这个类就是抽象类。
- 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类的抽象方法。
from abc import ABC, abstractmethod
# Animal就是抽象类
class Animal(ABC):
def __init__(self, name, age):
self.name = name
self.age = age
# 这时,cry就是一个抽象方法
@abstractmethod
def cry(self):
# print("......")
pass
# 注意,抽象类(含有抽象方法),不能实例化
# animal = Animal("apple", 3)
# 编写子类Tiger,集成Animal,并实现抽象方法
class Tiger(Animal):
def cry(self):
print("老虎嗷嗷叫!")
tiger = Tiger("alex", 9)
tiger.cry()
12.10.2、注意事项和使用细节
- 抽象类不能被实例化。
- 抽象类需要继承ABC,并且需要至少一个抽象方法。
- 抽象类中可以有普通方法。
- 如果一个类继承了抽象类,则它必须实现抽象方法类的所有抽象方法,否则它仍然是一个抽象类。
案例练习:
from abc import ABC, abstractmethod
class Employ(ABC):
def __init__(self, name, id, salary):
self.name = name
self.id = id
self.salary = salary
@abstractmethod
def work(self):
pass
class CommonEmployee(Employ):
def __init__(self, name, id, salary):
super().__init__(name, id, salary)
def work(self):
print(f"普通员工 {self.name} 工作中。。。")
class Manager(Employ):
def __init__(self, name, id, salary, bonus):
super().__init__(name, id, salary)
self.bonus = bonus
def work(self):
print(f"经理 {self.name} 工作中。。。")
ce = CommonEmployee("alex", 190, 20000)
m1 = Manager("bob", 10, 30000, 5000)
ce.work()
m1.work()
12.11、模板设计模式
12.11.1、什么是设计模式
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
- 设计模式就像是经典的棋谱,不同得棋局,我们用不同的棋谱,免去我们自己再思考和摸索。
12.11.2、模板设计模式 - 介绍
- 基本介绍:
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。 - 模板设计模式能解决的问题:
(1)当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
(2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
12.11.3、模板设计模式 - 最佳实践
开发需求:
1)有多个类,完成不同的任务job。
2)要求统计得到各自完成任务的时间。
3)请编程实现。
from abc import ABC, abstractmethod
import time
class Template(ABC):
@abstractmethod
def job(self):
pass
def cal_time(self):
start= time.time()
self.job()
end = time.time()
print(f"工作用时:{end - start}")
class AA(Template):
def job(self):
for i in range(10000):
if i != 9999:
print("挖矿中。。。")
else:
print("完成挖矿")
class BB(Template):
def job(self):
for i in range(100):
if i != 99:
print("钓鱼中。。。")
else:
print("完成钓鱼")
a = AA()
b = BB()
a.cal_time()
b.cal_time()
12.12、本章练习
12.12.1、练习1
定义一个Person类,属性:name, age, job,创建Person列表,有三个person对象,并按照 age 从大到小 的顺序进行排序。
# class Person:
# def __init__(self, name, age, job):
# self.name = name
# self.age = age
# self.job = job
# p1 = Person("alex", 19, "engineer")
# p2 = Person("bob", 23, "teacher")
# p3 = Person("cathy", 34, "worker")
# PERSON = [p1, p2, p3]
# length = len(PERSON)
# for i in range(length - 1):
# for j in range(length - 1 - i):
# if PERSON[j].age < PERSON[j+1].age:
# temp_name = PERSON[j].name
# temp_age = PERSON[j].age
# temp_job = PERSON[j].job
# PERSON[j].name = PERSON[j+1].name
# PERSON[j].age = PERSON[j+1].age
# PERSON[j].job = PERSON[j+1].job
# PERSON[j+1].name = temp_name
# PERSON[j+1].age = temp_age
# PERSON[j+1].job = temp_job
# for j in range(length):
# print(PERSON[j].name)
class Person:
def __init__(self, name, age, job):
self.name = name
self.age = age
self.job = job
p1 = Person("alex", 19, "engineer")
p2 = Person("bob", 23, "teacher")
p3 = Person("cathy", 34, "worker")
PERSON = [p1, p2, p3]
length = len(PERSON)
for i in range(length - 1):
for j in range(length - 1 - i):
if PERSON[j].age < PERSON[j+1].age:
# 交换整个对象,更简洁
PERSON[j], PERSON[j+1] = PERSON[j+1], PERSON[j]
for person in PERSON:
print(f"{person.name} - {person.age} - {person.job}")
#############################################################
class Person:
def __init__(self, name, age, job):
self.name = name
self.age = age
self.job = job
def __str__(self):
return f"{self.name} - {self.age} - {self.job}"
p1 = Person("alex", 19, "engineer")
p2 = Person("bob", 23, "teacher")
p3 = Person("cathy", 34, "worker")
PERSON = [p1, p2, p3]
# 方法2:使用内置排序(推荐)
PERSON.sort(key=lambda person: person.age, reverse=True)
# 输出结果
for person in PERSON:
print(person)
12.12.2、练习2
编写Doctor类,属性:name,age,job,gender,sal,重写__eq__()方法,并判断测试两个对象是否相等。
class Doctor:
def __init__(self, name, age, job, gender, sal):
self.name = name
self.age = age
self.job = job
self.gender = gender
self.sal = sal
def __eq__(self, other):
if isinstance(other, Doctor):
if self.name == other.name and self.age == other.age and self.job == other.job and self.gender == other.gender \
and self.sal == other.sal:
print("相等")
else:
print("不等")
else:
print("没意义")
d1 = Doctor("alex", 18, "doctor", "male", 18000)
d2 = Doctor("alex", 18, "doctor", "male", 18000)
d1.__eq__(d2)