Python 笔记 — 面向对象基础

目录

一、面向过程和面向对象

面向过程(Procedural Programming)和面向对象(Object-Oriented Programming,简称 OOP)是两种不同的编程范式,它们在程序设计的思想、结构和方法上有所区别。

1、面向过程(Procedural Programming)

面向过程编程是一种以过程(或函数)为中心的编程范式,它将程序划分为一系列的函数,每个函数执行特定的任务。在面向过程的程序中,数据和函数是分开的,数据在全局范围内存在,函数对数据进行处理。面向过程编程通常用于解决简单、直接的问题,强调如何执行操作。

对于面向过程的思想:需要实现一个功能的时候,看重的是开发的步骤和过程,每一个步骤都需要自己亲力亲为,需要自己编写代码(自己来做)。

特点:

  • 以过程(函数)为基本组织单位。

  • 数据和函数分离,函数对数据进行操作。

  • 强调算法和执行步骤。

  • 可能导致代码的重用性较低,维护困难。

    面向过程方式

    def calculate_rectangle_area(width, height):
    return width * height

    width = 5
    height = 10
    area = calculate_rectangle_area(width, height)
    print(f"面积为:{area}")

2、面向对象(Object-Oriented Programming,OOP)

面向对象编程是一种以对象为中心的编程范式,它将数据和方法组合成对象,对象可以互相交互和通信。在面向对象的程序中,数据和方法被封装在一起,对象之间通过消息传递进行通信。面向对象编程通常用于解决复杂、抽象的问题,强调如何描述问题的实体和它们之间的关系。

对于面向对象的思想:当需要实现一个功能的时候,看重的并不是过程和步骤,而是关心谁帮我做这件事(偷懒,找人帮我做)。

特点:

  • 以对象为基本组织单位。

  • 数据和方法封装在一起,实现了数据的隐藏和封装。

  • 强调对象的属性和行为,以及对象之间的关系。

  • 代码的重用性较高,维护相对更容易。

    面向对象方式

    class Rectangle:
    def init(self, width, height):
    self.width = width
    self.height = height

    def calculate_area(self):
        return self.width * self.height
    

    width = 5
    height = 10
    rectangle = Rectangle(width, height)
    area = rectangle.calculate_area()
    print(f"面积为:{area}")

3、区别与对比

(1)组织方式

  • 面向过程:以函数(过程)为组织单位,注重算法和步骤。
  • 面向对象:以对象为组织单位,注重描述实体和它们的关系。

(2)数据和方法

  • 面向过程:数据和方法分离,函数对数据进行操作。
  • 面向对象:数据和方法封装在一起,实现了数据的隐藏和封装。

(3)重用性和维护性

  • 面向过程:代码重用性较低,维护相对困难。
  • 面向对象:代码重用性较高,维护相对容易。

(4)抽象性

  • 面向过程:相对较低的抽象性,更关注具体的操作步骤。
  • 面向对象:较高的抽象性,强调实体的属性和行为。

(5)适用场景

  • 面向过程:适用于简单、直接的问题,强调操作的步骤。
  • 面向对象:适用于复杂、抽象的问题,强调实体和关系。

二、类和对象

1、类(Class)

1.1、定义

以 class 关键字开始,后面跟着类的名称。通常类的名称采用驼峰命名法,即每个单词的首字母大写,而且最好具有描述性,能够清楚地表达其含义,私有类可用一个下划线开头。

class MyClass:
    pass  # 类体中可以为空
1.2、结构

大方向上分为:静态变量和动态方法。

1.3、三要素

类名,属性(对对象的特征描述),方法(对象具有的行为)

方法的定义格式和函数几乎一样,区别在于第一个参数必须是 self

举例:

类名:人 Person

属性:姓名/身高/年龄

方法:跑/工作/走路

张三今年18岁,身高1.78, 每天早上去跑步。

类:Person 属性:name,age,height 方法:run()

1.4、案例

class Person:
    # 类属性、静态属性
    name = '张三'
    age = 18
    height = 1.78
    # 动态方法
    def run(self):
        pass

1.5、类名操作静态属性

class Person:
    # 类属性、静态属性
    name = '张三'
    age = 18
    height = 1.78
    # 动态方法
    def run(self):
        pass

# 查询所有属性,内置函数 __dict__
print(Person.__dict__)
# {'__module__': '__main__', 'name': '张三', 'age': 18, 'height': 1.8, 'run': <function Person.run at 0x000001FE78CEF9A0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}

# 查找
print(Person.name)  # 张三

# 修改
Person.age = 19
print(Person.age)  # 19

# 增加
Person.sex = '男'
print(Person.sex)  # 男

# 删除
del Person.height
# 删除后查找不到会报错
print(Person.height)  # type object 'Person' has no attribute 'height'

2、对象(Object)

2.1、定义

是从类中出来的,只要是类名加上小括号,这就是一个实例化过程,这个就会实例化一个对象。

2.2、格式

对象名 = 类名()

class MyClass:
    pass

test = MyClass()  # 实例化对象
2.3、实例方法

由对象调用,至少一个 self 参数,执行实例方法时,自动将调用该方法的对象赋值给 self。

在计算机中,通常使用十六进制表示内存地址

十进制和十六进制都是用来表达数字的,只是表示的方式不一样

十进制和十六进制的数字之间可以来回转换

实例化对象

class Example:
    class_attr = "我是一个类属性"
    def work(self):  # self 表示当前调用方法的对象自己
        print(self)  # <__main__.Example object at 0x0000020A45B7AE00>
        print('我在工作')

# 实例化对象, obj 变量中仍然记录的是,对象在内存中的地址
obj = Example()
# 打印对象,则默认打印对象在内存的地址,结果等同于 work 里的 print(self)
print(obj)  # <__main__.Example object at 0x0000020A45B7AE00>

# 对象操作类中的方法,表示选择属性或者方法
obj.work()  # 调用方法时,程序员不需要传递 self 参数
# <__main__.Example object at 0x00000125FAB4AE00>
# 我在工作
Example().work()  # 这样操作也可以,但是不推荐,只能单个对象使用
# <__main__.Example object at 0x000001CD0CF974C0>
# 我在工作

# 对象查看类中的属性
print(obj.class_attr)  # 我是一个类属性

在方法内通过 self 获取对象属性

class Person:
    def test(self):
        print('%s的年龄是%d' % (self.name, self.age))

# 实例化对象
obj = Person()
# self 和 obj 指向的是同一个内存地址同一个空间
obj.name = '张三'
obj.age = 20
obj.test()  # 张三的年龄是20

# 对象查询对象中所有属性。 对象.__dict__
print(obj.__dict__)  # {'name': '张三', 'age': 20}

对象操作对象中的单个属性

给对象设置属性,非常的容易,但是不推荐使用,因为,对象属性的封装应该封装在类的内部。

class Person:
    def test(self):
        print('%s的年龄是%d' % (self.name, self.age))

# 实例化对象
obj = Person()
# self 和 obj 指向的是同一个内存地址同一个空间
obj.name = '张三'
obj.age = 20
obj.test()  # 张三的年龄是20

# 对象查询对象中所有属性。 对象.__dict__
print(obj.__dict__)  # {'name': '张三', 'age': 20}

# 增加
obj.job = 'IT'
# 查看实例属性
print(obj.job)  # IT

# 修改
obj.age = 18
print(obj.age)  # 18

# 删除
del obj.job
print(obj.job)  # 报错:AttributeError: 'Person' object has no attribute 'job'

类中的方法一般都是通过对象执行的(除去类方法,静态方法外),并且对象执行这些方法都会自动将对象空间传给方法中的第一个参数 self。

self 其实就是类中方法(函数)的第一个位置参数,只不过解释器会自动将调用这个函数的对象传给 self。

所以把类中的方法的第一个参数约定俗成设置成 self,代表这个就是对象。

3、区别与对比

类:

就是具有相同属性和功能的一类事物。(抽象)

一个类可以找到多个对象。

对象:

就是类的具体表现,是面向对象编程的核心。(具体)

某一个具体事物的存在,在现实世界中可以是看得见摸得着的,可以直接使用。

区分类和对象

中也有属性、行为两个组成部分,而对象是类的具体实例。

类好比是一个蓝图,就像是建筑师绘制的房屋设计图。这个图纸包含了如何构建房子的计划,定义了房子的结构和特征。

对象就像是由这个图纸建造出来的实际房子,每座房子可能有不同的颜色、家具和风格,但都是基于同一个图纸建造的。

类 = 抽象概念的人

对象 = 实实在在的某个人

三、构造函数与析构函数

1、构造函数(__init__ 方法)

具有初始化的作用,也就是当该类被实例化的时候就会自动执行该函数。那么通常就可以把要先初始化的属性放到这个方法里面。

初始化实例属性

__init__ 方法是一个普通的实例方法,第一个参数必须是 self,它代表正在创建的实例对象。通过这个方法,可以在实例中设置初始属性值,以便在对象创建后,这些属性将会具有特定的初始值。

调用初始化方法

(1)Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,__init__ 就是一个魔法方法,通常用来做属性初始化或赋值操作。

(2)如果类里面没有写 __init__ 方法,Python 会自动创建,但是不执行任何操作。

(3)如果为了能够在完成自己想要的功能,可以自己定义 __init__ 方法。

(4)所以一个类里无论自己是否编写 __init__ 方法,一定有 __init__ 方法。

当使用 类名() 创建对象时,会自动执行以下操作:

为对象在内存中分配空间 —— 创建对象

为对象的属性设置初始值 —— 初始化方法(init)

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 创建 Person 实例
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)

print(person1.name)  # Alice
print(person2.age)   # 30

注意:

  • 在类内部获取属性和实例方法,通过 self 获取。
  • 在类外部获取属性和实例方法,通过对象名获取。
  • 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址。
  • 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过 self 来判断是哪个对象调用了实例方法。

2、析构函数(__del__ 方法)

是在对象被销毁时自动调用的方法,用于执行清理操作,释放资源或进行其它必要的收尾工作。当一个对象的引用计数减少到零时(即没有变量引用它时),析构函数将被调用。

它的执行一般也就意味着对象不能够继续引用,回收内存。作用通常是销毁、删除临时的变量,主要对那些长期占用内存的临时变量进行销毁。

注意:

  • 构造函数析构函数的名称都是特殊的,不需要手动调用它们,Python 会自动调用。
  • 构造函数析构函数一样都不能有返回值。
  • 析构函数不能有参数。
  • 一个类只能有一个析构函数,所以以什么形式来重载析构函数,都会导致出错。
  • 析构函数在对象销毁的时候将被调用。
  • 析构函数通常用于资源的释放,如关闭文件、断开网络连接等。
  • 析构函数不是必需的,大多数情况下,Python 的垃圾回收机制会自动处理对象的销毁和内存释放。

当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数会被调用一次,这样可以用来释放内存空间。

class Person:
    def __init__(self):  # 构造函数
        print('我是init方法')
    def __del__(self):  # 析构函数
        print('被销毁了')

xm = Person()
print('最后一句')

# 运行结果:
# 我是init方法
# 最后一句
# 被销毁了

正确运行时不会去调用 __del__ 方法,会先打印“最后一句”,当对象结束其生命周期时(例如对象所在的函数已经调用完毕),系统会自动执行析构函数,析构函数往往用来做清理善后的工作。所以打印“被销毁了”成了最后执行的语句。-
当使用 del 删除对象时,会调用它本身的析构函数,相当于手动释放内存。

class Person:
    def __init__(self):
        print('我是init方法')
    def __del__(self):
        print('被销毁了')

xm = Person()
del xm  # 删除对象
print('最后一句')

# 运行结果:
# 我是init方法
# 被销毁了
# 最后一句

del xm 语句执行时候,内存立即被回收,即执行打印“被销毁了”,最后才是执行打印“最后一句”。

总结:

当有变量保存了一个对象的引用时,此对象的引用计数就会加1,

当使用 del() 删除变量指向的对象时,则会减少对象的引用计数。

如果对象的引用计数不为1,那么会让这个对象的引用计数减1,

当对象的引用计数为0的时候,则对象才会被真正删除(内存被回收)。

四、类属性与实例属性

类属性属于类,实例属性属于对象。

  • 类属性在内存中只保存一份,实例属性在每个对象中都要保存一份。

  • 通过类创建实例对象时,如果每个对象需要具有相同名字的属性,那么就使用类属性,用一份即可。

    class Example:
    class_attr = “我是一个类属性” # 类属性

    def __init__(self, instance_attr):
        self.instance_attr = instance_attr  # 实例属性
    

    创建两个实例

    obj1 = Example(“实例 1”)
    obj2 = Example(“实例 2”)

    类属性在所有实例之间共享,只有一份在内存中

    print(obj1.class_attr) # 输出: 我是一个类属性
    print(obj2.class_attr) # 输出: 我是一个类属性

    修改类属性会影响所有实例

    Example.class_attr = “修改后的类属性”
    print(obj1.class_attr) # 输出: 修改后的类属性
    print(obj2.class_attr) # 输出: 修改后的类属性

    实例属性在每个对象中都有独立的副本

    print(obj1.instance_attr) # 输出: 实例 1
    print(obj2.instance_attr) # 输出: 实例 2

    修改实例属性只影响单个实例

    obj1.instance_attr = “更新后的实例 1”
    print(obj1.instance_attr) # 输出: 更新后的实例 1
    print(obj2.instance_attr) # 输出: 实例 2

  • 类属性,类可以访问到,实例也可以访问到。

  • 实例属性,类访问不到,实例才能访问到。

    类属性的案例

    class Example:
    class_attr = “我是一个类属性” # 类属性

    通过类访问类属性

    print(Example.class_attr) # 输出: 我是一个类属性

    创建实例并通过实例访问类属性

    obj = Example()
    print(obj.class_attr) # 输出: 我是一个类属性

    实例属性的案例

    class Example:
    def init(self, instance_attr):
    self.instance_attr = instance_attr # 实例属性

    创建实例并设置实例属性

    obj = Example(“我是一个实例属性”)

    通过实例访问实例属性

    print(obj.instance_attr) # 输出: 我是一个实例属性

    尝试通过类访问实例属性(会报错)

    print(Example.instance_attr) # 报错: 类没有实例属性 AttributeError: type object ‘Example’ has no attribute ‘instance_attr’

如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。

如果通过实例对象去引用,会产生一个同名的实例属性。

这种方式修改的是实例属性,不会影响到类属性。

并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。

class Example:
    class_attr = "我是一个类属性"  # 类属性

# 通过类对象修改类属性
Example.class_attr = "修改后的类属性"
print(Example.class_attr)  # 输出: 修改后的类属性

# 创建实例
obj = Example()

# 通过实例对象修改实例属性,而不影响类属性
obj.class_attr = "实例属性"  # 这会创建一个同名的实例属性
print(obj.class_attr)       # 输出: 实例属性
print(Example.class_attr)   # 输出: 修改后的类属性

# 实例属性强制屏蔽类属性
del obj.class_attr  # 删除实例属性
print(obj.class_attr)  # 输出: 修改后的类属性

总结:

  • 实例属性属于各个实例所有,互不干扰。
  • 类属性属于类所有,所有实例共享一个属性。
  • 不要对实例属性类属性使用相同的名字,否则将产生难以发现的错误。

五、代码练习

1、定义一个类,类里面有类属性 number,有方法 eat,play,-
2、有初始化方法,实例化的时候需要传入 name,age,sex,-
3、有析构方法,需要在程序结束前进行调用。

class Person:
    number = 0  # 类属性,用于统计实例数量

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex
        Person.number += 1  # 每次创建实例时增加实例数量

    def eat(self, food):
        print(f"{self.name} is eating {food}")

    def play(self, activity):
        print(f"{self.name} is playing {activity}")

    def __del__(self):
        Person.number -= 1  # 删除实例时减少实例数量
        print(f"{self.name} 对象被销毁了")

# 实例化对象
person1 = Person("Alice", 25, "女")
person2 = Person("Bob", 30, "男")

# 调用对象方法
person1.eat("pizza")
person2.play("soccer")

# 输出类属性
print(f"实例数量:{Person.number}")

# 程序结束时会自动调用析构方法

📝结尾

看到这里了还不给博主扣个:- ⛳️ 点赞☀️收藏 ⭐️ 关注!- 💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖- 拜托拜托这个真的很重要!- 你们的点赞就是博主更新最大的动力!- 有问题可以评论或者私信呢秒回哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值