Python零基础速成班-第9讲-Python面向对象编程(上),对象和类、初始化、继承、重写、多态、类方法、组合
学习目标
- 修饰器
- 面向对象编程:对象和类、初始化、继承、重写、多态、类方法、组合
- 课后作业(3必做)
友情提示:将下文中代码拷贝到JupyterNotebook中直接执行即可,部分代码需要连续执行。
1、修饰器Decorators
Python的修饰器(decorator)是一个非常强大的功能,一种优秀的设计模式,将重复的内容抽象出来,赋予一个函数其他的功能,但又不去改变函数自身。使得代码极其简洁,易于维护。一些常见的案例是程序日志记录和调试。
如下例,我们定义一个日志输出程序fwithlog(func),传入函数名字func,输出函数名称并执行函数。
def fwithlog (func):
def f_log(*args,**kwargs):
print("我们当前执行程序的名字是:",func.__name__,"函数")
return func(*args,**kwargs)
return f_log
def add(a,b):
return(a+b)
fwithlog(add)(1,2)
我们当前执行程序的名字是: add 函数
3
这个时候,我们就可以使用修饰器@,在函数定义前加上@decorator_name,那么将先执行decorator_name函数,在执行后续函数。
如下例,我们定义修饰器@fwithlog,定义乘法函数multip,将先执行fwithlog输出日志,再执行multip乘法函数。
@fwithlog
def multip(a,b):
return(a*b)
multip(2,5)
我们当前执行程序的名字是: multip 函数
10
2、面向对象编程
面向对象编程(Object Oriented Programming,OOP)的思想主要是软件工程领域中针对软件设计而提出,这种软件开发思想比较自然地模拟了人类对客观世界的认识,成为当前软件工程学的主流方法。
它使得软件设计更加灵活,支持代码复用和设计复用,代码具有更好的可读性和可扩展性,大幅度降低了软件开发的难度。Python 是真正面向对象的高级动态编程语言,完全支持面向对象的基本功能,如封装、继承、多态以及对基类方法覆盖或重写。
2.1 什么是对象?Python中的一切都是一个对象,从数字到模块。
如下例,我们可以键入“num=7”创建值为7的整型对象,并将对象引用指定给名称“num”。
我们同样可以通过“int(7)”的方式直接创建一个值为7的整形对象。
num = 7
num1 = int(7)
print(num1)
print(type(num1))
7
<class 'int'>
2.2 定义类class
如果一个对象像一个盒子,那么一个类就像制作盒子的模具。
在现实生活中,要描述一类事物,既要说明它的特征,又要说明它的用途。如果以人这一类事物来举例,首先要给这类的事物起个名字,其次人类包括身高、体重、性别、职业等特征,每天还会产生跑步、说话等行为。当我们把人类的特征和行为组合在一起,就可以完整地描述人类。面向对象程序的设计思想正是基于这种设计,把事物的特征和行为包含在类中。其中,事物的特征当作类的属性,事物的行为当作类的方法,而对象是类的一个实例。
当我们要想创建一个对象,就需要先定义一个类,类一般由以下三个部分组成。
- 类名:类的名称,它的首字母必须是大写,如Person
- 属性:用于描述事物的特征,如人有姓名、性别、年龄等特征。
- 方法:用于描述事物的行为,如人具有说话、行走、写字等行为。
Python使用Class关键字来声明一个类,其基本语法格式如下:
Class 类名:
类的属性
类的方法
如下例,我们定义一个Person对象(类),类中包含一个pass语句。pass 是空语句,不做任何事情,一般用做占位语句,保持程序结构的完整性。
class Person():
pass
someone = Person()
type(someone)
__main__.Person
如下例,我们定义了一个Person类,类中有一个say方法。
class Person():
def say(self):
print("我是say方法")
someone = Person()
someone.say()
我是say方法
2.3 初始化类initial the class
self表示自己,指虚拟对象本身。我们在Python中使用self,在Java中使用this。
初始化类,我们需要创建一个def __init__方法,第一个参数必须是self,其他参数则是该类的其他属性,如下例Person类,还增加了姓名和性别属性。
除了姓名和性别,我们还增加了say和get_name方法,say将输出一段包含Person姓名的句子,get_name则将直接输出Person姓名。
我们初始化一个Person对象one,设置名字Adele,性别female,one.name可直接输出姓名,one.say()可以输出姓名句子,one.get_name()可以获取名字。
class Person():
def __init__(self, name,gender):
self.name = name
self.gender= gender
def say(self):
print("My name is",self.name,",nice to meet you!")
def get_name(self):
return self.name
one = Person('Adele','female')
print(one.name)
print(one.gender)
one.say()
print(one.get_name())
Adele
female
My name is Adele ,nice to meet you!
Adele
2.4 继承Inheritance
这里需要首先学习两个概念:父类和子类。我们可以这么想,当定义好一个类以后,我们又有一个新的类,这个新的类需要用到之前定义好类中的属性以及方法,并在其基础上添加新的属性和方法,此时我们便可使用继承的方法,将之前的类当作父类,将新定义的类当作子类。简而言之,父类中定义公共的属性和方法,子类中针对每一个对象再定义属于其的一些属性和方法。通常而言,其代码结构为:
父类
class 父类名称:
公共属性
def 父类方法1(self):
方法具体实现
子类
class 子类名称(父类名称):
def __init__(self, 子类属性列表):
self.子类属性 = 子类属性
def 子类方法1(self):
方法具体实现
此时,定义好的子类不仅可以访问子类属性以及子类方法,还可以访问父类属性以及父类方法。
如下例,我们定义父类Person包含两个公共方法say和get_name,子类OtherPerson继承父类,包含一个私有方法other_say。初始化子类对象two,则即可以访问父类方法say,也可以访问子类方法other_say。
#父类:包含两个公共方法say和get_name
class Person():
def __init__(self, name,gender):
self.name = name
self.gender= gender
def say(self):
print("My name is",self.name,",nice to meet you!")
def get_name(self):
return self.name
#子类:继承父类,包含一个私有方法other_say
class OtherPerson(Person):#括号内是需要继承的对象
def other_say(self):
print("我继承父类Person.",)
two = OtherPerson('Mike','male')
two.say()
two.other_say()
My name is Mike ,nice to meet you!
我继承父类Person.
如下例,我们详细分解继承的过程:
我们先定义一个动物类作为父类。
我们在父类中定义了一个公共属性live,以及两个公共方法eat、sleep。
class Animal:
live = True
def eat(self):
print("This animal is eating")
def sleep(self):
print("This animal is sleeping")
接着我们定义2个子类:
class Rabbit(Animal):
def __init__(self, feet):
self.feet = feet
def run(self):
print("This rabbit is running")
class Fish(Animal):
def swim(self):
print("This fish is swimming")
我们定义了兔子、鱼两个子类,他们都继承于父类Animal。其中我们还为兔子定义了私有属性feet。接下来我们通过实例化对象访问他们的方法,看是否能访问父类的方法:
rabbit = Rabbit(4)
fish = Fish()
rabbit.eat()
rabbit.sleep()
rabbit.run()
print(rabbit.feet)
fish.eat()
fish.swim()
This animal is eating
This animal is sleeping
This rabbit is running
4
This animal is eating
This fish is swimming
可见每个子类均能访问父类及其自身的属性和方法。
2.5 重写Override
子类拥有父类的所有方法和属性,可以直接享受父类中已经封装好的方法,不需要再次开发。当父类的方法实现不能满足子类需求时,可以对方法进行重写。
重写 父类方法有两种情况:
- 覆盖父类的方法。
- 对父类方法进行扩展。
覆盖父类方法:相当于在子类中定义一个和父类同名的方法并且实现。之后在运行时,只会调用子类中重写的方法,而不会调用父类封装的方法。
如下例,重写了父类say()方法,且增加了work属性。work='student’表示默认传入参数为student,当有值传入时,则以新值为准。
重新初始化子类对象NewPerson后,say()方法从nice to meet you! 变为重写后的i am a student。
class Person():
def __init__(self, name,gender):
self.name = name
self.gender= gender
def say(self):
print("My name is",self.name,",nice to meet you!")
class NewPerson(Person):
def __init__(self,name,gender,work='student'):
self.name = name
self.gender = gender
self.work = work
def say(self):
print("My name is {},i am a {}.".format(self.name,self.work))
Taylor = NewPerson('Taylor Swift','female')
Taylor.say()
Taylor = NewPerson('Taylor Swift','female','singer')
Taylor.say()
My name is Taylor Swift,i am a student.
My name is Taylor Swift,i am a singer.
对父类方法进行扩展:相当于子类的方法实现中包含父类的方法实现,父类原本封装的方法实现是子类方法的一部分,就可以使用扩展,具体方式为:
- 在子类中重写父类的方法。
- 在需要的位置使用 super()表明是调用父类方法。
- 代码其他的位置针对子类的需求,编写子类特有的代码实现。
如下例,super().init(name,gender)代表继承父层内容,self.name = 'Ms '+name表示将原有传入name值扩展为 Ms + name值。
class Person():
def __init__(self, name,gender):
self.name = name
self.gender= gender
class NewPerson(Person):
def __init__(self,name,gender,work='student'):
super().__init__(name,gender)
self.name = 'Ms '+name
self.gender = gender
self.work = work
def say(self):
print("My name is {},i am a {}.".format(self.name,self.work))
Taylor = NewPerson('Taylor Swift','female','singer')
Taylor.say()
My name is Ms Taylor Swift,i am a singer.
2.6 多态Duck Typing
多态是指基类的同一个方法在不同派生类对象中具有不同的表现和行为,多态可为不同数据类型的实体提供统一的接口。
派生类继承了基类的行为和属性之后,还会增加某些特定的行为和属性,同时还可能会对继承来的某些行为进行一定的改变,这恰恰是多态的表现形式。Python中主要通过重写基类的方法来实现多态。
如下例,我们先定义了Person类,拥有一个work()方法;接着定义了继承自Person的两个子类Student(学生)和Teacher(教师),在两个类中分别重写了work()方法。
然后定义了一个带参数的函数func(),在该函数中调用了work()方法。
最后分别创建了Student(学生)的实例对象student和Teacher(教师)类的实例对象teacher,并作为参数调用了func()函数。
class Person(object):
def work(self):
print("--Person--work--")
# 定义一个表示学生的类,继承自人类
class Student(Person):
def work(self): # 重写父类的方法
print("--认真学习--")
# 定义一个表示教师的类,继承自人类
class Teacher(Person):
def work(self): # 重写父类的方法
print("--传道授业--")
# 定义一个函数
def func(objects):
objects.work()
student = Student()
func(student)
teacher = Teacher()
func(teacher)
--认真学习--
--传道授业--
2.7 类方法Classmethod
在Python中,类方法可以使用修饰器@classmethod来标识,其语法格式如下:
class 类名:
@classmethod
def 类方法名(cls):
方法体
上述格式中,类方法的第1个参数为cls,它代表定义方法的类,可以通过cls访问类的属性。要想调用类的方法,即可以通过对象名调用类方法,又可以通过类名调用类方法,这两种方法没有任何区别。
如下例,我们统计A对象实例化对象的数量,即class A()被实例化的次数。
class A():
count = 0
def __init__(self):
A.count+=1
@classmethod
def kids(cls):#代表class A()
print("A对象被实例化 {} 次".format(cls.count))
a1 = A()
a2 = A()
a3 = A()
A.kids()
A对象被实例化 3 次
我们同样可以通过类方法给类赋值,如下例:我们定义了一个Student类,在Student中添加了类属性num和类方法setNum()。程序运行时,调用了类方法setNum()给类属性num重新赋了值,为“20230901”。
class Student():
num = 0
@classmethod
def set_Num(cls,newNum):
cls.num = newNum
Student.set_Num(20230901)
print(Student.num)
20230901
2.8 静态方法Staticmethod
在Python中,静态方法可以使用修饰器@staticmethod来标识,其语法格式如下:
class 类名:
@staticmethod
def 静态方法名():
方法体
上述格式中,静态方法的参数列表中是没有任何参数的。这就是它跟前面所学的实例方法的不同,没有self参数,这一点也导致其无法访问类的实例属性;它也没有cls参数,导致它也无法类属性。通过以上的描述,我们可以从中得出结论。静态方法跟定义它的类没有直接的关系,它只是起到类似于函数的作用。
如下例,我们定义了Good类,类中包括一个静态方法idea()。随后创建了一个类的对象good,分别通过类和类的对象来调用静态方法。
class Good(object):
@staticmethod
def idea():
print("我只想做一个安静地学生!")
Good.idea()
good = Good()
good.idea()
我只想做一个安静地学生!
我只想做一个安静地学生!
2.9 组合Composition
在Python中,可以使用现有类作为属性来创建新类,实现组合效果。
如下例,创建一个轮胎类Tire,包含一个对象解释器__repr__,输出我是一只轮胎;一个类方法tires(cls)统计轮胎被实例化次数。创建一个汽车类Car,包含前后左右4只轮胎,每一只则是一个新的轮胎类,即实例化轮胎类Tire()一次,总共4次。
class Tire():#以汽车轮胎为例
count = 0
def __repr__(self):
return("我是一只轮胎")
def __init__(self):
Tire.count += 1
@classmethod
def tires(cls):
print("轮胎被实例化 {} 次".format(cls.count))
class Car():
def __init__(self):
self.left_front_tire =Tire()
self.right_front_tire =Tire()
self.left_rear_tire =Tire()
self.right_rear_tire =Tire()
car = Car()
print(car.left_front_tire)
Tire.tires()
我是一只轮胎
轮胎被实例化 4 次
3、课后作业,答案在下一讲
1、创建一个Dog类,再创建一个FlyDog类,FlyDog继承Dog类中的方法。Dog类拥有方法eat(self)和bark(self),eat方法输出"我要吃东西!“bark方法输出"汪汪汪!”,FlyDog类拥有方法fly(self),fly方法输出"我能飞" ,最后创建一个mydog=FlyDog(),执行mydog.bark()和mydog.fly()
您的代码:
2、创建一个Date类,设置year、month、day三种属性,利用@classmethod创建一个新的类方法new_Date,该方法是将新传入的日期参数newDate(格式:yyyy-mm-dd,如2024-12-31)拆分成年、月、日,重新赋值一个新的Date类,并分别输出year、month、day。
提示:日期拆分可以使用year,month,day = map(int,date_from_str.split(‘-’))实现
您的代码:
3、创建一个Date类,设置year、month、day三种属性,利用@staticmethod创建一个新的静态方法validate_Date,该方法是将新传入的日期参数newDate(格式:yyyy-mm-dd,如2025-10-30)拆分并进行校验,当同时满足year<2999、month<=12、day<=31时,返回True,否则返回False,最后实例化对象并输出结果。
您的代码:
4、上一讲Python零基础速成班-第8讲-Python文件操作File I&O、高级文件处理模块shutil、CSV、JSON、多线程基础 课后作业及答案
1、生成多个邮件内容的数据字典并打印。
邮件姓名name列表存于D盘根目录下name.txt,内容为
"""张三
李四
王五
"""
邮件主题subject为"I Love Python",内容存放于D盘根目录下body.txt,内容content为
"""Beautiful is better than ugly.
Explicit is better than implicit.
"""
通过程序为每个人生成一个邮件内容的数据字典 格式为{“name”:{“subject”:" “,“content”:” "}},使用pprint打印出来
import pprint
maildict ={}
mailnote ={}
with open('D://name.txt','rt',encoding='utf-8') as namefile:
listname = namefile.read().splitlines()
for i in range(len(listname)):
with open('D://body.txt','rt',encoding='utf-8') as contentfile:
mailnote["subject"] = "I Love Python"
mailnote["content"] = contentfile.read()
maildict[listname[i]] = mailnote
pprint.pprint(maildict)
{'张三': {'content': 'Beautiful is better than ugly.\n'
'Explicit is better than implicit.',
'subject': 'I Love Python'},
'李四': {'content': 'Beautiful is better than ugly.\n'
'Explicit is better than implicit.',
'subject': 'I Love Python'},
'王五': {'content': 'Beautiful is better than ugly.\n'
'Explicit is better than implicit.',
'subject': 'I Love Python'}}
2、通过代码提取.jpg图片文件的长、高信息,不使用任何现成的库文件。图片的网络位置是"https://pica.zhimg.com/v2-6c272f6470a98eca718c2fd550bbd804_1440w.jpg?source=172ae18b" ,请下载后保存到D盘根目录,命名为Python.jpg。
提示:
1.高的信息在jpg byte文件的第164位,2字节,宽信息是之后的2字节
2.通过file.seek()语法将指针放置于163位,取2个字节位高,再取两个字节为宽
3.byte使用偏移算法转化为数字:byte[0]<<8+byte[1]
with open('D://Python.jpg','rb') as jpgfile:
jpgfile.seek(163)
highbyte = jpgfile.read(2)
high = (highbyte[0] << 8) + highbyte[1]
widthbyte = jpgfile.read(2)
width = (widthbyte[0] << 8) + widthbyte[1]
print("The image's high and width is {} * {}".format(high,width))
The image's high and width is 900 * 1440
3、创建一个《Python之禅.txt》的文件,将下列内容写入文件中,并保存在D盘根目录下。
优美胜于丑陋
明了胜于晦涩
简洁胜于复杂
复杂胜于凌乱
扁平胜于嵌套
可读性很重要
tips:可以使用 open write close三步走,或者使用with open语法
note="""优美胜于丑陋
明了胜于晦涩
简洁胜于复杂
复杂胜于凌乱
扁平胜于嵌套
可读性很重要
"""
with open(file='D://Python之禅.txt',mode='wt') as txtfile:
txtfile.write(note)
4、使用递归计算,有一堆8两重的苹果,你要切成重量相等的若干份,每一份重量不大于1两,你会怎么切?
先切两份,A1,A2
再把A1细分,A11,A12,再把A2细分,A21,A22
再把A11细分两份,直到都小于1两
def slice_apple(weight,time):
if weight <=1:
return
else:
a1,a2 =weight/2,weight/2
print("切第{}次,分为两份为{}和{}".format(time,a1,a2))
time+=1
slice_apple(a1,time)
slice_apple(a2,time)
slice_apple(8,1)
切第1次,分为两份为4.0和4.0
切第2次,分为两份为2.0和2.0
切第3次,分为两份为1.0和1.0
切第3次,分为两份为1.0和1.0
切第2次,分为两份为2.0和2.0
切第3次,分为两份为1.0和1.0
切第3次,分为两份为1.0和1.0
*(扩展)5、The Digit’s Image Data 数字图像学习。
数字图像数据是一个3D列表。本案例围绕它构建。运行下面的代码检索100个示例数据。取第一组数据。
扩展:(sklearn)是机器学习中常用的第三方模块,对常用的机器学习方法进行了封装,包括回归(Regression)、降维(Dimensionality Reduction)、分类(Classfication)、聚类(Clustering)等方法。
from sklearn import datasets
import random
random.seed(2016)#随机数种子,当使用random.seed(x)设定好种子之后,其中x可以是任意数字,比如10,那么每次调用生成的随机数将会是同一个。
digit_data = dict(datasets.load_digits())#读取数字图像学习库
sample_index =random.sample(range(len(digit_data['images'])),100)#取100个sample
digit_image =[digit_data['images'][i] for i in sample_index]#数字图像
digit_true_label=[digit_data['target'][i] for i in sample_index]
print(digit_image[0])
[[ 0. 0. 3. 12. 16. 9. 0. 0.]
[ 0. 0. 12. 9. 13. 16. 9. 0.]
[ 0. 3. 16. 5. 0. 8. 12. 0.]
[ 0. 0. 9. 16. 10. 13. 2. 0.]
[ 0. 0. 0. 4. 16. 12. 0. 0.]
[ 0. 0. 0. 11. 9. 16. 0. 0.]
[ 0. 0. 1. 15. 2. 12. 0. 0.]
[ 0. 0. 2. 16. 16. 6. 0. 0.]]
Question1: show the image by using the following code 输出数字图像。
import matplotlib.pyplot as plt
%matplotlib inline
sample_2d_array =digit_image[22]
plt.imshow(sample_2d_array,plt.cm.gray_r)
plt.show()
Question2: show thematrix by using the following code 输出数字矩阵。
import copy
new_image = copy.copy(digit_image[digit_true_label.index(0)])
print(new_image)#原始图像
for row in range(len(new_image)):
for col in range(len(new_image[row])):
if new_image[row][col] >10:#图像越深数值越大,我们这里取大于10,则取1
new_image[row][col] =1
else:
new_image[row][col] =0
print(new_image)#编辑后的图像
#图像处理
import matplotlib.pyplot as plt
plt.imshow(new_image,plt.cm.gray_r)
import numpy as np
for(i,j),value in np.ndenumerate(new_image):
if value<1:
plt.text(j,i,int(value),ha='center',va='center',color="#000000")
else:
plt.text(j,i,int(value),ha='center',va='center',color="#FFFFFF")
[[ 0. 0. 0. 9. 13. 3. 0. 0.]
[ 0. 0. 8. 15. 12. 15. 2. 0.]
[ 0. 0. 12. 8. 0. 15. 4. 0.]
[ 0. 3. 13. 0. 0. 10. 7. 0.]
[ 0. 8. 9. 0. 0. 13. 7. 0.]
[ 0. 2. 16. 4. 7. 16. 5. 0.]
[ 0. 0. 14. 14. 16. 15. 1. 0.]
[ 0. 0. 1. 12. 14. 4. 0. 0.]]
[[0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 1. 1. 1. 0. 0.]
[0. 0. 1. 0. 0. 1. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 1. 0. 0. 1. 0. 0.]
[0. 0. 1. 1. 1. 1. 0. 0.]
[0. 0. 0. 1. 1. 0. 0. 0.]]