面向对象是python中的难点也是重点,如果你想开发一个较为复杂数据结构或需要模块化的项目时,面向对象将会非常有用。
目录
8、 实战:创建一个狗类,其中含有姓名和年龄两个实例属性,以及跑和输出信息的方法。
前言
面向对象是一个抽象的编程思想(程序设计思想),将编程当成是一个事物(对象),将程序中的数据和操作封装在一起,形成一个对象。在面向对象编程中,程序的执行是通过对象之间的交互来完成的。
正文
一、面向对象与面向过程
1、面向过程
先分析出解决问题的步骤,再把步骤拆成一个个方法(函数),是没有对象去调用的,由这一句函数的顺序执行
例如选课:
# 选课的功能
def choose_course(stu):
stu['选择的课程'] = input(f'{stu["姓名"]}请输入你选择的课程')
# 学生的数据
stu1 = {'姓名': '长生'}
stu2 = {'姓名': '土豆'}
stu3 = {'姓名': '写代码也摆烂'}
# 调用选课的功能
choose_course(stu1)
choose_course(stu2)
choose_course(stu3)
print(stu1)
print(stu2)
print(stu3)
运行结果:
数据与功能是分开的,想要使用需要分别调用
2、面向对象
它以对象为中心,通过封装、继承和多态等概念来组织代码,使得代码更加模块化、可重用、易维护和扩展。把原本分散开来的数据与功能整合到一起,方便我们去使用。
# 定义类与类中的方法
class Student:
def __init__(self, stu, course):
self.stu = stu
self.course = course
def print_stu(self):
print(f'姓名:{self.stu},选择的课程:{self.course}')
# 实例化对象
stu1 = Student('长生', '数学')
stu2 = Student('土豆', '语文')
stu3 = Student('写代码也摆烂', '英语')
# 调用类中的方法
stu1.print_stu()
stu2.print_stu()
stu3.print_stu()
运行结果:
把原本分散开来的数据与功能整合到一起,方便我们去使用。
二、面向对象编程
1、类
' 物以类聚,人以群分' 类就是同意类型的集合体。类是抽象的概念、不是真实存在的事物。
例如:
狗这个类型:他的特征有体重、年龄、性别,他的一般行为有行走、跑步、吃等
在面向对象编程中:属性就是狗类的特征,功能就是狗类的行为,总结起来就是类
2、对象
对象是类的具体表现,通过类创建出来真实存在事物。如(叫来福的狗)
3、类与对象之间的关系
类存在的意义:是存放对象共同的功能和属性数据
类的作用:节省空间,减少代码冗余
先有类再有对象,对象通过类去创建
4、创建类与对象
基本语法:
"""
定义类:
class 类名():
pass
创建对象:
对象 = 类名()
"""
# 创建一个狗类
# 没有继承可以不写类名后面的括号
class Dog:
pass
# 实例化对象(创建对象)
dog = Dog
5、实例方法:由对象来使用,至少需要一个self参数
实例方法里的self 不需要主动传递,因为在对象调用方法的时候,会把对象的内存地址自动传递给实例方法的第一个self参数 实例方法里的self 谁调用它它就是谁,self必须有没有会报错
# 创建一个狗类
# 没有继承可以不写类名后面的括号
class Dog:
# 创建一个实例方法
def run(self):
print('run')
# 实例化对象(创建对象)
dog = Dog()
# 对象可以通过.去调用类中的实例方法
dog.run() # 调用类中的实例方法run 结果为输出:run
隐藏方法(私有方法):目的隔离复杂度,该方法不能再类的外部进行调用,只能再类的内部调用。
语法:再方法前面加上__(双下划线)
注意:私有方法其实只是换了一个名字,以__开头的属性,会自动换名字为_属性__方法名
class Myclass:
def __init__(self, name):
self.name = name # 实例化属性
self.__name = name # 实例化私有属性
# 定义私有方法,不可再类外部调用
def __Print(self):
print(f'name:{self.name}')
print(f'__name:{self.__name}')
def Print(self):
print(f'name:{self.name}')
print(f'__name:{self.__name}')
# 实例化对象
a = Myclass(12)
# 调用方法
a.Print()
# 调用私有属性,不建议使用这种方式,该方式为强行调用
a._Myclass__Print()
# 直接调用私有属性,报错
a.__Print()
6、类里定义属性
6.1 类属性
定义在类里面,再函数体外的属性,是所有对象都可以使用访问公用的。
注意:利用对象去修改类属性,只是给该对象创造了一个新的属性 只有通过类去修改,才是真正的修改
# 创建一个狗类
# 没有继承可以不写类名后面的括号
class Dog:
# 定义一个类属性
home = 'xx福利院的狗狗'
# 创建一个实例方法
def run(self):
print('run')
# 实例化对象(创建对象)
dog = Dog()
# 可以通过类名\对象名调用类属性
print(dog.home)
print(Dog.home)
# 修改类属性
Dog.home = 'yy福利院的狗狗' # 只能通过类名去修改类属性可以修改类属性
print(dog.home) # yy福利院的狗狗
print(Dog.home) # yy福利院的狗狗
# 利用对象去修改类属性,只是给该对象创造了一个新的属性
dog.home = 'xx福利院的狗狗'
print(dog.home) # xx福利院的狗狗 注意这里是重新创建了一个新的属性
print(Dog.home) # yy福利院的狗狗 利用对象不能修改,类属性
# 如果利用对象去修改过属性之后,则该对象的类属性,不能通过类去修改了,因为其是一个新的属性
Dog.home = 'zz福利院的狗狗'
print(dog.home) # xx福利院的狗狗 因为上面通过对象进行修改了,这里变成了一个新的属性,故不能修改
print(Dog.home) # zz福利院的狗狗
6.2 实例属性(实例对象绑定属性)
由对象来进行调用,至少有一个self参数,在调用实例方法时会把实例属性赋值给self。
创建实例属性有两种方法:
1)创建对象后,动态的对属性进行绑定
语法:实例对象.属性名 = 属性值
# 创建一个狗类
# 没有继承可以不写类名后面的括号
class Dog:
# 类对象
home = 'xx福利院的狗狗'
def run(self):
print('run')
# 实例化对象(创建对象)
dog = Dog()
# 创建一个实例属性,绑定的是对象,只能通过对象调用
dog.age = 17
# 输出属性值
print(dog.age) # 输出结果为: 17
注意:类属性,属于类,是公共的,可以通过对象和类名调用,但是 实例属性只属于对象,不能通过类名调用。
2)通过构造函数,进行动态绑定(推荐使用这种)
特殊的实例方法:在创建一个类的实例对象的时候,会自动隐式的调用该方法(__init__),通常用来做属性初始化或赋值操作。
# 创建一个狗类
# 没有继承可以不写类名后面的括号
class Dog:
# 使用__init__特殊方法来定义实例属性,通过传递参数的方式,将参数赋值给(self.属性名)
def __init__(self, name, age):
# 通过参数传递的方式将参数赋值给实例属性
self.name = name
self.age = age
# 调用实例方法时会把实例属性赋值给self。所有可以通过self直接调用实例属性
def Print(self):
print(self.name, self.age) #输出:来福 5
# 实例化对象(创建对象),同时传递属性值
dog = Dog('来福', 5)
dog.Print()
# 在类外部通过对象名来调用实例属性
print(dog.age, dog.name) # 输出:5 来福
3)私有属性
私有属性,用于隐藏数据属性,该属性不能再类的外部进行调用和修改,但是可以再类的内部访问。
语法:在属性名前面加上__
注意:私有属性其实只是换了一个名字,以__开头的属性,会自动换名字为_类名__属性 / _People__money。
class Mycalss:
def __init__(self, name):
self.name = name # 实例化属性
self.__name = name # 实例化私有属性
def Print(self):
print(f'name:{self.name}')
print(f'__name:{self.name}')
a = Mycalss(12)
# 通过在类内部的方法调用私有属性与普通属性
a.Print() # 输出:name:12\n__name:12
print(a.name) # 在外部调用属性,输出:12
print(a.__name) # 在外部调用私有属性,报错AttributeError: 'Mycalss' object has no attribute '__name'
#零
print(f'__name:{a._Myclass__name}+"4"') # 输出:__name:12+"4"
隐藏并不是真正的目的,数据隐藏起来是不想在外部能直接去修改或访问,以及防止类的内部变量与外部变量相似,可以允许间接的来访问,在类内部单独设置了一个方法(通过该方法访问私有,修改私有属性),方法里额外增加逻辑去严格控制对数据修改
7、魔法方法
在python面向对象中还包含了许多特殊的方法,这些方法通常被称为‘魔法方法’,或双下划线方法。因为他们通常以双下划线开始和结束。
比如,我们上面提到的__init__被称为初始化方法,主要用于初始化实例属性。除此之外还有很多。如:表示方法 (__str__
和 __repr__),
比较方法 (__eq__
, __ne__
, __lt__
, __le__
, __gt__
, __ge__),
访问控制方法 (__getattr__
, __setattr__
, __delattr_)等。但是这些用的比较少,需要注意的就是表示方法__str__。
表示方法__str__
返回对象的描述信息给实例对象,显示更加友好,可读性更强
class Mycalss:
# 创建魔法方法__str__
def __str__(self):
return '这是我创建的空对象'
class Myclass1:
pass
a = Mycalss() # 将魔法方法__str__的返回值赋给a,但是不影响a调用其他的方法与属性
print(a)
b = Myclass1() # 输出对象名
print(b)
8、 实战:创建一个狗类,其中含有姓名和年龄两个实例属性,以及跑和输出信息的方法。
# 创建狗类, 没有继承可以不写括号
class Dog:
# 定义狗类的属性
def __init__(self, name, age):
self.name = name
self.age = age
# 定以跑的方法
def run(self):
print(self.name, 'run')
# 定义打印信息的方法
def info(self):
print(f'name:{self.name},age:{self.age}')
# 实例化对象(创建对象)
dog1 = Dog('来福', 5)
# 通过对象调用狗类中的方法
dog1.info()
dog1.run()
# 可以通过类创建多个对象
dog2 = Dog('招财', 6)
dog2.info()
dog2.run()
运行结果:
三、面向对象高级编程
1、封装
封装就是将复杂的信息、流程给包起来,内部处理,让使用者只需要通过简单的操作步骤,就能实现。类本质上就是封装,类是一个抽象的概念是多个对象由属性和方法封装而成的。
封装包括:1. 把属性和方法封装到一个抽象的类中
2. 外界只需要通过类去创建对象, 对象去调用类中的方法
3. 方法细节封装在内的内部
2、继承
含义:指的是多个类之间的所属关系,即子类默认继承父类的所有属性和方法。
作用:解决代码的冗余性,相同的代码不需要重复去写
注意:如果父类的方法或属性不想让子类去使用,可以将其设置为私有。同时需要注意属性以及方法的调用顺序,都是采用就近原则。例如,如果子类存在与父类中同名的方法或属性,会优先调用子类中的方法或属性。
1)单继承
单继承是最简单的继承形式,子类继承自一个父类。在单继承中,子类将获得父类的所有公共和保护成员(属性和方法)。
# 创建一个父类
class Parent:
# 在父类中创建实例属性
def __init__(self):
self.parent_attr = "I'm from Parent"
# 在父类中创建方法
def parent_method(self):
return "This is Parent's method"
# 创建一个子类,并继承自Parent类
class Child(Parent):
def child_method(self):
return "This is Child's method"
# 创建一个Child对象并测试
child = Child()
# 通过子类调用父类中的属性与方法
print(child.parent_attr) # 输出: I'm from Parent
print(child.parent_method()) # 输出: This is Parent's method
2)多重继承(多层继承)
子类拥有父类以及父类的父类中的属性和方法
# 创建一个父类
class Parent:
# 在父类中创建实例属性
def __init__(self):
self.parent_attr = "I'm from Parent"
# 在父类中创建方法
def parent_method(self):
return "This is Parent's method"
# 创建一个子类,并继承自Parent类
class Child(Parent):
def child_method(self):
return "This is Child's method"
# 再次创建一个子类,继承自Child
class Child2(Child):
pass
# 创建一个Child2对象并测试
child2 = Child2()
# 通过子类调用父类的父类中的属性与方法
print(child2.parent_attr) # 输出: I'm from Parent
print(child2.parent_method()) # 输出: This is Parent's method
# 通过子类调用父类中的方法
print(child2.child_method()) # 输出:This is Child's method
3)多继承
子类继承自多个父类,且能使用父类中的方法与属性,称之为多继承。
注意:当不同父类中,存在同名的属性或方法,那么实例对象将会采用就近原则,也就是先继承哪个父类则调用哪个父类中的方法与属性。
# 创建第一个父类
class Parent1:
# 在父类中创建实例属性
def __init__(self):
self.parent_attr = "I'm from Parent1"
# 在父类中创建方法
def parent_method(self):
print('这是Parent1的parent_method方法')
# 创建第二个父类
class Parent2:
# 在父类中创建实例属性
def __init__(self):
self.parent_attr = "I'm from Parent2"
# 在父类中创建方法
def parent_method(self):
print('这是Parent2的parent_method方法')
# 创建一个子类,并继承自Parent1与Parent2
class Child(Parent1,Parent2):
def child_method(self):
return "This is Child's method"
# 创建一个实例对象
child = Child()
# 调用类中继承的方法
# 需要注意这里会采用就近原则,因为我先继承自Parent1,所以调用parent_attr时优先调用Parent1中的属性与方法
print(child.parent_attr)
child.parent_method()
# 内置属性:__mro__
# 作用:多继承有同名方法时,会按照__mro__的输出结果从左往右的顺序查找
print(Child.__mro__)
运行结果:
4)方法重写
在现实的编程中,为了能应对多种不同的情况,父类的方法并不能做到面面俱到,当父类的方法不能满足子类的需求时,可以实现对父类方法的重写。重写一般有两种情况:
4.1 直接覆盖父类的方法或属性
原理:因为在继承中会采用就近原则,如果在子类中再次定义与父类同名的方法或属性,则会覆盖掉父类中的方法或属性。
# 创建第一个父类
class Parent:
# 在父类中创建实例属性
def __init__(self):
self.parent_attr = "I'm from Parent1"
self.parent_age = 18
# 在父类中创建方法
def parent_method(self):
print('这是Parent的parent_method方法')
# 创建一个子类,并继承自Parent1与Parent2
class Child(Parent):
def __init__(self):
# 创建一个与父类同名的属性
self.parent_attr = "I'm from Child"
# 创建一个与父类同名的方法
def parent_method(self):
print('这是Child的parent_method方法')
# 创建一个实例对象
child = Child()
# 调用Child中的属性和方法
print(child.parent_attr)
child.parent_method()
# 值得注意的是,因为在子类中重写了init方法,所以代码不会再执父类中的init方法,则父类中的parent_age属性是不可用的,如果想继续使用,需要再子类中实例化该属性
print(child.parent_age)
运行结果:
4.2 在原有的基础上拓展属性或方法
4.2.1 通过父类名去调用父类的属性或方法(了解即可)
因为该方法需要明确父类的类名,且如果父类名进行修改或者继承关系发生改变,则需要修改所有的父类名,不利于代码的维护,所有并不推荐使用此方法,了解即可。
# 创建第一个父类
class Parent:
# 在父类中创建实例属性
def __init__(self, name, age):
self.parent_name = name
self.parent_age = age
# 在父类中创建方法
def parent_method(self):
print('这是Parent的parent_method方法')
# 创建一个子类,并继承自Parent1与Parent2
class Child(Parent):
def __init__(self, name, age, sex):
Parent.__init__(self, name, age) # 调用父类的init方法
self.sex = sex # 在父类的init方法的基础下创建一个新的属性
# 创建一个与父类同名的方法
def Print(self):
Parent.parent_method(self) # 调用父类的方法,注意要写上self
print(f'name:{self.parent_name},age:{self.parent_age},sex:{self.sex}') # 调用父类的方法的同时新增一个语句
# 创建一个实例对象
child = Child('写代码', 18, 'man')
# 调用Child中的属性和方法
print(child.parent_name,child.sex)
child.Print()
运行结果:
4.2.2 使用super()特殊对象拓展父类的属性或方法
该对象是专门用来调用父类的属性和方法的,super()可以直接代替,该类的父类名,即可避免当父类名修改时需要对调用代码的反复修改。一般的如果遇见需要拓展父类功能和属性时,推荐使用该方法。
# 创建第一个父类
class Parent:
# 在父类中创建实例属性
def __init__(self, name, age):
self.parent_name = name
self.parent_age = age
# 在父类中创建方法
def parent_method(self):
print('这是Parent的parent_method方法')
# 创建一个子类,并继承自Parent1与Parent2
class Child(Parent):
def __init__(self, name, age, sex):
super().__init__(name,age) # 注意该方法与使用父类名不同,不需要写self
self.sex = sex # 在父类的init方法的基础下创建一个新的属性
# 创建一个与父类同名的方法
def Print(self):
super().parent_method() # 调用父类的方法
print(f'name:{self.parent_name},age:{self.parent_age},sex:{self.sex}') # 调用父类的方法的同时新增一个语句
# 创建一个实例对象
child = Child('写代码', 18, 'man')
# 调用Child中的属性和方法
print(child.parent_name,child.sex)
child.Print()
运行结果:
3、 多态
多态是面向对象编程中的一个重要概念,它允许不同类的对象对于相同的方法作出不同的响应。简单来讲就是多个类继承自同一个父类,每个不同的类都继承了父类中的方法,但是不同的类调用父类中的方法表现出来的结果不同。
示例:以下,我们以动物类为父类,不同的动物都是存在叫声的行为,以此为方法,来创建一个多态性的类。
# 创建一个名为动物的父类
class Animal:
# 动物都存在叫声的行为,创建一个叫的方法
def speak(self):
print('动物的叫声', end=':')
# 定义不同的子类,子类进行重写父类的方法
class Dog(Animal):
def __init__(self, name):
self.name = name
def speak(self):
super().speak()
print(f'狗{self.name}:汪汪汪')
class Cat(Animal):
def __init__(self, name):
self.name = name
def speak(self):
super().speak()
print(f'猫{self.name}:喵!')
class Duck(Animal):
def __init__(self,name):
self.name = name
def speak(self):
super().speak()
print(f'鸭{self.name}:嘎嘎嘎!')
# 定义一个函数,做为接受任意Animal类型的对象的接口,并调用speak方法
def make_sound(animal):
animal.speak()
# 创建不同的动物对象
dog = Dog('来福')
cat = Cat('咪咪')
duck = Duck('可达')
# 调用make_sound函数,传入不同的动物对象
make_sound(dog)
make_sound(cat)
运行结果:
程序的多态性,在大大增加程序的灵活性以及拓展性的同时也能更加方便程序的使用者,不管实例对象怎么变,都只需要去了解父类中统一的实例方法,在通过使用函数定义的接口,便能达到目的。
为了能够更加规范,面向对象的多态性,我们可以通过导入abc(Abstract Base Classes)模块提供的装饰器用于声明一个抽象方法。抽象方法是指在父类中定义了方法的名称和参数列表,但没有具体的实现,具体的实现留给子类来完成。子类必须实现父类中的抽象方法,否则会报错。
import abc # 导入abc模块
# 创建一个名为动物的父类
class Animal(abc.ABC): # 继承abc模块中的ABC,才能使用abc模块中的装饰器
# 动物都存在叫声的行为,创建一个叫的方法
@abc.abstractmethod # 使用abc模块中的装饰器,代表它下面定义的一个方法为抽象方法
def speak(self): # 定义一个抽象方法,之后的子类都必须拥有该方法,否则会报错
print('动物的叫声', end=':')
# 定义不同的子类,子类进行重写父类的方法
class Dog(Animal):
def __init__(self, name):
self.name = name
def speak(self):
super().speak()
print(f'狗{self.name}:汪汪汪')
# 继承自Animal类,但是没有speak方法
class Cat(Animal):
def __init__(self, name):
self.name = name
# 创建不同的动物对象
dog = Dog('来福') # 继承自Animal,有speak方法正常运行
dog.speak()
cat = Cat('咪咪') # 没有speak方法实例化对象时会报错
运行结果:
4、类方法
类方法是一种特殊类型的方法,它是定义在类上而不是实例上的方法。类方法使用装饰器@classmethod
来标识,在方法的参数列表中第一个参数通常被命名为cls(即该类本身,可代替类名)
,它用于引用类本身而不是类的实例。
作用1:修改类属性
在前文中我们提到过类属性只能通过类名来修改,使用实例对象来修改是给该对象新增一个属性。除了上述方法我们也可以通过类方法来修改类属性,具体如下:
class A:
s = '写代码不摆烂' # 创建一个类属性
def __init__(self, sr): # 实例化属性
self.name = sr
# 通过@classmethod装饰器创建类方法
@classmethod
def change(cls, name):
# self.sr # 注意类方法中不能调用实例属性,这里没有self
print('cls == class, 可以用于代替类名')
cls.s = name # 通过类方法修改类属性
# 通过类名使用类方法
A.change('写代码也摆烂')
print(A.s)
# 类方法也可以通过实例对象来使用,但是不推荐这样用,主要还是通过类名来使用,了解即可
a = A('写代码也摆烂')
a.change('写代码也摆烂')
print(A.s)
作用2:提供一个新造对像
通过类方法来约束,实例化的对象时参数是否满足要求
# 假设需要创建要给三角形类,并计算其面积和周长,
# 但是创建对象前,我们需要判断提供的三条边是否能组成三角形,则我们可以利用类对象来实现
class Triangle:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def S(self):
print('计算面积')
def C(self):
print('计算周长')
@classmethod # 创建类方法
def varify(cls, a, b, c):
if a + b > c and b + c > a and a + c > b: # 判断是否能组成三角形
print('实例化对象成功')
return cls(a, b, c) # 因为cls为类本身,所以可以通过cls来实例化对象,并返回
else:
print('实例化对象失败,不符合创建三角形的定理')
exit() # 终止程序
s = input('请输入三角形的三条边(用英文逗号隔开):').split(',') # 使用split分割
s = [int(s[i]) for i in range(len(s))] # 使用列表推导式,【python】列表(List)与元组(Tuple)文章有提过
t = Triangle.varify(s[0], s[1], s[2]) # 通过调用类方法来实例化对象
运行结果:
1、输入3,4,5
2、输入1,2,3
5、静态方法
语法:方法的上面@staticmethod
静态方法可以使用对象访问,也可以使用类访问,因为静态方法不需要传递类对象(cls),也不需要传递实例对象(self),所以一般用于不需要实例属性和不需要使用类属性时,可以减少参数的传递。
class Triangle:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def are(self):
print('计算面积')
def perimeter(self):
print('计算周长')
@staticmethod
def varify(a,b,c): # 没有self / cls
print('静态方法和普通的函数是一样的 ')
if a + b > c and b + c > a and a + c > b:
print('能创建')
return True
else:
print('不能创建')
return False
res = Triangle.varify(1,2,5)
总结
类中定义方法:
实例方法
类方法 @classmethod
静态方法 @staticmethon
类中定义属性:
实例属性
类属性
使用实例属性 配合 实例方法里有self
使用类属性配合类方法cls代表就是类本身
欢迎指正!