面向对象编程(OOP)基础
面向对象编程(Object-Oriented Programming,OOP)是一种强大的编程范式,它将数据和操作数据的方法封装为对象,通过类的继承和多态实现代码复用和系统扩展。
目录
1. 类与对象:基础结构
类是抽象的, 封装数据以及数据对象的操作,定义了对象的属性和方法。
对象是类的实例,是具体的, 有具体数据。
class Rectangle:
def __init__(self,long,wide):
# 实例属性
self.long = long
self.wide = wide
# 实例方法
def girth(self):
return (self.long + self.wide) * 2
def area(self):
return self.long * self.wide
def set_data(self,l = None,w = None):
self.long = l if l is not None else self.long
self.wide = w if w is not None else self.wide
# 创建对象
l1 = Rectangle(10,20)
# 访问属性和方法
print(l1.long,l1.wide,l1.girth())
print(l1.area())
l1.set_data(20)
print(l1.long,l1.wide,l1.girth())
print(l1.area())
2. 继承:创建层次结构
继承允许一个类继承另一个类的属性和方法,提高代码复用性。
# 父类
class Animal(object):
def __init__(self,age):
self.age = age
def __str__(self):
return "动物可以叫"
# 子类
class Dog(Animal):
def __init__(self, age,height):
super().__init__(age)
self.height = height
def __str__(self):
return "dog"
def speak(self):
print("汪汪")
# 子类
class Aqi(Dog):
def __init__(self, age, long, height):
super().__init__(age, height)
self.long = long
def __str__(self):
return "aqi"
def speak(self):
super().speak()
print("嗷嗷叫")
a1 = Animal(6)
print(a1.age,a1)
print("=======")
d1 = Dog(9,0.4)
# 使用继承
print(d1.age,d1.height)
print(d1)
d1.speak()
print("=======")
aq1 = Aqi(10,1.0,0.6)
print(aq1.age,aq1.height,aq1.long)
aq1,aq1.speak()
3、多继承:代码复用的 “双刃剑”
概念
多继承指一个子类可以同时继承多个父类,语法为 class 子类(父类1, 父类2, ...)
。它的优势是能让子类整合多个父类的属性和方法,快速复用不同来源的功能;但也因多个父类可能存在同名方法、属性,引发 “菱形继承” 等冲突问题,增加代码复杂度。
class Father:
def __init__(self,money):
self.money = money
def get_money(self):
print(self.money)
class Mother:
def __init__(self,color):
self.color = color
def get_color(self):
print(self.color)
class Son(Father,Mother):
def __init__(self, money,color):
# super().__init__(money)
Father.__init__(self,money)
Mother.__init__(self,color)
s1 = Son(100,"red")
s1.get_money()
s1.get_color()
4、MRO(方法解析顺序)
核心作用:
MRO(Method Resolution Order )即方法解析顺序,它定义了多继承子类在查找属性、方法时,遍历父类的顺序规则。Python 中通过 __mro__
属性或 mro()
方法可查看,其遵循 C3 算法,确保父类方法查找不重复、不冲突,解决多继承的 “方法调用歧义” 问题。
class B1:
pass
# def __str__(self):
# return "b1"
class B2:
pass
def __str__(self):
return "b2"
class B(B1,B2):
pass
# def __str__(self):
# return "b"
class C:
pass
def __str__(self):
return "c"
class A(B, C):
pass
# def __str__(self):
# return "A"
a = A()
print(a)
print(A.__bases__)
print(A.__mro__)
#解析循序为A->B->B1->B2->C->OBJECT
5. 多态:统一接口,不同实现
多态允许不同类的对象通过相同的接口进行调用,根据对象的实际类型执行不同的实现。简单来说,就是 "一个接口,多种实现"。
class Animal:
def speak(self):
raise NotImplementedError("子类必须重写speak")
# a1 = Animal()
# a1.speak()
class Dog(Animal):
def speak(self):
print("汪汪汪")
class Cat(Animal):
pass
def speak(self):
print("喵喵喵")
d1 = Dog()
d1.speak()
c1 = Cat()
c1.speak()
6、公有、私有与保护属性
1,公有属性与方法
在 Python 类中,默认情况下,定义的属性和方法都是公有的。这意味着在类的外部,可以直接通过对象访问这些属性和调用这些方法。
2,私有属性与方法
在 Python 中,通过在属性或方法名前添加双下划线(__)来定义私有属性和方法。私有属性和方法在类的外部是不能直接访问的,它们主要用于类的内部实现,对外部隐藏实现细节。
3, 保护属性
在属性或方法名前添加单下划线(_
)表示保护属性或方法。从约定俗成的角度来看,它暗示着该属性或方法是 “受保护的”,意味着子类可以访问,但不建议在类的外部直接访问。不过,在 Python 中,单下划线的属性和方法在语法上仍然是可以从外部访问的。
代码案例:
class Person:
def __init__(self, name, age, sex):
self.name = name # 公有属性:姓名
self.__age = age # 私有属性:年龄(外部无法直接访问)
self._sex = sex # 保护属性:性别(约定不直接访问)
def show(self):
return f"名字: {self.name} 年纪:{self.__age} 性别:{self._sex}"
def __str__(self):
return self.show()
def __get_age(self):
#私有方法:打印并返回对象的年龄
print(self.__age)
return self.__age
def pr(self):
#公有方法:通过内部调用私有方法获取年龄
self.__get_age()
# 测试Person类
# p1 = Person("dd",23,"男")
# print(p1.__age) # 报错:无法直接访问私有属性
# print(p1,p1._sex) # 可以访问保护属性
# print(p1.__get_age) # 报错:无法直接访问私有方法
# p1.pr() # 可以通过公有方法间接调用私有方法
class SuperPerson(Person):
def __init__(self, name, age, sex, skill):
super().__init__(name, age, sex) # 调用父类构造函数
self._skill = skill # 保护属性:特殊技能
def __str__(self):
return f"{self.show()} 技能:{self._skill}"
s1 = SuperPerson("aaa", 22, "保密", "喷火")
print(s1) # 调用重写的__str__方法
print(s1._skill) # 可以直接访问保护属性
print(s1._sex) # 可以直接访问父类的保护属性
7.类的成员
1、类属性:类与实例的 “共享与独立”
类属性是定义在类中、方法外的属性,它属于类本身,所有该类的实例共享这一属性。就像代码里 Person
类的 info = "两脚兽"
,它是类层面的公共数据
class Person:
info = "两脚兽" # 类属性
访问与修改:
类访问:直接用 类名.属性名
,如 Person.info
,获取的是类属性原始值,修改也会直接改变类属性(所有实例后续访问会受影响 )。
实例访问:实例默认会先找自己的实例属性,若没有,就会去类属性里找。像 p1.info
,初始时会拿到 Person.info
的值 。但如果对实例的该属性赋值(如 p1.info = "666"
),就会在实例上创建独立的实例属性,后续实例访问的是自己的,不再关联类属性。
p1 = Person("aaaa")
print(Person.info) # 输出: 两脚兽,类直接访问类属性
print(p1.info) # 输出: 两脚兽,实例访问类属性(未创建实例属性时)
p1.info = "666" # 给实例p1创建独立的实例属性info
print(p1.info) # 输出: 666,访问实例自己的属性
p2 = Person("李四")
print(p2.info) # 输出: 两脚兽,p2未创建实例属性,访问类属性
2、类方法:操作类本身的工具
@classmethod
装饰器
类方法用 @classmethod
装饰,第一个参数是 cls
(代表类本身 ),而非实例方法的 self
(代表实例 )。它主要用于操作类相关的逻辑,比如访问类属性、创建类的实例等。
@classmethod
def get_class(cls):
return cls # 返回类本身
@classmethod
def get_class_name(cls):
print(cls.info) # 访问类属性
print(cls.__bases__) # 查看类的父类元组
print(cls.__mro__) # 查看方法解析顺序
return cls.__name__ # 返回类名
3、静态方法:独立功能
静态方法特点
Util
类里的方法用 @staticmethod
装饰,它不需要 self
或 cls
参数,就是单纯的函数,逻辑上属于该类的工具功能,但不依赖类或实例的状态。
class Util:
@staticmethod
def max(a, b): # 静态方法,实现两数比较取大
return a if a > b else b
@staticmethod
def sum(a, b): # 静态方法,实现两数求和
return a + b
8、动态添加类成员
1、动态添加实例属性与方法
1. 动态添加实例属性
在 Python 中,我们可以随时为对象添加新的属性,即使类定义中并未声明。这种特性使得对象的状态可以根据运行时需求灵活调整。
class Person:
"""空的Person类"""
p1 = Person()
p2 = Person()
# 为p1动态添加name属性
p1.name = "小明"
print(p1.name) # 输出: 小明
# p2没有name属性,以下代码会报错
# print(p2.name)
2. 动态添加实例方法
动态添加方法相对复杂一些,有两种主要方式:
方式一:直接绑定函数(需手动传递 self)
这种方式简单直接,但调用时需要手动传递实例对象作为第一个参数。
def get_name0(self):
return self.name
def set_name0(self, name):
self.name = name
return self.name
# 直接将函数绑定到对象
p1.get_name = get_name0
p1.set_name = set_name0
# 调用时需手动传递self参数
print(p1.get_name(p1)) # 输出: 小明
print(p1.set_name(p1, "小红")) # 输出: 小红
方式二:使用 types.MethodType(自动传递 self)
这是更优雅的方式,使用types.MethodType
将函数绑定到实例,调用时会自动传递实例对象。
import types
# 将函数转换为绑定方法
p1.get_name = types.MethodType(get_name0, p1)
p1.set_name = types.MethodType(set_name0, p1)
# 正常调用,无需手动传递self
print(p1.get_name()) # 输出: 小红
print(p1.set_name("小李")) # 输出: 小李
2、动态添加类属性与方法
1. 动态添加类属性
类属性属于类本身,可以被所有实例共享。动态添加类属性后,所有实例都能访问到这个新属性。
# 动态添加类属性
Person.info = "两脚兽"
# 类和实例都能访问类属性
print(Person.info) # 输出: 两脚兽
print(p1.info) # 输出: 两脚兽
print(p2.info) # 输出: 两脚兽
# 注意:如果实例有同名属性,会优先访问实例属性
p1.info = "特殊人类"
print(p1.info) # 输出: 特殊人类(实例属性)
print(p2.info) # 输出: 两脚兽(类属性)
2. 动态添加类方法
类方法通过@classmethod
装饰器定义,第一个参数是类本身(通常用cls
表示)。动态添加类方法时,直接将函数赋值给类即可。
@classmethod
def get_class0(cls):
return cls
@classmethod
def get_bases0(cls):
return cls.__bases__
@classmethod
def get_doc0(cls):
return cls.__doc__
# 动态添加类方法
Person.get_class = get_class0
Person.get_bases = get_bases0
Person.get_doc = get_doc0
# 调用类方法
print(Person.get_class() == Person) # 输出: True
print(Person.get_bases()) # 输出: (<class 'object'>,)
print(Person.get_doc()) # 输出: 空的Person类
3. 动态添加静态方法
静态方法通过@staticmethod
装饰器定义,不依赖于类或实例。动态添加静态方法的方式与类方法类似。
@staticmethod
def say_hello():
return "Hello, World!"
@staticmethod
def add(a, b):
return a + b
# 动态添加静态方法
Person.say_hello = say_hello
Person.add = add
# 调用静态方法
print(Person.say_hello()) # 输出: Hello, World!
print(Person.add(3, 5)) # 输出: 8
# 也可以通过实例调用静态方法
print(p1.say_hello()) # 输出: Hello, World!
9.Python 中的属性操作
hasattr:判断对象是否有指定属性
hasattr 函数的作用是判断一个对象是否拥有指定名称的属性。它的语法非常简单:hasattr(object, name)。其中,object 是要检查的对象,name 是属性的名称,需要以字符串的形式传入。
当对象拥有该属性时,函数返回 True;反之,则返回 False。举个例子,我们定义一个简单的类:
class Student:
def __init__(self, name):
self.name = name
s = Student("小明")
print(hasattr(s, "name")) # 输出 True,因为 s 有 name 属性
print(hasattr(s, "age")) # 输出 False,因为 s 没有 age 属性
setattr:为对象设置属性
setattr 函数用于为对象设置属性。如果该属性不存在,就会新建这个属性;如果属性已存在,就会修改属性的值。它的语法是:setattr(object, name, value)。这里的 object 是要设置属性的对象,name 是属性名(字符串形式),value 是要设置的属性值。
还是用上面的 Student 类举例:
s = Student("小明")
setattr(s, "age", 18) # 为 s 设置 age 属性,值为 18
print(s.age) # 输出 18
setattr(s, "name", "小红") # 修改 name 属性的值
print(s.name) # 输出 小红
getattr:获取对象的属性值
getattr 函数的功能是获取对象指定属性的值。它的语法为:getattr(object, name[, default])。object 是目标对象,name 是属性名(字符串),default 是可选参数,当属性不存在时,会返回这个默认值;如果没有设置默认值且属性不存在,就会抛出 AttributeError 异常。
例如:
s = Student("小明")
setattr(s, "age", 18)
print(getattr(s, "name")) # 输出 小明
print(getattr(s, "age")) # 输出 18
print(getattr(s, "gender", "未知")) # 输出 未知,因为 gender 属性不存在且设置了默认值
delattr:删除对象的属性
delattr 函数用于删除对象的指定属性,其语法为:delattr(object, name)。object 是要操作的对象,name 是要删除的属性名(字符串)。如果属性不存在,会抛出 AttributeError 异常。
示例如下:
s = Student("小明")
setattr(s, "age", 18)
delattr(s, "age") # 删除 age 属性
print(hasattr(s, "age")) # 输出 False,说明 age 属性已被删除
10.property:属性封装
1. 使用装饰器语法
class Person:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
这种方式更加简洁,是 Python 中推荐的写法。@property
装饰器将方法转换为只读属性,而 @<property>.setter
装饰器则为该属性添加了 setter 方法。
2. 使用 property 类
对于 age
和 sex
属性,我们使用了 property
类的构造函数:
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age
def __get_age(self):
return self.__age
def __set_age(self, value):
self.__age = value
age = property(__get_age, __set_age)
3.property 的优势
-
接口一致性:保持了属性访问的简洁性,与直接访问普通属性的语法完全一致,无需调用方法。
-
代码可维护性:将属性的访问逻辑封装在类内部,当需求变化时,只需修改类的实现,而不会影响外部调用代码。