代码获取:https://github.com/qingxuly/hsp_python_course
完结版:Python基础学习(完结版)
面向对象编程(进阶)
面向对象编程三大特征
- 面向对象编程有三大特征:封装、继承、多态。
面向对象编程—封装
封装介绍
- 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部。
- 程序只有通过被授权的操作,才能对数据进行访问。
- 封装经典案例:电视机的操作就是典型封装。
封装的理解和好处
- 隐藏实现细节:方法(绘制柱状图)<——调用(传入参数…)。
- 可以对数据进行验证(比如:age: 1~120、password: 长度要求),保证安全合理。
- 可以保护数据隐私(比如salary),要求授权才可以访问。
私有成员
- 公共的变量和方法介绍。
- 默认情况下,类中的变量和方法都是公有的,它们的名称前都没有下划线。
- 公共的变量和方法,在类的外部、类的内部,都可以正常访问。
- 如何将属性/方法进行私有化。
- 类中的变量或方法以双下划线
__
开头命名,则该变量或方法为私有的,私有的变量或方法,只能在本类内部使用,类的外部无法使用。
- 类中的变量或方法以双下划线
- 如何访问私有的属性/方法:提供公共的方法,用于对私有成员的操作。
快速入门
# 创建职员类(Clerk),属性:name, job, salary
# 1、不能随便查看职员 Clerk 的职位和工资等隐私,比如职员 ("tiger", "python 工程师", 20000)
# 2、提供公共方法,可以对职员和工资进行操作
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) # AttributeError: 'Clerk' object has no attribute '__ job'
print(clerk.get_job())
clerk.set_job("Java 工程师")
print(clerk.get_job())
# 私有方法不能在类的外部直接调用
# clerk.hi() # AttributeError: 'Clerk' object has no attribute 'hi'
# 通过公共方法,调用私有方法
clerk.f1()
注意事项和细节
- python语言的动态特性,会出现伪私有属性的情况
class Clerk:
# 公共属性
name = None
# 私有属性
__job = None
__salary = None
# 构造方法
def __init__(self, name, job, salary):
self.name = name
self.__job = job
self.__salary = salary
# 提供公共的方法,对私有属性操作
def get_job(self):
return self.__job
clerk = Clerk("apple", "python 工程师", 20000)
# 如果这样使用,因为 python 语言的动态特性,会动态的创建属性 __job,但是这个属性和我们在类中定义的私有属性 __ job 不是同一个变量,我们在类中定义的__job 私有属性完整的名字是 _Clerk__job,而并不是
# 可以通过 debug 的“Evaluate Expression”来观察。
clerk.__job = "Go 工程师"
print(f " job = {clerk.__job}") # job = Go 工程师
# 获取真正的私有属性__job
print(f "{clerk.get_job()}")
- 练习
# Account 类要求具有属性:姓名(长度为 2-4 位)、余额(必须 > 20)、密码(必须是六位),如果不满足,则给出提示
# 通过 set_xx 的方法给 Account 的属性赋值
# 编写 query_info() 接收姓名和密码,如果姓名和密码正确,返回该账号信息
"" "
思路分析:
类名:Account
私有属性:姓名(长度为 2-4 位)、余额(必须 > 20)、密码(必须是六位)
构造器:无
方法:set_xx(self, 属性名) 进行赋值,并且对各个接收到的数据进行校验
方法:query_info(self, name, pwd) 而且需要验证,才返回响应信息
"" "
class Account:
__name = None
__balance = None
__pwd = None
def set_name(self, name):
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("密码(必须是六位)")
def query_info(self, name, pwd):
if name == self.__name and pwd == self.__pwd:
return f "账户信息:{self.__name} {self.__ balance}"
else:
return "请输入正确的名字和密码"
# 测试
account = Account()
account.set_name("tim")
account.set_pwd("000000")
account.set_balance(100)
print(account.query_info("tim", "000000"))
面向对象编程—继承
基本介绍
-
继承基本介绍
- 基础可以解决代码复用,让我们的编程更加靠近人类思维。
- 当多个类存在相同的属性(成员变量)和方法时,可以从这些类中抽象出方法,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法。
-
示意图
- 基本语法
class DerivedClassName(BaseClassName):
<statement-1>
...
<statement-N>
# 派生类就会自动拥有基类定义的属性和方法
# 基类习惯上也叫父类
# 派生类习惯上也叫子类
快速入门
# 编写父类
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
# 小学生类,继承 Student
class Pupil(Student):
def testing(self):
print("小学生在考小学数学...")
# 大学生类,继承 Student
class Graduate(Student):
def testing(self):
print("大学生在考高等数学...")
# 测试
student1 = Pupil("apple", 10)
student1.testing()
student1.set_score(70)
student1.show_info()
student2 = Graduate("grape", 22)
student2.testing()
student2.set_score(80)
student2.show_info()
- 继承给编程带来的便利
- 代码的复用性提高了。
- 代码的扩展性和维护性提高了。
继承的注意事项和细节
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法在子类不能在子类直接访问,要通过父类提供公共的方法去访问。
# 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法在子类不能在子类直接访问,要通过父类提供公共的方法去访问。
class Base:
# 公共属性
n1 = 100
# 私有属性
__n2 = 200
def __init__(self):
print("Base 构造方法")
def hi(self):
print("hi() 公共方法")
def __hello(self):
print("__hello() 私有方法 ")
# 提供公共方法,访问私有属性和私有方法
def test(self):
print("属性:", self.n1, self.__n2)
self.__hello()
class Sub(Base):
def __init__(self):
print("Sub 构造方法")
def say_ok(self):
print("say_ok() ", self.n1)
self.hi()
# print(self.__n2) # AttributeError: 'Sub' object has no attribute '_Sub__n2'
# self.__hello() # AttributeError: 'Sub' object has no attribute '_Sub__n2'
# 测试
sub = Sub()
sub.say_ok()
# 调用子类继承父类的公共方法,去实现访问父类的私有成员的效果
sub.test()
-
Python语言中,
object
是所有其它类的基类。Ctrl + H
可以查看一个类的继承关系。 -
Python支持多继承。
class A:
n1 = 100
def sing(self):
print("A sing()...", self.n1)
class B:
n2 = 200
def dance(self):
print("B dance()...", self.n2)
class C(A, B):
# Python pass 是空语句,是为了保持程序结构的完整;pass 不做任何事情,一般用作站位语句。
pass
c = C()
# 继承的属性信息
print(f "属性信息:{c.n1} {c.n2}")
# 调用继承的方法
c.dance()
c.sing()
- 在多重继承中,如果有同名的成员,遵守从左到右的继承优先级(即:写在左边的父类优先级高,写在右边的父类优先级低)。
class A:
n1 = 100
def sing(self):
print("A sing()...", self.n1)
class B:
n2 = 200
def dance(self):
print("B dance()...", self.n2)
def sing(self):
print("B sing()...", self.n2)
class C(A, B):
# Python pass 是空语句,是为了保持程序结构的完整;pass 不做任何事情,一般用作站位语句。
pass
c = C()
# 继承的属性信息
print(f "属性信息:{c.n1} {c.n2}")
# 调用继承的方法
c.dance()
c.sing() # A sing()... 100
继承的练习题
class GrandPa:
name = "大头爷爷"
hobby = "旅游"
class Father(GrandPa):
name = "大头爸爸"
age = 39
class Son(Father):
name = "大头儿子"
son = Son()
print(f "son.name is {son.name} and son.age is {son.age} and son.hobby is {son.hobby}")
class Computer:
cpu = None
memory = None
disk = None
def __init__(self, cpu, memory, disk):
self.cpu = cpu
self.memory = memory
self.disk = disk
def get_details(self):
return f "CPU: {self.cpu}, Memory: {self.memory}, Disk: {self.disk}"
class PC(Computer):
brand = None
def __init__(self, cpu, memory, disk, brand):
# 初始化子类的属性——方式 1
# self.cpu = cpu
# self.memory = memory
# self.disk = disk
# self.brand = brand
# 初始化子类的属性——方式 2
# 通过 super().xx 方式可以去调用父类方法,这里通过 super().__init__(cpu, memory, disk, brand) 去调用父类的构造器
super().__init__(cpu, memory, disk)
self.brand = brand
def print_info(self):
print(f "品牌:{self.brand}\t{self.get_details()}")
pc = PC("inter", 32, 1000, "戴尔")
pc.print_info()
调用父类成员
基本介绍&实例
- 基本介绍
- 如果子类和父类出现同名的成员,可以通过
父类名
、super() 访问父类的成员。
- 如果子类和父类出现同名的成员,可以通过
- 基本语法
- 访问父类成员方式1
- 访问成员变量:
父类名.成员变量
- 访问成员方法:
父类名.成员方法(self)
- 访问成员变量:
- 访问父类成员方式2
- 访问成员变量:
super().成员变量
- 访问成员方法:
super().成员方法()
- 访问成员变量:
- 访问父类成员方式1
- 案例演示
class A:
n1 = 100
def run(self):
print("A-run()...")
class B(A):
n1 = 200
def run(self):
print("B-run()...")
# 通过父类名去访问父类的成员
def say(self):
print(f "父类的 n1 ={A.n1} 本类的 n1 ={self.n1}")
# 调用父类的 run
A.run(self)
# 调用本类的 run()
self.run()
# 通过 super()方式去访问父类对象
def hi(self):
print(f "父类的 n1 ={super().n1}")
b = B()
b.say()
b.hi()
注意事项和使用细节
- 子类不能直接访问父类的私有成员
class A:
n1 = 100
__n2 = 600
def run(self):
print("A-run()...")
def __jump(self):
print("A-jump()...")
class B(A):
# 子类不能直接访问父类的私有成员
def say(self):
# print(A.__n2) # AttributeError: type object 'A' has no attribute '_B_ _n2'. Did you mean: '_A__n2'?
# print(super().__n2) # AttributeError: 'super' object has no attribute '_B__n2'
# A.__jump(self)
# super().jump()
print("B-say()...")
b = B()
b.say()
- 访问不限于直接父类,而是建立从子类向上级父类的查找关系 A->B->C…
class Base:
n3 = 800
def fly(self):
print("Base-fly()...")
class A(Base):
n1 = 100
__n2 = 600
def run(self):
print("A-run()...")
def __jump(self):
print("A-jump()...")
class B(A):
# 访问不限于直接父类,而是建立从子类向上级父类的查找关系 B-> A-> Base...
def say(self):
print(Base.n3, A.n3, super().n3)
Base.fly(self)
A.fly(self)
super().fly()
self.fly()
b = B()
b.say()
- 建议使用
super()
的方式,因为如果使用父类名
方式,一旦父类变化,类名统一需要修改,比较麻烦。
练习题
- 分析下面的代码,看看输出什么内容?
class A:
n1 = 300
n2 = 500
n3 = 600
def fly(self):
print("A-fly()...")
class B(A):
n1 = 200
n2 = 400
def fly(self):
print("B-fly()...")
class C(B):
n1 = 100
def fly(self):
print("C-fly()...")
def say(self):
print(self.n1) # 100
print(self.n2) # 400
print(self.n3) # 600
print(super().n1) # 200
print(B.n1) # 200
print(C.n1) # 100
c = C()
c.say()
- 针对上面的程序,想在C的say()中,调用C的fly()和A的fly(),应该如何调用?
self.fly() # C-fly()...
A.fly(self) # A-fly()...
方法重写
基本介绍&实例
- 基本介绍
- 重写又称覆盖(override),即子类继承父类的属性和方法后,根据业务需要,再重新定义同名的属性和方法。
- 案例演示
练习题
class Person:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
return f "名字:{self.name} 年龄:{self.age}"
class Student(Person):
id = None
score = None
def __init__(self, id, score, name, age):
# 调用父类的构造器完成继承父类的属性和属性的初始化
super().__init__(name, age)
# 子类的特有的属性,我们自己完成初始化
self.id = id
self.score = score
def say(self):
return f "id:{self.id} score:{self.score} {super().say()}"
person = Person("tom", 12)
print(person.say())
student = Student("tom", 14, "tom", 18)
print(student.say())
类型注解-type hint
基本介绍
- 为什么需要类型注解
- 随着项目越来越大,代码也就会越来越多,在这种情况下,如通过没有类型注解,很容易不记得某一个方法的参数类型是什么。
- 一旦传入了错误类型的参数,python是解释性语言,只有运行时候才能发现问题,这对大项目来说是一个巨大的灾难。
# 对字符串进行遍历
# a: str 给形参 a 进行类型注解,标注 a 的类型是 str。
def fun1(a: str):
for ele in a:
print(ele)
# Ctrl + P 提示参数时,没有类型提示
# 如果类型传错了,就会出现异常
fun1(100) # TypeError: 'int' object is not iterable
- 类型注解作用和说明
- 从Python3.5开始,引入了类型注解机制,作用和说明如下:
- 类型提示,防止运行时出现参数类型、返回值类型、变量类型不符合。
- 作为开发文档附加说明,方便使用者调用时传入和返回类型参数。
- 加入后并不会影响程序的运行,不会报正式的错误,只有提示。
- Pycharm支持类型注解,参数类型错误会黄色提示。
- 从Python3.5开始,引入了类型注解机制,作用和说明如下:
变量的类型注释
-
基本语法:
变量: 类型
。 -
基础数据类型注解
n1: int = 10 # 对n1进行类型注解,标注n1的类型为int;如果给出的类型与标注的类型类型不一致,则Pycharm会给出黄色警告
n2: float = 10.1
is_pass: bool = True
name: str = "Tom"
- 实例对象类型注解
class Cat:
pass
cat: Cat = Cat() # 对cat进行类型注解,标注cat类型是Cat
- 容器类型注解
my_list: list = [1, 2, 3] # 对my_list进行类型注解,标注my_list类型为list。
my_tuple: tuple = ("run", "sing", "fly")
my_set: set = {1, 2, 3}
my_dict: dict = {"name": "Tom", "age": 22}
- 容器详细类