目录
2.2 类的定义(具有相同或相似属性(数据)和动作(方法)的一组实体集合)
2.5 构造方法 __init__ 和析构方法 __del__
1. 面向对象与面向过程的对比
面向过程:以过程(函数和对象)为中心,关注任务的实现步骤和流程,代码按照功能分成多个函数,数据和函数分开处理。通过调用函数按顺序完成任务。
面向对象:以对象为中心,关注数据和操作数据的方法,数据和操作数据的方法封装在类和对象中,通过类和继承实现代码复用和扩展。
方面 |
面向过程 |
面向对象 |
核心思想 |
以过程(函数)为中心 |
以对象(数据+方法)为中心 |
代码结构 |
函数和数据分离 |
数据和方法封装在类和对象中 |
复用方式 |
通过函数调用复用 |
通过封装、继承、多态复用 |
维护性 |
逻辑复杂时维护困难 |
封装提高维护性和扩展性 |
数据安全 |
数据易被外部访问 |
封装保护数据,隐藏内部实现 |
适用场景 |
简单任务、脚本 |
复杂系统、大型项目 |
1.1 面向对象和面向过程
面向过程(过程式编程):强调一步步执行逻辑,把程序看作是函数和数据的组合。
举例:
# 过程式写法:计算矩形面积
def area(length, width):
return length * width
print(area(5, 3)) # 输出 15
面向对象编程: 强调把现实事物抽象为“对象”,对象包含属性和方法。
举例:
# 面向对象写法:矩形对象
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
rect = Rectangle(5, 3)
print(rect.area()) # 输出 15
1.2 面向对象的优势
- 代码复用:通过继承和组合,避免重复造轮子。
- 易维护:对象之间相对独立,修改某一部分不会影响整体。
- 贴近现实:用“类”和“对象”来模拟真实世界,更符合人的思维方式。
2. 面向对象的基本概念
2.1 对象(Object)和 类(Class)
- 类(Class):蓝图或模板。
- 对象(Object):类的实例,具体存在。
2.2 类的定义(具有相同或相似属性(数据)和动作(方法)的一组实体集合)
使用class关键字来定义类,后面跟着的括号表示继承的类,默认继承object类,默认情况下也可以不写。类的名字推荐使用大驼峰法命名以区别函数。
class Person(object):
pass
"""
或者这样定义也是可以的
class Person:
pass
"""
2.3 类的实例化(创建对象)
使用类型名后面跟括号的方式实例化对象(与函数的类似),创建的对象实际是该对象在内存中的引用地址。
对象变量 = 类名()
p = Person()
print(p) #输出的为p的地址
类每次实例化的对象都是一个新对象,他们的地址都不同,可以使用id(),或print()查看
class Person(object):
pass
for i in range(3):
p=Person()
print(p)
"""
输出:
<__main__.Person object at 0x0000018F15FDB590>
<__main__.Person object at 0x0000018F15FDB650>
<__main__.Person object at 0x0000018F15FDB510>
"""
2.4 类中的self关键字
self
代表实例本身。- 实例方法的第一个参数必须是
self
(Python 自动传入)。
class Person:
def __init__(self, name, age):
self.name = name # 给实例绑定属性 name
self.age = age # 给实例绑定属性 age
def introduce(self):
# 通过 self 访问实例属性
print(f"大家好,我叫{self.name},今年{self.age}岁。")
# 创建实例
p = Person("小明", 20)
p.introduce() # 调用方法时,Python 自动把 p 传给 self
2.5 构造方法 __init__ 和析构方法 __del__
构造方法 __init__
是 Python 中类的一个特殊方法,用于在创建对象时初始化对象的属性。它会在实例被创建后自动调用,通常用来给对象设置初始状态。
__init__
只在对象创建时调用一次,之后该对象存在期间不会再自动调用__init__
。- 访问对象的属性也不会重新执行初始化逻辑。
析构方法 __del__
也是一个特殊方法,当对象即将被销毁时(比如没有引用指向该对象时),Python 会调用它。通常用于资源释放或清理工作,但在 Python 中不建议过多依赖 __del__
,因为垃圾回收的时机不确定。
3. 属性和方法
属性:存储对象的状态或特征(存数据)
方法:定义对象的行为或功能(功能)
3.1 实例属性 and 类属性
1、实例属性(Instance Attributes)
- 属于每个对象(实例)自己的属性。
- 在对象创建后,可以通过
self
绑定到实例上。 - 不同实例的实例属性互相独立,修改一个实例的属性不会影响其他实例。
- 通常在
__init__
方法中定义。 - 实例属性总结:属于对象本身,互不影响,用于存储对象的独有数据。
代码示例:
class Books:
def __init__(self, book_name):
self.book_name = book_name # 实例属性
b1 = Books("Python入门")
b2 = Books("数据结构")
print(b1.book_name) # 输出:Python入门
print(b2.book_name) # 输出:数据结构
说明:
b1
和b2
是两个不同的实例,它们各自拥有独立的book_name
。- 修改一个实例的属性不会影响另一个实例。
2、类属性(Class Attributes)
- 属于类本身的属性,由所有实例共享。
- 直接定义在类体内,不在任何方法中。
- 所有实例访问的都是同一个类属性,修改类属性会影响所有实例(除非实例有同名的实例属性覆盖它)。
- 类属性总结:属于类,所有实例共享,用于存储类的公共信息。每次实例化对象不会重置,除非显式的修改。
代码示例:实例属性会覆盖同名的类属性。
class Books:
# 类属性:属于类本身,所有实例共享
book_start_id = 1000
def __init__(self, book_name):
# 实例属性:属于具体对象,每个实例独立
self.book_name = book_name
@classmethod
def add_id(cls):
# 通过类方法访问和修改类属性
cls.book_start_id += 1
return f"BK{cls.book_start_id}"
# 演示
print(Books.book_start_id) # 输出:1000,类属性初始值
b1 = Books("Python入门")
print(b1.book_name) # 实例属性,输出:Python入门
print(Books.add_id()) # 调用类方法,输出:BK1001
print(Books.book_start_id) # 类属性被修改,输出:1001
b2 = Books("数据结构")
print(b2.book_name) # 实例属性,输出:数据结构
print(b2.add_id()) # 通过实例调用类方法,输出:BK1002
print(Books.book_start_id) # 类属性继续增加,输出:1002
说明:
book_start_id
是类属性,它属于Books
类本身,所有实例共享同一个变量。book_name
是实例属性,它属于具体的对象,每个对象有自己独立的值。add_id
是类方法,通过它可以访问和修改类属性book_start_id
- 无论通过类还是实例调用
add_id
,修改的是同一个类属性,不会重置。
3.2 属性的访问与修改
直接访问属性
- 可以通过
obj.attr
直接访问实例或类的属性。 - 访问时,Python 会先查找实例属性,再查找类属性。
动态访问和修改属性(内置函数)
Python 提供了三个内置函数,方便动态地访问和操作对象属性:
函数 |
用途 |
说明 |
|
获取对象指定名称的属性值 |
,如果属性不存在返回 或抛异常 |
|
设置对象指定名称的属性值 |
,不存在则创建 |
|
判断对象是否有指定名称的属性 |
返回布尔值 |
示例代码:
class Car:
wheels = 4 # 类属性
def __init__(self, color):
self.color = color # 实例属性
car = Car("红色")
# 直接访问
print(car.color) # 红色
print(car.wheels) # 4
# 使用 getattr 访问
print(getattr(car, "color")) # 红色
print(getattr(car, "wheels")) # 4
print(getattr(car, "speed", 100)) # 100,属性不存在返回默认值
# 使用 setattr 修改或添加属性
setattr(car, "color", "蓝色") # 修改实例属性
setattr(car, "speed", 120) # 新增实例属性
print(car.color) # 蓝色
print(car.speed) # 120
# 使用 hasattr 判断属性是否存在
print(hasattr(car, "speed")) # True
print(hasattr(car, "brand")) # False
3.3 方法分类
方法类型 |
第一个参数 |
调用方式 |
作用范围 |
实例方法 |
self |
实例调用 |
操作实例属性 |
类方法 |
cls |
类或实例调用 |
操作类属性 |
静态方法 |
无 |
类或实例调用 |
工具函数,无状态 |
3.3.1 实例方法
- 定义:最常见的方法,第一个参数是
self
,代表实例对象本身。 - 作用:操作实例属性,访问或修改当前对象的状态。
- 调用:只能通过实例对象调用。
示例:
class Books:
def __init__(self, book_name):
self.book_name = book_name # 实例属性
def show_name(self): # 实例方法
print(f"书名是:{self.book_name}")
b = Books("Python入门")
b.show_name() # 输出:书名是:Python入门
3.3.2 类方法
- 定义:使用装饰器
@classmethod
,第一个参数是cls
,代表类本身。 - 作用:操作类属性,访问或修改类级别的状态,通常用于工厂方法或者管理类属性。
- 调用:可以通过类名或实例调用。
class Books:
book_count = 0 # 类属性
def __init__(self, book_name):
self.book_name = book_name
Books.book_count += 1
@classmethod
def get_book_count(cls):
return cls.book_count
print(Books.get_book_count()) # 输出:0
b1 = Books("Python入门")
b2 = Books("数据结构")
print(Books.get_book_count()) # 输出:2
print(b1.get_book_count()) # 也可以通过实例调用,输出:2
3.3.3 静态方法
解释:在开发时,如果需要在类中封装一个方法,这个方法:既不需要访问实例属性或者调用实例方法,也不需要访问类属性或者调用类方法。这个时候,可以把这个方法封装成静态方法。
- 定义:使用装饰器
@staticmethod
,不需要默认传入self
或cls
参数。 - 作用:类似普通函数,但定义在类中,通常用于一些与类或实例无关的工具函数。
- 调用:可以通过类名或实例调用。
注意事项:
- 静态方法不能直接访问类属性或实例属性,因为没有
cls
或self
参数。 - 静态方法可以通过类名显式访问类属性。
- 静态方法访问实例属性必须通过传入实例对象。
示例:
class Books:
@staticmethod
def is_valid_name(name):
return isinstance(name, str) and len(name) > 0
print(Books.is_valid_name("Python")) # 输出:True
print(Books.is_valid_name("")) # 输出:False
4. 面向对象的三大特性
4.1 封装(Encapsulation)
定义:将对象的属性(数据)和方法(操作)绑定在一起,并隐藏对象的内部细节,只暴露必要的接口给外部使用,从而保护对象的状态不被外部随意修改。
作用:
- 保护数据,防止外部代码直接访问或修改对象的内部状态,保证数据的完整性和安全性。
- 隐藏实现细节,只暴露接口,简化使用,提高代码的灵活性和可维护性。
- 提高代码复用性和模块化。
示例:
class Person:
def __init__(self, name, age):
self.name = name # 公有属性
self._age = age # 受保护属性(约定)
self.__salary = 5000 # 私有属性
def get_salary(self): # 公有方法,访问私有属性
return self.__salary
def set_salary(self, value): # 公有方法,修改私有属性
if value > 0:
self.__salary = value
else:
print("工资必须大于0")
p = Person("小明", 30)
print(p.name) # 可以访问公有属性
print(p._age) # 可以访问受保护属性(不建议)
# print(p.__salary) # 报错,无法访问私有属性
print(p.get_salary()) # 通过公有方法访问私有属性
p.set_salary(6000)
print(p.get_salary())
注意事项:对于私有属性,如果在外部直接对私有属性进行修改,实际上是创建了一个成员属性,名字和私有属性一样而已,这是因为在Python中,私有属性实际上是通过名称改写(Name Mangling)变成了 _类名__属性名
的形式。
示例:
class Person:
def __init__(self, name, age):
self.name = name # 公有属性
self._age = age # 受保护属性(约定)
self.__salary = 5000 # 私有属性
def get_salary(self): # 公有方法,访问私有属性
return self.__salary
def set_salary(self, value): # 公有方法,修改私有属性
if value > 0:
self.__salary = value
else:
print("工资必须大于0")
p = Person("小明", 30)
print(p.name) # 可以访问公有属性
print(p._age) # 可以访问受保护属性(不建议)
# print(p.__salary) # 报错,无法访问私有属性
print(p.get_salary()) # 通过公有方法访问私有属性
p.__salary = 5000 #在外部直接对私有属性进行修改,实际是是创建了一个成员属性(错误示例)
print(p.__salary)
p.set_salary(6000)
print(p.get_salary())
4.2 继承(Inheritance)
4.2.1 继承的定义和作用
定义:子类可以继承父类所有的非私有属性和非私有方法,实现代码重用。
作用:
- 避免重复代码,提高代码复用性。
- 让程序结构更清晰,便于维护和扩展。
使用:class 子类名称(父类名称),括号内的为子类将要继承的父类
4.2.2 继承的类型
单继承:子类继承一个父类。
示例:
class Animal:
def eat(self):
print("吃东西")
class Dog(Animal):
def bark(self):
print("汪汪叫")
dog = Dog()
dog.eat() # 继承自Animal
dog.bark() # 子类自己的方法
多继承:子类继承父类多个相关的属性和方法
- 多重继承
- 多层继承
class Flyable:
def fly(self):
print("会飞")
class Swimmable:
def swim(self):
print("会游泳")
class Duck(Flyable, Swimmable):
pass
d = Duck()
d.fly()
d.swim()
class Animal:
def eat(self):
print("吃东西")
class Mammal(Animal):
def walk(self):
print("走路")
class Dog(Mammal):
def bark(self):
print("汪汪叫")
d = Dog()
d.eat() # 来自Animal
d.walk() # 来自Mammal
d.bark() # 自己的方法
多继承中的方法执行顺序
- MRO 决定了多继承时,方法的调用顺序。
4.3 多态(Polymorphism)
定义:同一个接口可以有不同的实现方式,在运行时根据对象类型调用相应的方法。其本质是不同对象调用相同方法产生不同结果。