
一木.溪桥 在Logic Education跟Amy学Python
逻辑教育 :https://logicedu.ke.qq.com
12期:Python基础课
一木.溪桥学Python-12:面向对象编程、私有属性、实例属性、类属性、封装、继承、单继承 、super()
日期:2021年1月11日
学习目标:
- 私有属性
- 类属性
- 封装
- 继承
- 单继承
- super()
学习内容:
私有属性
私有属性与私有方法介绍
- 在实际开发中,对象的某些属性或者方法,只希望在对象的内部使用,这时,我
们就可以使用私有属性和私有方法。
私有属性
-
私有属性: 就是对象不希望公开的属性。
-
定义方式: 在属性名前面增加两个下划线(例如:__name)
-
eg. 练习
定义类为:"人"类
创建对象:rose
初始化对象属性:name 与 age
要求:age 不能在类的外部访问
class People:
def __init__(self, name, age):
self.name = name
# self.age = age
self.__age = age
def print_info(self): # 1.私有属性在类的内部的方法当中是可以访问的
print(self.__age)
def get_age(self): # 2.在类的内部将私有属性返回出来,进行访问
return self.__age
def set_age(self, new_age): # 3.在类的外部对私有属性进行修改
self.__age = new_age
print(f"new age:{self.__age}")
zs = People("zs", 19)
print(zs.name) # zs
# print(zs.age) # 在类的外部访问私有属性报错:AttributeError: 'People' object has no attribute 'age'
# 如何才能在类的外部访问私有属性?
zs.print_info() # 1.私有属性在类的内部的方法当中是可以访问的 19
print(zs.get_age()) # 2.在类的内部将私有属性返回出来,进行访问 19
zs.set_age(50) # 3.在类的外部对私有属性进行修改 new age:50
zs.print_info() # 50
print(zs.get_age()) # 50
zs.set_age(30) # new age:30
zs.print_info() # 30
print(zs.get_age()) # 30
zs.set_age(20) # new age:20
- 需求:一定要在类的外部访问到对象的私有属性,如何实现?
- 方法:
- 可以通过类的内部的方法访问私有属性
- 通过类的内部方法将私有属性返回出去
- 在类的外部调用该方法并且接收
私有方法
-
私有方法: 就是对象不希望公开的方法
-
定义方式: 在方法名前面增加两个下划线(例如:__test)
-
注意:
在 python 中,并没有真正意义上的私有,只有伪私有。当我们在访问时,以对象 ._ 类名 __私有属性即可访问,私有方法同理。但是不建议使用。
class Demo:
def test1(self):
print("--1--")
def __test2(self):
print("--2--")
def test3(self):
return self.__test2()
d = Demo()
d.test1() # --1--
# d.__test2() # 私有方法没有办法直接在类的外部进行访问,报错:AttributeError: 'Demo' object has no attribute '__test2'
d.test3() # 通过类的内部的方法去进行调用 # --2--
# d._Demo__test2() # 注意:可以直接通过 对象._类名__私有方法名() 去访问私有方法,但是不建议使用
类的成员
实例属性
- 实例属性属于对象,只能通过对象访问
- 定义:self. 属性名称
- 访问:self. 属性名称 或 对象 . 属性名称
eg.练习:
- 定义一个省份类,打印输出班级每位同学所在的国家与省份。
class Province:
def __init__(self, name):
# 实例属性 self.属性名
self.country = "中国"
self.name = name
def print_info(self):
print(self.country, self.name) # self就是对象本身 访问
# print(clara.country, clara.name) # 通过每个对象 访问 可以 但是不建议使用
# print(Province.country, Province.name) # 报错
clara = Province("湖南")
jx = Province("广州")
clara.print_info()
jx.print_info()
Run:
中国 湖南
中国 广州
静态属性和实例属性的选择
在实际开发中,如果属性的值是固定的,不随对象的改变而改变。那就使用
静态属性(类属性),这样有利于节省内存的消耗。而如果属性的值随对象的
改变而改变,就使用实例属性。
class Province:
country = "中国" # 类属性(静态属性)
def __init__(self, name):
# 实例属性 self.属性名
self.name = name
def print_info(self):
print(self.country, self.name) # self就是对象本身 访问 类属性
# print(clara.country, clara.name) # 通过每个对象 访问 可以 但是不建议使用
print(Province.country) # 类属性,是可以直接通过类名来访问的
clara = Province("湖南")
jx = Province("广州")
clara.print_info()
jx.print_info()
Run:
中国 湖南
中国
中国 广州
中国
类属性 ( 静态属性 )
类属性属于类,保存在类中。在创建时,仅创建一份,并且所有对象都共享
静态属性。执行时可以是类访问也可以是对象访问。
定义: 直接在类中定义
访问: 类名 . 属性名称 或 self. 属性名称 或 对象 . 属性名称
实例方法
普通方法保存在类中,在实例化对象后,一般通过对象调用。第一个参数必
须为系统自建参数,默认为 self,代指对象本身。
注意 :
当需要在方法中使用实例属性时,通常使用实例方法
self 仅仅是变量名。使用其它也可,但最好不要修改
类方法: @classmethod
类方法通过在方法上面添加 @classmethod 装饰器,保存在类中;
注意 :
类方法不需要传入self。但有一个系统自建参数为cls,cls代指类本身;
类方法一般通过类调用,也可通过对象调用
class Demo:
def __init__(self):
self.name = "hello"
# 实例方法:通常用于需要使用实例属性
def test_one(self):
print(self) # <__main__.Demo object at 0x000002AE16120080>
print(self.name) # hello
@classmethod
def cls_md(cls, age): # 自建参数不再是self, 而是cls,代指当前类
print(cls) # <class '__main__.Demo'>
print(age) # 89
@classmethod
def stat_md(cls):
print("我是静态方法") # 我是静态方法
d = Demo()
d.test_one()
d.cls_md("89")
d.stat_md()
静态方法: @staticmethod
静态方法通过在方法上面添加 @staticmethod 装饰器定义,保存在类中;
注意 :
静态方法不需要传入self参数,即使传入了self参数也并不像普通方法那样代指对象本身,仅仅是一个普通的形参。
静态方法的调用虽然可以通过对象调用,但一般由类调用。
静态方法的参数并不代表对象这一特点,使静态方法在类中使用起来更像一个单纯的函数。完全可以将静态方法放在类的外部,当成函数使用。但是放在类中,便于使用与维护。
- eg1. # 获取当前的时间
import time
# 获取当前的时间
def show_time():
print(time.localtime()) # 返回的是当前时间,是元组格式:
# time.struct_time(tm_year=2021, tm_mon=1, tm_mday=13, tm_hour=14, tm_min=6, tm_sec=38, tm_wday=2, tm_yday=13, tm_isdst=0)
# 进行时间格式化
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) # 2021-01-13 14:06:38
print(time.strftime("%A %a %b %B %C %c", time.localtime())) # Wednesday Wed Jan January 20 Wed Jan 13 14:06:38 2021
print(time.strftime("%I %p", time.localtime())) # 02 PM
show_time()
- eg.2 @staticmethod 便于使用与维护
import time
class TimeTest:
@staticmethod
def show_time():
# print(time.localtime()) # 返回当前时间,是元组格式,需要进行时间格式化
print(time.strftime("%Y-%m-%d", time.localtime())) # 2021-01-13
t = TimeTest()
t.show_time()
封装
封装介绍
封装是面向对象编程的一大特点,将属性和方法放到类的内部,通过对象访问属性或者方法,隐藏功能的实现细节,也可以设置访问权限。
- eg.1:
class Student:
addr = "123" # 类属性(静态属性)
def __init__(self, name, age):
self.name = name # 将属性封装到类的内部
self.age = age
def print_info(self):
print(self.name, self.age)
# print(self.addr) # 123
# self.addr = "222" # 不能通过实例去修改类属性
# print(self.addr) # 222
# print(Student.addr) # 123
# 在同一个类创建多个对象之间,属性是互不干扰的
xn = Student("寻你", 19)
jx = Student("九夏", 29)
xn.print_info() # 寻你 19
jx.print_info() # 九夏 29
继承 ( 重点 )
-
Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。
-
继承是一种创建新类的方式,如果子类需要复用父类的属性或者方法时,就可以使用
继承。当然,子类也可以提供自己的属性和方法。 -
注意:在 python 中,新建的类可以继承一个或多个父类
继承作用:避免重复造轮子,减少代码的冗余
新式类与经典类
- 在 Python2 当中类分为新式类和经典类,如果有继承父类 object 则是新式类,否则为经典类。
- 在 Python3 当中,全部都是新式类,默认继承 object。
- 练习:验证 Python3 中,全部都是新式类
实现思路:比较继承与无继承两个空类的成员是否一致
== 拓展方法:对象 .__dir__() 查看对象的属性与方法==
# Tips:在python3 中默认都是新式类
class Demo: # 此格式,在python2 中,叫经典类
pass
class Demo2(object): # 此格式,继承了object,在 python2 中,叫新式类
pass
d1 = Demo()
d2 = Demo2()
print(len(d1.__dir__())) # 对象 .\_\_dir\_\_() 查看对象的属性,返回的是列表。用len()测其长度: 26
print(len(d2.__dir__())) # 对象 .\_\_dir\_\_() 查看对象的属性,返回的是列表。用len()测其长度: 26
# 说明了:在python3 中以上两格式都是新式类。
单继承
- 子类继承父类,则可以直接享受父类中已经封装好的方法
- 当对象调用方法时,查找顺序先从自身类找,如果自身没找到,则去父类找,父类
无,再到父类的父类找,直到object类,若还无,则报错。这也称为深度优先机制。
- 需要注意的是,当子类与父类拥有同名称的方法时,子类对象调用该方法优先执行自身的方法。那么实际上就是子类的方法覆盖父类的方法,也称为重写。
- 但是实际的开发中,遵循开放封闭原则。我们并不会完全的重写父类的方法,而是希望同时实现父类的功能。这时,我们就需要调用父类的方法了,可以通过 super()函数实现。
super()
-
super(type[, object-or-type]) 函数是用于调用父类(超类)的一个方法
type --> 类
object-or-type --> 对象或类,一般是 self -
eg.练习: 继以上练习实现,在Son的sleep方法当中,调用父类的sleep方法。
class GrandFather(object):
def sleep(self):
print("GrandFather sleep 10")
class Father(GrandFather):
def eat(self):
print("Father eat")
def drink(self):
print("Father drink")
class Son(Father):
def study_python(self):
print("Son study python")
def sleep(self):
print("Son sleep 8") # 当子类有与父类名字相同的方法时,就意味着是重写了父类的方法。
# 在执行自己的方法的时候,仍然继承了父类的方法
# super(Son, self).sleep() # super() , Son的对象去调用父类的sleep的方法
# supper().sleep() # 参数不传也可以
GrandFather.sleep(self) # 通过 类名.方法名(self),调用父类的sleep的方法
s = Son()
s.study_python() # Son study python
s.eat() # Father eat
# 通过子类的对象可以使用父类的方法,子类没有则去父类找
s.sleep() # Son sleep 8 GrandFather sleep 10
# 子类没有,则去父类找,父类没有,则继续去父类的父类找...
- Run:
Son study python
Father eat
Son sleep 8
GrandFather sleep 10
“”"
1.单继承:深度优先
2.重写: 防止执行父类当中的方法
3.self永远指的是执行该方法的调用者
4.super(当前类,self).父类中的方法(arg)
“”"
- Tips:
- __init__方法也会继承,同实例方法一致
- 私有属性以及私有方法没有被继承
"""
思考:__init__ 方法是否会被继承?
会被继承,并且同实例方法一致;深度优先。
"""
class Father(object):
def __init__(self):
self.name = "amy"
self.age = 18
def test(self):
print("test")
def __test2(self):
print("__test2")
class Son(Father):
def get_test(self):
print(self.name)
self.test()
# 注意:私有属性不会被继承
# print(self.__age)
# 注意:私有方法也不会被继承
# self.__test2()
s = Son()
print(s.name)
# print(s.__age)
s.get_test()
- Run:
amy
amy
test
作业:
作业答案:
题1:
"""
玩个特别无聊的游戏 , 猜数字。
玩家输入一个数字与
计算机随机生成的数字作对比
当两个值相等时 , 则说明用户猜对了
注意 : 外部不能获取到计算机随机生成的值
"""
import random
num_max = int(input("请输入一个最大数字:"))
ran_num = random.randint(0, num_max)
flag = 1
while flag:
ipt_num = int(input(f"请猜一个0~{num_max}之间的数:"))
# print(ran_num)
if ipt_num == ran_num:
print(f"恭喜,猜对了!就是{ran_num}!")
break
elif ipt_num < ran_num:
print("猜小了!再猜!")
else:
print("猜大了!再猜!")
- Run:
请输入一个最大数字:20
请猜一个0~20之间的数:10
猜小了!再猜!
请猜一个0~20之间的数:15
猜小了!再猜!
请猜一个0~20之间的数:30
猜大了!再猜!
请猜一个0~20之间的数:18
猜大了!再猜!
请猜一个0~20之间的数:17
恭喜,猜对了!就是17!
Amy的答案:
"""
玩个特别无聊的游戏,猜数字。
玩家输入一个数字与
计算机随机生成的数字作对比
当两个值相等时,则说明用户猜对了
注意:外部不能获取到计算机随机生成的值
"""
import random
class GuessNun(object):
def __init__(self):
self.__rand_num = random.randint(1, 10)
print(f"random_number:{self.__rand_num}")
def guess_it(self):
ipt_num = int(input("请输入1-10的数:"))
while True:
if self.__rand_num == ipt_num:
print("猜对了!")
break
else:
ipt_num = int(input("猜的不对奥,请重新输入:"))
b = GuessNun()
b.guess_it()
Run:
random_number:4
请输入1-10的数:46
猜的不对奥,请重新输入:2
猜的不对奥,请重新输入:6
猜的不对奥,请重新输入:4
猜对了!
题2:没做对,等答案哈!
import time
class JianBing(object):
def __init__(self):
self.time0 = 0
def s_unripe(self):
print("煎饼是生的!")
def s_underdone(self):
print("煎饼是半生不熟的!")
def s_ripe(self):
print("煎饼熟了!")
def s_scorch(self):
print("煎饼焦了!")
def zuoliao(self):
print("煎饼要加大葱、 大蒜 、 烤肠吗?")
# def calcu_time()
# pass
# start = time.time()
# end = time.time()
# res = end - start
# return res
J = JianBing()
print("客人:师傅来个煎饼!", "师傅:好的马上做!", sep='\n')
# use_time = calcu_time()
use_time = 6
if 0 < use_time <= 3:
J.s_unripe()
elif 3 <use_time <= 5:
J.s_underdone()
elif 5 <use_time <= 8:
J.s_ripe()
J.zuoliao()
else:
J.s_scorch()
- Run:
Amy的答案:
"""
创建一个煎饼类 调用烹饪时长的方法累计煎饼状态
如果煎的时间在0-3之间则状态为生的
如果煎的时间在3-5之间则状态为半生不熟的
如果煎的时间在5-8之间则状态为全熟的
当时间超过8分钟状态焦了
并且还可以给煎饼添加作料
比如大葱(hhh),大蒜(hhh)?,烤肠等等
"""
class PanCake(object):
def __init__(self):
self.cake_status = "生的"
self.cake_level = 0
self.condiments = []
def __str__(self):
str1 = "+"
return f"煎饼的状态:{self.cake_status}, 煎饼的时长:{self.cake_level}, 添加的作料:{ str1.join(self.condiments)}"
def cook(self, cooked_time):
self.cake_level += cooked_time
if self.cake_level >= 0 and self.cake_level < 3:
self.cake_status = "生的"
elif self.cake_level >= 3 and self.cake_level < 5:
self.cake_status = "半生不熟"
elif self.cake_level >=5 and self.cake_level < 8:
self.cake_status = "全熟"
elif self.cake_level >= 8:
self.cake_status = "焦了"
def add_condiments(self, food):
self.condiments.append(food)
pc = PanCake()
pc.cook(1)
pc.cook(5)
pc.add_condiments("热狗")
pc.add_condiments("鸡蛋")
pc.add_condiments("洋葱")
print(pc)
Run:
煎饼的状态:全熟, 煎饼的时长:6, 添加的作料:热狗+鸡蛋+洋葱
