前言:本篇讲解面向对象的三大特征(封装,继承,多态),还有比较细致的(类属性类方法,静态方法),分步骤讲解,比较适合理清楚三大特征的思路
面向对象的的三大特征:
封装------根据职责将属性和方法封装到一个抽象的类中 ; 增强代码的安全性
继承------实现代码的重用,相同的代码不需要重复的编写 ; 增强代码的可重用性
多态------不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度 。 增强代码的可扩展性
封装
1.1 概念:
封装是将数据(属性)和操作数据的方法(行为)捆绑在一起,形成一个独立的单元,即对象。同时,对外部隐藏对象的内部实现细节,只提供公开的接口供外部访问和操作,以此增强数据的安全性和程序的可维护性。
把现实世界中的主体中的属性和方法书写到类的里面的操作即为封装封装可以为属性和方法添加为私有权限,不能直接被外部访问
1.2 封装中的私有属性和私有方法:
在面向对象代码中,我们可以把属性和方法分为两大类:公有(属性、方法)、私有(属性、方法)Python:公有(属性、方法),私有(属性、方法)Java:公有(属性、方法),受保护(属性、方法),私有(属性、方法)公有属性和公有方法:无论在类的内部还是在类的外部我们都可以对属性和方法进行操作。但是有些情况下,我们不希望在类的外部对类内部的属性和方法进行操作。我们就可以把这个属性或方法封装成私有形式。
1.3 定义方式:
在 Python 里,通过命名约定来实现访问控制。以单下划线
_
开头的属性和方法被视为受保护的,虽外部仍可访问,但开发者通常将其作为内部使用的标识;以双下划线__
开头的属性和方法被视为私有的,外部不能直接访问。
class BankAccount:def __init__(self, balance):# 私有属性self.__balance = balancedef deposit(self, amount):self.__balance += amountreturn self.__balancedef withdraw(self, amount):if amount <= self.__balance:self.__balance -= amountreturn self.__balanceelse:return "余额不足"def get_balance(self):return self.__balanceaccount = BankAccount(1000)
# 不能直接访问私有属性
# print(account.__balance) 会报错
print(account.get_balance())
当然私有属性不能在类的外部被直接访问,我们也有方法。
1.4 私有属性设置与访问"接口"
读取私有属性的接口(Getter 方法)
为了让外部代码能够安全地获取私有属性的值,可以在类中定义一个公共的方法,通常称为 Getter 方法。
class Person:def __init__(self, name, age):self.__name = nameself.__age = age# Getter 方法,用于获取私有属性 __name 的值def get_name(self):return self.__name# Getter 方法,用于获取私有属性 __age 的值def get_age(self):return self.__agep = Person("Alice", 25)
print(p.get_name()) # 输出: Alice
print(p.get_age()) # 输出: 25
修改私有属性的接口(Setter 方法)
如果需要允许外部代码修改私有属性的值,可以在类中定义一个公共的方法,通常称为 Setter 方法。在 Setter 方法中,可以添加一些验证逻辑,确保修改的值是合法的。
class Person:def __init__(self, name, age):self.__name = nameself.__age = age@propertydef name(self):return self.__name@name.setterdef name(self, new_name):if isinstance(new_name, str):self.__name = new_nameelse:print("名称必须是字符串类型")@propertydef age(self):return self.__age@age.setterdef age(self, new_age):if isinstance(new_age, int) and new_age >= 0:self.__age = new_ageelse:print("年龄必须是一个非负整数")p = Person("Alice", 25)
print(p.name) # 输出: Alice
p.name = "Bob"
print(p.name) # 输出: Bob
总结:在Python中,一般定义函数名' get_xx '用来获取私有属性,定义' set_xx '用来修改私有属性值。
继承
2.1 概念:
继承是指一个类(子类、派生类)可以继承另一个类(父类、基类)的属性和方法。子类能够复用父类的代码,还可添加新属性和方法,或者修改父类的方法以实现不同行为。
2.2 作用:
代码复用:避免重复编写相同代码,提高开发效率。例如多个类有共同属性和方法,可将这些提取到父类,子类直接继承使用。
功能扩展:子类在继承父类基础上,能添加新属性和方法,满足不同需求。
建立类的层次结构:有助于组织和管理代码,体现类之间的关系。
2.3 基本语法:
class B(object):pass
class A(B):pass
a = A()
a.B中的所有公共属性
a.B中的所有公共⽅法
2.4 单继承:
一个子类只继承一个父类。不能同时继承多个类。这个类会有具有父类的属性和方法。这是最常见的继承方式,结构清晰,易于理解和维护。
基本语法:
# 1、定义⼀个共性类(⻋类)
class Car(object):def run(self):print('i can run')
# 2、定义汽油⻋
class GasolineCar(Car):pass
# 3、定义电动⻋
class EletricCar(Car):pass
bwm = GasolineCar()
bwm.run()
单继承的传递性:
单继承的传递性可以描述为:如果类
C
继承自类B
,而类B
又继承自类A
,那么类C
不仅会继承类B
中定义的属性和方法,还会继承类A
中定义的属性和方法。这种传递性可以一直延续下去,无论继承层次有多深,子类都能获取到其所有祖先类的非私有属性和方法。
class C(object):def func(self):print('我是C类中的相关⽅法func')
class B(C):def funb(self):print('我是B类中的相关⽅法funb')
class A(B):def funA(self):print('我是A类中的相关⽅法funa')a = A()
a.funa()
b = B()
b.funb()
c = C()
c.func()
2.5 object:
在 Python 中,
object
是所有类的基类,在 Python 面向对象编程体系里扮演着非常基础且重要的角色,所有类默认都会隐式继承自object
类。也就是说,即使你定义类时没有显式指定基类,它实际上也是object
类的子类。
# 定义一个没有显式指定基类的类
class MyClass:pass# 检查 MyClass 是否是 object 的子类
print(issubclass(MyClass, object)) # 输出: True
2.6 多继承:
多继承是指一个子类可以同时继承多个父类的特性。也就是说,子类可以拥有多个父类所定义的属性和方法,从而可以复用多个不同父类的代码。
# 定义第一个父类
class Parent1:def method1(self):print("This is method1 from Parent1")# 定义第二个父类
class Parent2:def method2(self):print("This is method2 from Parent2")# 定义子类,继承自 Parent1 和 Parent2
class Child(Parent1, Parent2):pass# 创建子类的实例
child = Child()# 调用从 Parent1 继承的方法
child.method1()# 调用从 Parent2 继承的方法
child.method2()
2.7 重写:
子类重写父类中的属性与方法,子类可以重写父类的方法,即子类定义一个与父类同名的方法,这样在调用该方法时,会优先使用子类的方法。
基本语法:
class Father(object):属性⽅法
class Son(Father):⽗类属性和⽅法⾃⼰的属性和⽅法(如果⼦类中的属性和⽅法与⽗类中的属性或⽅法同名,则⼦类中的属性或⽅法会对⽗类中同名的属性或⽅法进⾏覆盖(重写))
示例代码:
class Animal(object):def eat(self):print('i can eat')# 公共⽅法def call(self):print('i can call')
class Dog(Animal):# 重写⽗类的call⽅法def call(self):print('i can 汪汪汪')
class Cat(Animal):# 重写⽗类的call⽅法def call(self):print('i can 喵喵喵')
wangcai = Dog()
wangcai.eat()
wangcai.call()
miaomiao = Cat()
miaomiao.eat()
miaomiao.call()
2.8 super:
在 Python 面向对象编程的继承机制里,
super()
是一个非常实用的内置函数,它主要用于调用父类(超类)的方法。super()
函数返回一个代理对象,该对象会将方法调用委托给父类或兄弟类,从而可以方便地调用父类的方法。
调用父类的构造方法
在子类的构造方法中,经常需要调用父类的构造方法来初始化从父类继承的属性。
class Animal:def __init__(self, name):self.name = nameprint(f"Animal 的构造方法被调用,名字是 {self.name}")class Dog(Animal):def __init__(self, name, breed):# 调用父类的构造方法super().__init__(name)self.breed = breedprint(f"Dog 的构造方法被调用,品种是 {self.breed}")# 创建 Dog 类的实例
dog = Dog("旺财", "金毛寻回犬")
在上述代码中,Dog
类继承自 Animal
类。在 Dog
类的构造方法 __init__
中,使用 super().__init__(name)
调用了父类 Animal
的构造方法,先完成父类属性的初始化,再初始化子类特有的属性。
调用父类的其他方法
除了构造方法,super()
还可以用于调用父类的其他普通方法。当子类重写了父类的某个方法,但又需要在子类方法中使用父类方法的功能时,就可以使用 super()
来调用父类的方法。
class Animal:def speak(self):return "动物发出声音"class Dog(Animal):def speak(self):# 调用父类的 speak 方法parent_speak = super().speak()return f"{parent_speak},具体来说是汪汪叫"# 创建 Dog 类的实例
dog = Dog()
print(dog.speak())
super()
函数的工作原理与 Python 的方法解析顺序(Method Resolution Order,MRO)有关。MRO 定义了在多继承情况下,Python 查找方法的顺序。super()
会根据 MRO 来确定要调用的父类方法。可以通过类的 __mro__
属性查看类的 MRO。
2.9 方法解析顺序(MRO)
当多个父类中存在同名方法时,Python 需要确定调用哪个父类的方法,这就涉及到方法解析顺序(Method Resolution Order,MRO)。可以通过类的
__mro__
属性查看方法解析顺序。
class A:def method(self):print("Method from A")class B(A):def method(self):print("Method from B")class C(A):def method(self):print("Method from C")class D(B, C):pass# 查看 D 类的方法解析顺序
print(D.__mro__)# 创建 D 类的实例
d = D()
# 调用 method 方法
d.method()
在上述代码中,D
类继承自 B
和 C
,而 B
和 C
又都继承自 A
。D.__mro__
会输出一个元组,显示方法解析的顺序。当调用 d.method()
时,Python 会按照 MRO 的顺序查找 method
方法,找到后就会调用该方法。
多态
3.1 概念:
![](https://i-blog.csdnimg.cn/direct/ffeedd95b807495abb9092c2a7af39b9.png)
3.2 基本语法:
'''
⾸先定义⼀个⽗类,其可能拥有多个⼦类对象。当我们调⽤⼀个公共⽅法(接⼝)时,传递的对象不
同,则返回的结果不同。
'''
class Fruit(object): # ⽗类Fruitdef makejuice(self):print('i can make juice')
class Apple(Fruit): # ⼦类:苹果# 重写⽗类⽅法def makejuice(self):print('i can make apple juice')
class Banana(Fruit): # ⼦类:⾹蕉# 重写⽗类⽅法def makejuice(self):print('i can make banana juice')
class Orange(Fruit): # ⼦类:橘⼦# 重写⽗类⽅法def makejuice(self):print('i can make orange juice')
# 定义⼀个公共接⼝(专⻔⽤于实现榨汁操作)
def service(obj):# obj要求是⼀个实例化对象,可以传⼊苹果对象/⾹蕉对象obj.makejuice()
# 调⽤公共⽅法
service(Orange())
如果加号的两边都是数值类型的数据,则加号代表运算符如果加号的两边传入的是字符串类型的数据,则加号代表合并操作,返回合并后的字符串'a' + 'b' = 'ab'如果加号的两边出入序列类型的数据,则加号代表合并操作,返回合并后的序列[1, 2, 3] + [4, 5, 6] = [1, 2, 3, 4, 5, 6]
类属性
类属性是一种重要的概念,它与实例属性共同构成了类的数据部分。类属性是属于类本身的属性,它被该类的所有实例对象所共享。类属性在类定义中直接声明,不依赖于某个具体的实例对象。
4.1 特点:
- 共享性:所有由该类创建的实例对象都可以访问类属性,类属性只有一份拷贝,存储在类的命名空间中。
- 独立性:类属性独立于实例属性,即使没有创建任何实例对象,类属性依然存在于类中。
4.2 基本语法:
class Car:# 定义类属性wheels = 4def __init__(self, brand):self.brand = brand# 通过类名访问类属性
print(Car.wheels) # 输出: 4# 创建类的实例
my_car = Car("宝马")
# 通过实例对象访问类属性
print(my_car.wheels) # 输出: 4
在上述代码中,wheels
是 Car
类的类属性,既可以通过类名 Car
直接访问,也可以通过类的实例对象 my_car
进行访问。
4.3 修改类属性
类属性可以通过类名进行修改,修改后所有实例对象访问该类属性时都会得到新的值。
class Car:wheels = 4def __init__(self, brand):self.brand = brand# 修改类属性
Car.wheels = 6# 创建类的实例
my_car = Car("宝马")
# 通过实例对象访问修改后的类属性
print(my_car.wheels) # 输出: 6
需要注意的是,如果通过实例对象去修改类属性,实际上会为该实例创建一个同名的实例属性,而不会影响类属性本身。
4.4 类属性与实例属性的对比:
- 存储位置:类属性存储在类的命名空间中,而实例属性存储在每个实例对象的命名空间中。
- 访问方式:类属性可以通过类名和实例对象访问,实例属性只能通过实例对象访问。
- 数据共享性:类属性被所有实例对象共享,一个实例对类属性的修改会影响其他实例;实例属性是每个实例独有的,修改一个实例的属性不会影响其他实例。
4.5 综上所述
类属性是类定义中声明的属性,被所有实例共享。可通过类或实例访问修改,用于存储类级别的数据和状态。
类方法
类方法是绑定到类而不是实例的方法。在 Python 中,使用
@classmethod
装饰器来定义类方法。类方法的第一个参数通常命名为cls
,它代表类本身,通过cls
可以访问和操作类的属性和调用类的其他方法。
5.1 基本语法
class MyClass:class_attribute = "这是类属性"@classmethoddef class_method(cls):return cls.class_attribute# 调用类方法
result = MyClass.class_method()
print(result) # 输出: 这是类属性
5.2 访问和修改类属性
类方法可以直接访问和修改类属性,因为它的第一个参数 cls
代表类本身。
class Student:total_students = 0def __init__(self, name):self.name = nameStudent.total_students += 1@classmethoddef get_total_students(cls):return cls.total_students# 创建 Student 类的实例
s1 = Student("Alice")
s2 = Student("Bob")# 调用类方法获取学生总数
total = Student.get_total_students()
print(total) # 输出: 2
在上述代码中,get_total_students
是一个类方法,通过 cls.total_students
访问了类属性 total_students
。
5.3 调用方式
类方法可以通过类名直接调用,也可以通过实例对象调用,但推荐使用类名调用,因为类方法的主要作用是操作类本身。
class MyClass:@classmethoddef class_method(cls):print("这是类方法")# 通过类名调用
MyClass.class_method()
# 通过实例调用
obj = MyClass()
obj.class_method()
5.4 与实例方法和静态方法的对比
与实例方法对比
- 参数不同:实例方法的第一个参数是
self
,代表实例对象本身,通过self
可以访问和操作实例属性;类方法的第一个参数是cls
,代表类本身,通过cls
可以访问和操作类属性。 - 调用方式不同:实例方法必须通过实例对象调用;类方法可以通过类名或实例对象调用。
- 用途不同:实例方法主要用于操作实例对象的属性和实现实例对象的行为;类方法主要用于操作类属性和创建工厂方法等与类本身相关的操作。
与静态方法对比
- 参数不同:静态方法没有特殊的第一个参数,它不与类或实例有直接的绑定关系;类方法有第一个参数
cls
,代表类本身。 - 功能用途不同:静态方法通常用于执行一些与类和实例的状态无关的通用操作,例如数学计算、字符串处理等;类方法主要用于操作类属性、创建工厂方法等与类本身相关的操作。
5.5 综上所述
不需要创建类的对象,通过 类名. 的⽅式就可以访问类的属性或者调用类的方法 。
用@classmethod 修饰的方法为类方法;
类方法的参数为 cls,在类方法内部通过 cls.类属性 或者 cls.类方法 来访问同一个类中的其他类属性和类方法;
类方法不需要实例化就可以调用,类方法只能访问同一个类中的类属性和类方法
静态方法
静态方法是属于类的方法,但它既不依赖于类本身(即类属性和类方法),也不依赖于类的实例对象(即实例属性和实例方法)。在 Python 中,使用
@staticmethod
装饰器来定义静态方法,静态方法没有类似self
(代表实例对象)或cls
(代表类本身)这样的特殊第一个参数。既不需要访问实例属性或者调用实例方法也不需要访问类属性或者调用类方法这个时候,可以把这个方法封装成一个静态方法
6.1 基本语法
# 开发⼀款游戏
class Game(object):# 开始游戏,打印游戏功能菜单@staticmethod # 修饰符,声明为静态⽅法,⽆需传递self参数,直接使⽤类名即可,⽆需实例化对象def menu(): # 静态⽅法print('1、开始游戏')print('2、游戏暂停')print('3、退出游戏')
# 开始游戏、打印菜单
Game.menu() # 调⽤静态⽅法,⽆需实例化对象,直接使⽤类名即可
6.2 特点
- 独立性:静态方法不与类或实例的状态相关联,它不访问类属性、类方法,也不访问实例属性、实例方法。可以把它看作是一个定义在类内部的普通函数,只是为了代码组织的需要而放在类中。
- 调用灵活性:既可以通过类名直接调用,也可以通过类的实例对象调用。
6.3 综上所述
类里面的普通成员方法需要对象进行调用,不能直接使用类名调用。
用@staticmethod 修饰的方法为静态方法;
静态方法是独立存在的,不能访问类或者实例的任何属性和方法;
通过 类名.静态方法 调用静态方法 。