Python面向对象编程核心概念:类、self与构造函数详解
本文详细解析Python面向对象编程的核心机制,涵盖类的定义规范、驼峰命名法的应用、self参数的作用域与绑定原理,以及__init__构造函数的使用。通过实例演示实例属性与类属性的区别、对象生命周期管理及内存释放机制,帮助开发者掌握Python OOP的关键实践。
面向对象编程(Object-Oriented Programming,简称OOP)是Python编程的核心范式之一,它通过“类”这一概念来组织代码。
面向对象编程的本质,是利用对象的属性和方法来进行编码开发的过程。
在面向对象编程中,自定义对象的数据类型即对应于“类”(class)的概念。
类的声明关键字为 class。
使用 class 关键字声明类时,类名应遵循驼峰命名法,即首字母大写,若由多个单词组成,每个单词的首字母均需大写。尽管在Python的一般变量命名中建议避免使用驼峰命名法,但类名的命名是一个特例,推荐使用驼峰命名法以符合规范。
类的定义与具体使用方式。
类的定义示例如下:
class Nmae(object):
# class关键字 + 类名(首字母大写) + 小括号(括号内填写 object:为python中的通用对象,书写通用对象的 class 会带有更多的内置功能) + 冒号
变量 = 变量的值
# 可以定义 类 的变量
def func(self):
do
# 也可以定义 类 的函数:类函数内有个必传参数 'self' ,一定要书写在类函数的第一参数位,这是 python 内部的语法规定
# 需要注意的是 类的属性与函数的缩进要统一
类的实际使用示例如下:
# 定义一个动物类;动物类中定义一个变量;定义一个 跑 的函数(属性)
class Animal(object): # 定义一个类
name = '哈士奇' # 类变量(类属性)
def run(self): # 类函数:将 self 作为第一个参数传入 类函数 'run()'
print(f'{self.name} can run')
# 'self.name'为类属性,如果不加上'self.'则不会找到类属性;如果想要在类函数中调用类属性就必须加上'self.'
# 'self' 参数具备两个功能
# 1.可以帮助我们调用类属性
# 2.将使用 self 的函数调用到类中,如果我们有另一个函数,可以在另一个函数中通过 'self.' 来进行调用 run 函数
dog = Animal() # 类的实例化
print(dog.name) # 通过实例化进行属性调用
dog.run() # 通过实例化,进行函数调用
# >>> 执行结果如下:
# >>> 哈士奇
# >>> 哈士奇 can run
类方法中的关键参数:self。
在类内部定义的所有实例方法,必须包含 self 参数,且该参数必须位于参数列表的第一位,每个实例方法有且仅有一个 self 参数。
self 参数的含义是:在类中定义的方法里,第一个参数 self 指向调用该方法的实例对象。在方法内部,通过 self.属性名的形式来访问该对象的实例属性。
- self 是类方法中必须传递的参数,且必须置于第一个参数位置。
- self 实际上是一个对象引用,它代表当前正在调用该方法的实例化变量本身。
- 可以通过点号(.)直接通过 self 定义类变量,例如 self.name = 'Neo'。如果在函数体内定义变量,需通过 self + . + 变量名的方式进行赋值。
- 绑定在 self 上的变量以及含有 self 参数的函数,可以在类的任何其他函数内被随意调用。
- 在类中非函数部分(即类级别)定义的变量,在定义时不需要使用 self。
如何深入理解 self 参数。
可以通过类比来理解。
- 如果把“类”比作建造房子的设计图纸。
- 那么类实例化后生成的对象,就是真正可以居住的实体房子。
- 根据同一张图纸(类),可以设计出成千上万栋房子(实例对象)。
- 每栋房子外观相似(拥有相同的实例属性和实例方法),但它们各自拥有不同的主人。
- 如何区分不同的房子?通过 self 参数,可以确保每个房子的主人只能进入自己的房子(即每个实例对象只能访问自己的实例属性和实例方法)。
关键点总结。
- 一个类可以生成多个实例对象。当某个实例对象调用实例方法时,该对象会将自身的引用作为第一个参数自动传递给该方法。
- 换句话说:Python 解释器会自动将实例方法的第一个参数指向调用该方法的对象。
- 这样做使得 Python 解释器能够明确知道具体要执行哪个对象的实例方法。
- 在调用实例方法时,用户不需要手动为第一个参数(self)传值。
如果对上述概念仍感困惑,我们可以结合类的两个关键要素——属性和方法,通过具体代码来看看 self 的实际应用效果:
class Persion(object):
name = None
age = None
def run(self):
print(f'{self.name} 的健身项目是\'跑步\'')
def swim(self):
print(f'{self.name} 的健身项目是\'游泳\'')
neo = Persion()
neo.name = 'Neo'
neo.run()
# >>> 执行结果如下:
# >>> Neo 的健身项目是'跑步'
我们重新实例化一个对象,观察这个新实例化对象是否同步了 neo 的 name 属性。
class Persion(object):
name = None
age = None
def run(self):
print(f'{self.name} 的健身项目是\'跑步\'')
def swim(self):
print(f'{self.name} 的健身项目是\'游泳\'')
neo = Persion()
neo.name = 'Neo'
neo.run()
jack = Persion()
jack.run()
# >>> 执行结果如下:
# >>> Neo 的健身项目是'跑步'
# >>> None 的健身项目是'跑步'
从输出结果可以看出,我们修改的 neo 实例化对象对应的 name 值仅作用于其自身实例,而 Person 类本身以及新的 jack 实例化对象并未受到影响。
因此,即使使用新对象进行实例化,仍需通过新的实例化对象来修改类的属性,以达到预期效果。这很好理解:虽然都是人类(类),但每个人的个体特征(实例属性)是独立的。他们在拥有类的共同属性(name, age)后,也可以自定义属于自己的属性。
目前我们的 Person 类定义了两个属性 'name' 和 'age',如果我们要再添加一个属性呢?答案是肯定的。现在我们针对 'Jack' 增加一个自定义属性,尝试一下。
class Persion(object):
name = None
age = None
def run(self):
print(f'{self.name} 的健身项目是\'跑步\'')
def swim(self):
print(f'{self.name} 的健身项目是\'游泳\'')
neo = Persion()
neo.name = 'Neo'
neo.run()
jack = Persion()
jack.top = 180
print('\'Jack\'的身高是', jack.top)
# >>> 执行结果如下
# >>> Neo 的健身项目是'跑步'
# >>> 'Jack'的身高是 180
print('\'Neo\'的身高是', neo.top)
# >>> 执行结果如下:
# >>> AttributeError: 'Persion' object has no attribute 'top'
从上述 jack.top 与 neo.top 的自定义属性对比中,我们可以发现三件事。
1. 实例化对象可以自定义属性。
2. 每个实例化对象自己定义的属性与其他实例化对象不通用。
3. Person 类在实例化对象之后,依然只保留其定义的两个属性(name 和 age)。实例化对象自定义的属性仅作用于该对象本身,并不影响类定义。
self 的解析与总结。
说实话,初次接触 Python 中的 self 时,许多人会感到困惑。这里做一个总结,希望对同样感到困惑的读者有所帮助。
- 在 Python 中,self 代表类的实例;在定义类的方法时 self 是必须的,但在调用时不必显式传入相应参数。
- 在 Python 中,self 只有在针对类的方法时才有意义。
- self 只能用于 Python 类的方法中。
具体的举例说明如下:
关于属性。
关于属性 - 1:如果变量定义在类下面而不是类的方法下面,那么该变量既是类的属性,也是类实例的属性。
class Cat(object):
eyes = '有2只眼睛'
legs = '有4条腿'
tail = '有1只尾巴'
dragonLi = Cat()
dragonLi.name = '狸花猫'
dragonLi_eyes = dragonLi.eyes
dragonLi_legs = dragonLi.legs
dragonLi_tail = dragonLi.tail
print(' 猫 ' + Cat.eyes, Cat.legs, Cat.tail)
print(dragonLi.name, dragonLi_eyes, dragonLi_legs, dragonLi_tail)
# >>> 执行结果如下:
# >>> 猫 有2只眼睛 有4条腿 有1只尾巴
# >>> 狸花猫 有2只眼睛 有4条腿 有1只尾巴
关于属性 - 2:如果变量定义在类的方法下面,若加了 self,则该变量是类实例的属性,不是类的属性;若没有加 self,则该变量只是该方法的局部变量,既不是类的属性也不是类实例的属性。
class Cat(object):
eyes = '有2只眼睛'
legs = '有4条腿'
tail = '有1只尾巴'
def __init__(self): # 关于__init__() 会在下面的 '类的构造器'有详细讲解
self.color_01 = '黄棕色'
color_02 = '黑棕色'
dragonLi = Cat()
dragonLi_color_01 = dragonLi.color_01
print('狸花猫有两种披毛颜色,一种是:', dragonLi_color_01)
# >>> 执行结果如下:
# >>> 狸花猫有两种披毛颜色,一种是: 黄棕色
dragonLi_color_02 = dragonLi.color_02
print('狸花猫有两种披毛颜色,另一种是:', dragonLi_color_02)
# >>> 执行结果如下:
# >>> AttributeError: 'Cat' object has no attribute 'color_02'.
关于方法。
关于方法 1:如果在类中定义函数时加了 self,则该函数是类实例的方法,而不是类的方法。
class Cat(object):
def eat(self):
print('爱吃鱼')
dragonLi = Cat()
dragonLi.eat()
# >>> 执行结果如下:
# >>> 爱吃鱼
Cat.cat()
# >>> 执行结果如下:
# >>> TypeError: Cat.eat() missing 1 required positional argument: 'self'
关于方法 2:如果在类中定义函数时没有加 self,则该函数只是类的方法,而不是类实例的方法。
class Cat(object):
def eat():
print('爱吃鱼')
Cat.eat()
# >>> 执行结果如下:
# >>> 爱吃鱼
dragonLi = Cat()
dragonLi.eat()
# >>> 执行结果如下:
# >>> TypeError: Cat.eat() takes 0 positional arguments but 1 was given
小结。
属性规则:
- 如果变量定义在类下面而不是类的方法下面,那么该变量既是类的属性,也是类实例的属性。
- 如果变量定义在类的方法下面,若加了 self,则该变量是类实例的属性,不是类的属性;若没有加 self,则该变量只是该方法的局部变量,既不是类的属性也不是类实例的属性。
方法规则:
- 如果在类中定义函数时加了 self,则该函数是类实例的方法,而不是类的方法。
- 如果在类中定义函数时没有加 self,则该函数只是类的方法,而不是类实例的方法。
类的构造函数。
前面我们了解了类的创建、类的属性以及类函数的使用方法,现在来看看类的构造函数。
什么是类的构造函数?构造函数是类中的一种默认函数,通过定义它可以在类实例化的同时,将参数传入类中(类似于函数执行时可以传入参数)。
构造函数的创建方法。
重点:构造函数依然要在类中定义。
def __init__(self, a, b) # def关键字 + __init__ + 小括号(括号内第一个传入的依然是 self ,后面再跟上希望实例化时传入的参数)
self.a = a # 在构造函数里,将参数绑定在 self 中,将变量通过 self 绑定之后,就可以在类的各个函数中进行调用了
self.b = b
构造函数的用法,示例如下:
class Test(object):
def __init__(self, a): # __init__ 构造函数一定要写在第一个,这是一个很好的编程规范
self.a = a
def run(self):
print(self.a)
test = Test(1)
test.run()
# >>> 执行结果如下:
# >>> 1
test_02 = Test('Hello')
test_02.run()
# >>> 执行结果如下:
# >>> Hello
接下来我们再使用构造函数针对前面我们创建的 Cat 类进行修改。
class Cat(object):
def __init__(self, eyes, legs, tail, color='黄棕色'):
self.eyes = eyes
self.legs = legs
self.tail = tail
self.color = color
def show_cat(self):
self.work = '抓老鼠'
print('猫的通用属性为', self.eyes, self.legs, self.tail)
dragonLi = Cat('2只眼睛', '4条腿', '1只尾巴')
dragonLi.show_cat()
# >>> 执行结果如下:
# >>> 猫的通用属性为 2只眼睛 4条腿 1只尾巴 黄棕色
dragonLi.name = '狸花猫'
dragonLi.color = '虎斑色'
print(dragonLi.name, dragonLi.eyes, dragonLi.legs, dragonLi.tail, dragonLi.color, dragonLi.work)
# >>> 执行结果如下:
# >>> 狸花猫 2只眼睛 4条腿 1只尾巴 虎斑色 抓老鼠
关于对象的生命周期。
注意:这里说的对象生命周期,指的是实例化的对象。
之前我们提到过,当一个变量不再被使用时,就会被内存管理器清理。接下来我们就来看看一个变量的一生,从出现到消亡的过程。在这里插入这一章节介绍对象的生命周期,是为了更好地理解对象,从而更好地使用它们。
我们之前学习的深拷贝与浅拷贝,浅拷贝是创建一个新的内存地址,而深拷贝是使用之前已经定好的变量。
通过对一个对象的生命周期的理解,可以让我们权衡是终结一个对象还是继续使用它们。
我们通过构造函数完成一个对象的实例化,此时对象的生命周期开始。在这个时候,内存管理器发现有一个对象加入,就会为该实例化对象分配一个内存地址(即实例化对象在内存中的“家”)。
接下来我们就可以操作这个对象,可以调用它的内置函数和功能。当我们不再使用这个对象时,一般有两种情况:
第一种是这个内存块中的值没有变量与之绑定。例如,当一个变量的默认值被一个函数调用后,变量有了新的值,此时变量原有的默认值与变量不再是赋值关系。
第二种是当我们执行完所有程序,即代码已经执行到了最后一行。Python 解释器发现已经处理完所有业务,此时脚本会停止处理并释放脚本中所有的对象。释放所有对象其实就是告知内存管理器,内存管理器会自动处理这些对象的内存地址。
以上两种情况的统一表现形态都是不再使用这些对象。此时,每个对象中自带的内置函数 __del__(两个下划线)就会被调用,通知内存管理器从内存中释放每个对象对应的内存块。这就是整个对象的生命周期。
无论是字符串、数字、列表、元组、字典、集合,甚至布尔类型与空类型,我们知道 Python 中一切皆是对象,所以它们也是按照这个规律存在与消亡。
Python 中的内存管理都是自动完成的,所以我们并不需要特意去对它进行专门的处理。
关于 __del__ 函数并不需要我们书写和定义,当我们实例化一个对象之后,它就会默认存在,并拥有自动通知内存管理器清理内存的功能。这也是 Python 的特点之一。
以上就是 Python 学习之面向对象编程详解的详细内容,更多关于 Python 面向对象编程的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









