Python 面向对象核心概念梳理

Python 面向对象核心概念梳理

Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。

如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。

接下来我们先来简单的了解下面向对象的一些基本特征。

一、面向对象技术简介

  • 类(Class) : 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 类变量: 类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员: 类变量或者实例变量, 用于处理类及其实例对象的相关的数据。
  • 方法重写: 如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量: 定义在方法中的变量,只作用于当前实例的类。
  • 实例变量: 在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
  • 继承: 即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化: 创建一个类的实例,类的具体对象。
  • 方法: 类中定义的函数。
  • 对象: 通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

二、创建类与对象

类是面向对象程序设计的基础,可以定义指定类的对象。类中可以定义属性(特性)和方法(行为)。

1、定义类

在 Python 中,通过 class 关键字来定义类。定义类的语法格式如下:

calss 类名:
    成员变量
    成员函数

例如,定义了一个 Person 类:

# 定义 Person 类

class Person:
    name = 'brenden'        # 定义了一个属性
    def printName(self):    # 定义了一个方法
        print(self.name)

2、对象的创建和使用

在 Python 中,用赋值的方式创建类的实例,一般格式为:

对象名 = 类名(参数列表)

创建对象后,可以使用 “.” 运算符,通过实例对象来访问这个类的属性和方法(函数),一般格式为:

对象名.属性名
对象名.函数名()

例如,语句 “p = Person()” 产生了一个 Person 的实例对象,此时可以用 p.name 来调用类的 name 属性。

# 类和对象应用示例

class CC:
    x = 20              # 定义属性
    y = 20              # 定义属性
    z = 30              # 定义属性
    def show(self):     # 定义方法
        print((self.x + self.y + self.z) / 3)

b = CC()                # 创建实例对象 b
b.x = 30                # 调用属性
b.show()                # 调用方法 show

# 输出:
# 26.666666666666668

3、类属性和实例属性

- 类属性

类属性为所有类对象的实例对象所共有。类属性通常在类体中初始化。对于公有的类属性,在类外可以通过类对象和实例对象访问。

定义 Person 类,定义类属性和方法:

# 定义 Person 类,定义类属性和方法

class Person:
    name = 'brenden'    # 公有的类属性
    __age = 18          # 私有的类属性
p = Person()
print("p.name")         # 正确,但不提倡
print(Person.name)      # 正确
print(p.__age)          # 错误,不能在类外通过实例对象访问私有的类属性
print(Person.__age)     # 错误,不能在类外通过类对象访问私有的类属性

# 输出:
# p.name
# brenden
# Traceback (most recent call last):
#   File "c:\Gitee\python\04_Day\03_oop.py", line 9, in <module>
#     print(p.__age)          # 错误,不能在类外通过实例对象访问私有的类属性
# AttributeError: 'Person' object has no attribute '__age'
- 实例属性

实例属性不需要在类中显式定义,而应在 _ _init _ _ 构造函数中定义,定义时以 " self. " 作为前缀,实例属性属于特定的实例。

在其他方法中也可以随意添加新的实例属性,但并不提倡这么做,所有的实例属性最好在 _ _ init _ _ 中给出。实例属性在内部通过 “ self. ” 访问,在外部通过对象实例访问。

定义 Student 类,定义实例属性和方法

# 定义 Student 类,定义实例属性和方法

class Studen:
    def __init__(self,name,age,grade):
        self.name = name
        self.age = age
        self.grade = grade
    def say_hi(self):
        print('I am a student,my name is',self.name)

s1 = Studen('二二',21,3)
s1.say_hi()
print(s1.grade)

s2 = Studen('三三',19,1)
s2.say_hi()
print(s2.grade)


# 输出:
# I am a student,my name is 二二
# 3
# I am a student,my name is 三三
# 1

上述例子中,Student 类中定义了实例属性 name、age 和 grade。s1、s2 是 Student 的两个实例,这两个实例分别拥有自己的属性值,有别于其他对象。

实例的属性可以像普通变量一样进行操作,例如:

s1.grade = s1.grade + 1

4、类的方法

方法是与类相关的函数。方法和函数不同,函数是封装操作的小程序。方法是定义在类内部的函数,并且定义方法与普通函数有所不同。

类的方法主要三种类型:实例方法、类方法和静态方法,不同的方法有不同的使用场景和声明调用形式,不同的方法也具有不同的访问限制。

- 方法的声明和调用

在类的内部,使用 def 关键字可以为类定义一个方法。与一般函数定义不同,类方法必须包含对象本身的参数,通常为 self ,且为第一个参数。

声明方法的语法格式如下:

def 方法名(self,[形参列表]):
    函数体

方法调用的语法格式如下:

对象.方法名([参数列表])

在调用方法时,第一个参数 self 不需要用户为其赋值,Python 自动把对象实例传递给参数 self。

- 实例方法

实例方法是在类中经常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第一个参数,一般以 “self” 作为这第一个参数。

在类外,实例方法只能通过实例对象去调用。实例方法的声明格式如下:

def 方法名(self,[形参列表]):
    函数体

实例方法通过实例对象调用:

对象.方法名([参数列表])

定义 Person 类,通过实例调用实例方法

# 但行好事,莫问前程

class Person:
    place = 'Changsha'
    def getPlace(self):         # 实例方法,至少有个 self 参数
        return self.place

p = Person()                    # 创建实例对象 p
print(p.getPlace())             # 正确,可以通过实例对象引用
print(p.place)                  # 通过实例对象 p 调用对象属性

# 输出:
# Changsha
# Changsha
- 类方法

类方法不对特定实例进行操作,在类方法中访问实例属性会导致错误。类方法需要用修饰器 “@ classmethod” 来标识其为类方法。对于类方法,第一个参数必须是类对象,一般以 “cls” 作为这第一个参数,类方法可通过实例对象和类对象去访问。

类方法的声明格式如下:

@ classmethod
def 类方法名(cls,[形参列表]):
    函数体

类方法调用格式如下:

类名.类方法([实参列表])

通过实例对象和类对象去访问类方法。

创建 Person 类,定义并调用类方法

# 创建 Person 类,定义并调用类方法

class Person:
    place = 'Changsha'
    @ classmethod               # 类方法,以 @ classmethod 来进行修饰
    def getPlace(cls):
        return cls.place

p = Person()
print(p.getPlace())             # 可以用过实例对象引用
print(Person.getPlace())        # 可以通过类对象引用

# 输出:
# Changsha
# Changsha

类方法还有一个用处就是可以对类属性进行修改。

# 创建 Person 类,定义并修改类属性

class Person:
    place = 'Changsha'
    @ classmethod               # 标识其为类方法
    def getPlace(cls):
        return cls.place
    @ classmethod
    def setPlace(cls,place1):
        cls.place = place1

p = Person()
p.setPlace('Shanghai')          # 修改类属性
print(p.getPlace())
print(Person.getPlace())

# 输出:
# Shanghai
# Shanghai
- 静态方法

静态方法需要通过修饰器 “@ staticmethod” 来进行修饰,静态方法不需要多定义参数。

创建 Person 类,定义并调用静态方法

# 创建 Person 类,定义并调用静态方法

class Person:
    place = 'Changsha'
    @ staticmethod
    def getPlace():
        return Person.place
print(Person.getPlace())

# 输出:
# Changsha

5、构造方法和析构方法

类中最常见的内置方法就是构造方法和析构方法。

- 构造方法

构造方法 _ _ init _ _(self, ··· ···) 在生成对象是调用,可以用来进行一些属性初始化操作,不需要显式去调用,系统会默认执行。构造方法支持重载,如果用户自己没有重新定义构造方法,系统就自动执行默认的构造方法。

构造方法使用示例:

# 构造方法使用示例

class Person:
    def __init__(self,name):
        self.PersonName = name
    def sayHi(self):
        print("大家好,我是{}。".format(self.PersonName))

p = Person('二二')
p.sayHi()

# 输出:
# 大家好,我是二二。

image-20251212201907223

- 析构方法

析构方法 _ _ def_ _(self) 在释放对象时调用,支持重载,可以在其中进行一些释放资源的操作,不需要显式调用。

下面的示例说明了类的普通成员函数以及构造方法的和析构方法的作用,析构方法示例:

# 析构方法的示例

class Test:
    def __init__(self):
        print('构造方法执行')
    def __del__(self):
        print('析构方法执行')
    def myf(self):              # 第一个参数必须是self(代表当前实例)
        print('调用自定义方法')

# 1. 实例化Test类,创建对象赋值给obj
obj = Test()
# 2. 通过实例调用myf方法
obj.myf()
# 3. 重赋值obj为None(模拟原代码的赋值操作)
obj = None
# 4. 删除obj变量
del obj

# 输出:
# 构造方法执行
# 调用自定义方法
# 析构方法执行

很多新手会误以为del obj一定会立刻触发__del__,但其实这里有个关键:del是删除变量的引用,而不是直接销毁对象。只有当对象的引用计数为 0 时,垃圾回收器才会销毁对象并调用__del__

6、属性和方法的访问控制

- 属性的访问控制

在 Python 中没有像 C++ 中 public 和 private 这些关键字来区别公有属性和私有属性,它是以属性命名方式来区分,如果在属性名前面加了两个下划线 “_ _” ,则表明该属性是私有属性,否则为公有属性。方法也一样,如果在方法名前面加了下划线,则表示方法是私有的,否则为公用的。

创建 Person 类,调用其公有属性

# 创建 Person 类,调用其公有属性

class Person:
    name = '二二'
    age = 21
p = Person()
print(p.name,p.age)

# 输出:
# 二二 21

这里的 name 和 age 都是公有的,可以直接在类外通过对象名访问,如果想定义成私有的,则需要在前面加两个下划线 “_ _”。

- 方法的访问控制

在类中可以根据需要定义一些方法,定义方法采用 def 关键字,在类中定义的方法至少会有一个参数,一般以名为 “self” 的变量作为该参数(用其他名称也可以),而且需要作为第一个参数,下面看一个例子:

# 方法的访问控制使用示例

class Person:
    __name = '二二'
    __age = 16
    def getName(self):
        return self.__name
    def getAge(self):
        return self.__age

p = Person()
print(p.getName(),p.getAge())    

# 输出:
# 二二 16

_ _ name 和 _ _ age 是类的私有属性,getName 和 getAge 是类的方法。紧接着定义了实例对象 p 。如果想获得姓名、年龄的信息,不能使用 p._ _ name 和 p._ _ age 直接获取,必须要使用 p.getName() 和 p.getAge() 才能获取。

三、面向对象的三大特性

1. 封装(Encapsulation)

核心思想:隐藏对象的内部细节,仅通过公开方法对外提供访问接口,确保数据安全。
Python 通过 “命名约定” 实现封装:

  • 公开属性 / 方法:默认命名(如namesay_hello),可直接访问。
  • 私有属性 / 方法:以双下划线__开头(如__secret),只能在类内部访问。

示例:私有属性的封装

class BankAccount:
    def __init__(self, account_id, balance):
        self.account_id = account_id  # 公开属性
        self.__balance = balance      # 私有属性(余额不允许直接修改)

    # 公开方法:提供安全的余额访问
    def get_balance(self):
        return self.__balance

    # 公开方法:提供安全的存款逻辑
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(f"存款成功,当前余额:{self.__balance}")
        else:
            print("存款金额必须为正数")

# 使用
account = BankAccount("622202XXXX", 1000)
print(account.account_id)  # 输出:622202XXXX(允许访问公开属性)

# 尝试直接访问私有属性(会报错)
# print(account.__balance)  # AttributeError: 'BankAccount' object has no attribute '__balance'

# 通过公开方法访问和修改
print(account.get_balance())  # 输出:1000
account.deposit(500)          # 输出:存款成功,当前余额:1500

注:Python 的私有属性并非绝对不可访问(可通过_类名__属性名间接访问,如account._BankAccount__balance),但这是不推荐的 “黑魔法”,违背封装原则。

2. 继承(Inheritance)

核心思想:子类(派生类)继承父类(基类)的属性和方法,并可扩展新功能或重写父类方法,实现代码复用。

(1)单继承

一个子类只能继承一个父类(Python 也支持多继承)。

# 父类:动物
class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name}在吃东西")

# 子类:狗(继承Animal)
class Dog(Animal):  # 通过括号指定父类
    # 重写父类方法(Override)
    def eat(self):
        print(f"{self.name}在啃骨头")  # 扩展父类的实现

    # 子类新增方法
    def bark(self):
        print(f"{self.name}在汪汪叫")

# 使用
dog = Dog("旺财")
dog.eat()   # 输出:旺财在啃骨头(调用重写后的方法)
dog.bark()  # 输出:旺财在汪汪叫(调用子类新增方法)
(2)调用父类方法

通过super()函数在子类中调用父类的方法(尤其是构造方法)。

class Parent:
    def __init__(self, a):
        self.a = a

class Child(Parent):
    def __init__(self, a, b):
        # 调用父类的__init__方法初始化a
        super().__init__(a)  # 等价于 Parent.__init__(self, a)
        self.b = b  # 子类新增属性

child = Child(10, 20)
print(child.a, child.b)  # 输出:10 20
(3)多继承

Python 支持一个子类继承多个父类,语法为class 子类(父类1, 父类2, ...)
但需注意方法解析顺序(MRO):当多个父类有同名方法时,按子类.__mro__的顺序查找。

class A:
    def say(self):
        print("A的say方法")

class B:
    def say(self):
        print("B的say方法")

class C(A, B):  # 继承A和B
    pass

c = C()
c.say()  # 输出:A的say方法(按MRO顺序,A在B前)
print(C.__mro__)  # 输出:(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
3. 多态(Polymorphism)

核心思想:不同子类对同一方法有不同实现,通过父类引用调用时,表现出不同行为。
Python 的多态基于 “动态类型”(无需显式声明类型),更加灵活。

# 父类:形状
class Shape:
    def draw(self):
        pass  # 父类方法,子类需重写

# 子类:圆形
class Circle(Shape):
    def draw(self):
        print("画圆形")

# 子类:矩形
class Rectangle(Shape):
    def draw(self):
        print("画矩形")

# 统一接口:接收Shape类型(或其子类)
def draw_shape(shape):
    shape.draw()  # 调用时自动匹配子类的实现

# 多态体现:同一接口,不同实现
draw_shape(Circle())    # 输出:画圆形
draw_shape(Rectangle()) # 输出:画矩形

写在最后

刚开始学面向对象时,总觉得“绕”——比如self到底是啥,封装要不要严格用双下划线。后来发现不用死扣概念,多写实际代码更重要:

  1. 先从简单的类开始,比如定义Student类,包含姓名、学号、选课方法,自己实例化几个对象试试;
  2. 遇到重复代码时,想想能不能用继承;
  3. 担心属性被乱改时,试试用封装(单下划线+接口方法);
  4. 不确定方法类型时,先写实例方法,后续再根据需求改类方法/静态方法。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值