python 类与对象的详细用法
当前版本:
简介
类是一种自定义的数据类型,用于创建对象。类定义了一个对象的属性(也称为数据成员)和方法(也称为成员函数)。对象是类的实例化,是具体的数据实体,可以访问类中定义的属性和方法。
文章目录如下
1. 类与对象的作用
2. 基础用法
2.1. 定义类
2.2. 定义属性
2.3. 定义方法
3. 类的继承
3.1. 单继承
3.2. 多继承
3.3. 连续继承
3.4. 多态性
4. 类的装饰器
4.1. 类方法 @classmethod
4.2. 静态方法 @staticmethod
4.3. 类方法与静态方法的区别
5. 应用场景
5.1. 汽车案例
5.2. 电视案例
1. 类与对象的作用
类与对象的作用是将数据和相关的操作封装在一起,以创建可重用和模块化的代码。
类与对象本身比较抽象,我们将其带入到现实中作比喻:
我们每个人类需要做很多事,这些事情存在不同的类型,也有大事小事之分,所以需要通过不同的类来区分不同的事情。
【类1】我名下有一辆汽车,那么需要对汽车封装一个类:
类的属性:汽车的颜色、品牌、型号等。 类的方法:汽车的功能 加速、刹车、转弯等。 对象:将汽车实例化,通过这个对象去调用,而不是每次繁琐的去执行那些方法。 【类2】我有一部手机,那么再对这个手机封装一个类:
类的属性:手机的品牌、型号、尺寸等。 类的方法:手机的功能 拍照、打电话、发送短信等。 对象:将手机实例化,也是通过对象去调用这个类 类的主要作用就是将大事分成不同的类,大事中的细节通过方法去定义。
大致流程如下:
类与函数的区别
"函数" 的使用方法与 "类" 实际上是差不多的,一般在封装一个独立的方法时,直接使用函数就行。如果是编写一个小程序,那么这个小程序会存在大量关联性的方法,这时候函数就显得有点无助。比如封装一个购物车,购物车肯定包含 加入购物车、删除商品、结算商品,计算价格等方法,这些方法都具有一定的关联性。像一部分变量是公用的,使用函数的话只能将这些变量全局化,但全局化又会面临另一个问题,整个程序的变量非常多,肯定存在大量相同的变量(这种情况难以处理)。如果使用类来处理,类中的变量对这个类里面的方法是公用的,对其他类是隔离的,所以能够避免变量名相同的劣势。
类也支持继承的概念,可以通过继承从其他类获取属性和方法,并进行扩展和定制。并且可以共享类的属性,即类的所有实例对象可以访问和修改类的属性。所以大功能使用类,独立功能使用函数。
2. 基础用法
2.1. 定义类
python 使用关键字 class 来定义一个类,比如:
class 类名(继承对象):
代码块
定义一个最简单的类:
# 定义一个类,继承超类(提供一些通用的方法和属性)
class MyClass(object): # 在python3中默认继承object,可以为空
# 在类中编写代码
print("这是一个类!")
# 创建一个类的实例
mc = MyClass()
2.2. 定义属性
属性实际上就是在类中定义的变量,作用于类中。属性包含:类属性、私有类属性、实例属性、方法属性。
当这些属性名相同时,在方法中调用属性的优先级为:
方法属性 > 实例属性 > 类属性
举一个简单的例子
# 定义一个类
class MyClass(object):
x = 10 # 定义的属性
def func(self): # 定义的方法
print(f"这是类中的一个方法, 属性x为:{self.x}") # 共享类属性
# 创建一个类的实例
mc = MyClass()
mc.func() # 调用类中的方法
注意:self 是一个特殊的参数,用于引用当前对象(实例)。能够使得类的方法可以与对象实例进行交互,访问和修改对象的属性,并调用其他方法。若不使用self参数,在方法中无法直接访问和操作对象实例的属性和方法。如下:
class MyClass(object):
def __init__(x):
x = x
def func():
print(x)
mc = MyClass(10) # 实例化对象,传入一个参数
mc.func() # 调用其中一个方法
无法实例化,并且 func 方法也无法调用属性x
常见3种定义属性的方法
【方案一】直接定义属性(类属性)
# 定义一个类
class MyClass(object):
x = 10 # 定义一个共享属性
def func1(self): # 定义方法1
print(f"方法1中引用属性x:{self.x}")
def func2(self): # 定义方法2
print(f"方法2中引用属性x:{self.x}")
mc = MyClass() # 创建一个类的实例
mc.func1() # 调用方法1
mc.func2() # 调用方法2
在类中直接定义的属性具有类级别的作用域,可以被类的所有实例对象所共享。但如果需要为不同的实例对象定义不同的属性值,就需要在每个实例对象的构造方法中进行定义,或者使用其他方式来实现。
【方案二】通过构造方法 __init__() 来定义
类中直接定义的属性是在类的层级下直接定义的,而__init__
方法是类的构造方法,在实例化对象时被调用来初始化对象的属性。
class MyClass(object):
def __init__(self, x): # 初始化一个实例属性
self.x = x # 使用self.x引用变量x
mc = MyClass(10) # 实例化时需要传入一个参数(自定义)
比如:使用两个属性都接收变量 x
class MyClass(object):
def __init__(self, x): # 初始化一个实例属性
self.x = x # 使用self.x引用变量x
self.y = x # 使用self.y引用变量x
def func(self):
print(f"变量self.x的值为:{self.x}")
print(f"变量self.y的值为:{self.y}")
mc = MyClass(10) # 实例化对象,传入一个参数
mc.func() # 调用方法
这个变量同样支持默认值,写法与函数一样
def __init__(self, x=默认值):
如果需要多个参数则使用逗号分开
def __init__(self, x=默认值, y, z):
【方案三】通过自定义方法动态定义属性
上面两种属性都可以作用到整个类全局,第3种方案就是在方法中定义属性
class MyClass(object):
def func(self, x): # 封装一个方法
self.x = x
print(self.x)
mc = MyClass() # 实例化对象
mc.func(10) # 调用方法需要传入一个参数
在方法中定义属性与第2种方案是类似了,同样可以定义多个参数和默认值
def func(self, x=默认值, y, z):
2.3. 定义方法
方法实际上就是在类中定义的函数,作用于类中。
class MyClass(object): # 定义一个类
def func(self, x): # 封装一个方法
pass
方法间通过关键字 self 来相互调用
class MyClass(object): # 定义一个类
def func1(self): # 封装第一个方法
print("我是方法1")
def func2(self): # 封装第二个方法
print("我是方法2")
self.func1() # 调用方法1
MyClass().func2() # 调用方法2
上面的方法都是共有方法,而在 python 中还有一个私有方法(需要以双下划线开头)
class MyClass(object):
def __func1(self): # 私有方法(以双下划线开头)
print('这是一个私有方法')
def func2(self): # 公共方法
print('这是一个普通方法')
self.__func1() # 内部调用这个私有方法
代码中定义了2个方法,一个私有,一个共有。私有方法在外部无法调用
mc = MyClass() # 实例化这个对象
mc.__func1() # 调用这个私有方法
这种方法只能在内部调用,代码 func2 中调用了 __func1,所以直接在外部调用 func2
mc = MyClass() # 实例化这个对象
mc.func2() # 调用这个公共方法
3. 类的继承
继承是通过创建一个子类来继承另一个已经定义的类的属性和方法。流程图如下:
爷爷类没有继承任何其他类,所以他只享有自己的属性和方法; 爸爸类和叔叔类都继承了爷爷类,所以他们可以共享爷爷类的属性和方法,但爸爸和叔叔的属性、方法互不干涉; 儿子类继承了爸爸类,他可以共享爸爸类的属性和方法;由于爸爸继承了爷爷,所以儿子也共享爷爷的属性和方法; 侄儿类同时继承叔叔和爸爸,所以他可以同时共享叔叔类和爸爸类的属性和方法;由于叔叔和爸爸也继承了爷爷,所以侄儿同样继承爷爷的属性和方法; 孙子类继承了儿子和侄儿,所以孙子可以共享儿子类和侄儿类的属性和方法;由于儿子和侄儿也继承了爸爸、叔叔、爷爷,所以孙子可以共享所有类的属性和方法。 每个类可以使用的属性和方法如下:
爷爷:本身 爸爸:本身、爷爷 叔叔:本身、爷爷 儿子:本身、爸爸、爷爷 侄儿:本身、爸爸、叔叔、爷爷 孙子:本身、儿子、侄儿、爸爸、叔叔、爷爷
3.1. 单继承
定义 A、B两个类,子类可以继承父类中的所有属性和方法
class A(object): # 定义一个类
x = 10
def func_a(self):
print("我是A类中的方法")
class B(A): # 继承A类(包括全部属性和方法)
def func_b(self):
print(f"我是B类中的方法,属性x为:{self.x}")
B().func_b() # 调用B类中的方法
B().func_a() # 调用B类中继承的方法
【子类重写】如果子类与父类的方法名或属性名相同,调用子类时默认使用子类的方法或属性
class A(object): # 定义A类
a = 1
def func1(self):
print('A类')
class B(A): # 继承A类
a = 2 # 属性与A类相同
def func1(self): # 方法也与A类相同
print(f"B类,属性a为:{self.a}") # 使用当前类的属性
B().func1() # 调用子类
3.2. 多继承
当继承多个类时,这些类的属性名或方法名相同时,默认使用继承的第一个父类
class A(object):
a = 1 # 父类定义属性a
class B(object):
a = 2 # 父类定义属性a
class C(A, B): # 先继承A,后继承B
def func1(self):
print(f"属性a的值为:{self.a}") # 默认调用A的属性
C().func1() # 调用子类C
3.3. 连续继承
当出现连续继承时,最小的子类可以使用上面全部父类的属性和方法
class A(object): # 定义第1个类
def func1(self):
print('A类')
class B(A): # 定义第2个类,继承第1个
def func2(self):
print('B类')
class C(B): # 定义第3个类,继承第2个
pass
C().func1() # 使用第3个类的实例调用第1个类的方法
C().func2() # 使用第3个类的实例调用第2个类的方法
当连续继承太多,相同的方法名也比较多,我们也可以使用 super 函数去调用上一个父类的方法
class A(object): # 定义第1个类
def func(self): # 方法名与第2个类相同
print('A类')
class B(A): # 定义第2个类
def func(self): # 方法名与第1个类相同
print('B类')
class C(B): # 定义第3个类
def func(self): # 方法名与第1个和第2个类相同
super().func() # 调用上一级类方法,不需要指定类名
C().func() # 调用子类的方法
注意:super 只能调用上一级,调用其他可以直接指定类名
A.func(self)
B.func(self)
它们的继承的关系可以通过 __mro__ 查看
class A(object): # 继承超类
pass
class B(A): # 继承第1个类
pass
class C(B): # 继承第2个类
pass
print(C.__mro__) # 查看C类的继承关系
3.4. 多态性
多态性是通过继承和方法重写来实现的,允许不同的对象对相同的方法作出不同的响应,从而增加了代码的灵活性和可扩展性。
封装4个类,1个作为父类,另外三个作为子类,它们的方法名称是一样的
class Animal: # 父类
'''动物类'''
name = '[3号]'
class Dog(Animal): # 继承动物类
'''小狗类'''
def sound(self):
print(f"{self.name} 汪汪汪!")
class Cat(Animal): # 继承动物类
'''小猫类'''
def sound(self):
print(f"{self.name} 喵喵喵!")
class Cow(Animal): # 继承动物类
'''小牛类'''
def sound(self):
print(f"{self.name} 哞哞哞!")
4个类中的方法名完全一致,这时我们来封装一个函数,用来调用这些类(因为方法名是一样的,调用一个即可)
def make_sound(x): # 需要传入一个x参数
'''x参数就是类名,通过传入的类名去调用方法'''
x.sound() # sound方法是4个类中的方法
最后我们来实例化这几个动物类
dog = Dog() # 实例化小狗类
cat = Cat() # 实例化小猫类
cow = Cow() # 实例化小牛类
向函数中传入小狗类
make_sound(dog) # 调用函数,传参为小狗类
向函数中传入小猫类
make_sound(cat) # 调用函数,传参为小猫类
向函数中传入小牛类
make_sound(cow) # 调用函数,传参为小牛类
4. 类的装饰器
装饰器是一个用于修改函数或类的行为的特殊函数或类,它可以在不修改原始代码的情况下,添加额外的功能或行为,提供了一种简洁的方式来修改或增强函数或类的功能。
4.1. 类方法 @classmethod
类方法是作用于整个类而不是实例的方法。它们可以访问和修改类的属性,提供额外的构造方法,实现多态行为,封装和管理类相关的功能,与类级别数据进行交互。类方法使用 @classmethod 装饰器进行标识,第一个参数通常被命名为 cls,表示类本身。
我们在类中定义一个实例方法和一个类方法,看一下两者之间的调用区别:
class MyClass(object):
def func1(self): # 封装一个实例方法
print('我是实例方法')
@classmethod # 特殊标识符
def func2(cls): # 封装一个类方法
print('我是类方法')
在调用时,实例方法需要初始化实例,而类方法不需要
m = MyClass()
m.func1() # 调用实例方法,需要实例化
MyClass.func2() # 调用类方法,不需要实例化
为什么调用类方法不需要实例化?
因为声明的类方法就是类的本身,形参 cls 就相当于类名,所以本身调用本身不需要实例化。实例化是用来调用实例方法的
类方法使用类属性时,以 cls 方式调用
class MyClass(object):
x = 1 # 定义一个属性
@classmethod
def func(cls): # 使用cls
print(f'公共类属性a为{cls.x}')
MyClass.func() # 调用类方法
注意:类方法无法调用实例属性,仅适用于类级别上的操作。
class MyClass(object):
def __init__(self):
self.x = 1 # 定义实例属性
@classmethod
def func(cls):
print(f'实例属性a为{cls.x}') # 无法调用
MyClass.func() # 调用类方法
4.2. 静态方法 @staticmethod
静态方法既不需要传递类对象,也不需要传递实例对象(没有形参)。静态方法可以独立于类的任何实例或类级别而存在,因此它们是最为模块化、灵活和可维护的方法类型之一。
特点
- 静态方法不能访问类的变量。
- 静态方法不需要特殊的参数,但仍然可以使用默认参数。
- 静态方法往往更容易维护和测试,因为它们不会改变且不依赖于类的状态。
- 由于静态方法可以独立于类而存在,它们也可以很方便地跨越模块、包和应用程序。
静态方法的调用与类方法类似,不需要实例化对象
class MyClass():
@staticmethod # 特殊标识符
def func(n): # 定义一个静态方法
print(f"我是一个静态方法,属性n为:{n}") # 调用属性不需要关键字
MyClass.func(10) # 调用静态方法,传入一个参数
由于静态方法本身是独立的,所以它无法调用实例属性和实例方法。当然,实例方法也无法调用静态方法。
class MyClass(object):
def func1(self): # 定义一个实例方法
print('实例方法')
@staticmethod
def func2(): # 定义一个静态方法
func1() # 直接调用实例方法(无法调用)
MyClass().func1() # 通过类调用实例方法(无法调用)
MyClass.func() # 调用静态方法
总结
4.3. 类方法与静态方法的区别
1、从参数的角度来看
类方法第一个参数默认 cls 代表类的本身,非实例本身。 静态方法不需要额外的形参,可以把它当成一个独立函数。
2、从属性角度来看
类方法可以访问类中的其他属性。 静态方法不可以访问类中的其他属性。
3、从继承角度来看
类方法可以被继承和重写。 静态方法无法被继承。
4、从用途角度来看
类方法通常用于操作与类有关的属性和方法,适用于通用代码。 静态方法通常用于实现可维护性高的代码,适用于独立和复用的代码块。
5. 应用场景
5.1. 汽车案例
定义一个汽车的类,具有品牌、型号和颜色等属性,封装汽车详细信息的方法、汽车数量的方法、检查颜色合法性的方法。
# 封装一个表示汽车的类
class Car(object):
car_count = 0 # 调用类的计数器
def __init__(self, brand, model, color):
self.brand = brand # 汽车品牌
self.model = model # 汽车型号
self.color = color # 汽车颜色
Car.car_count += 1 # 每次创建一个Car对象,计数增加1
def get_car_info(self):
'''封装一个获取汽车的详细信息的实例方法'''
return f"品牌: {self.brand}, 型号: {self.model}, 颜色: {self.color}"
@classmethod
def get_total_count(cls):
'''封装一个获取总的汽车数量类方法'''
return cls.car_count
@staticmethod
def is_valid_color(color):
'''封装一个用于检查是否是合法的颜色的静态方法'''
valid_colors = ["red", "blue", "green"]
return color.lower() in valid_colors
我们来调用这个类
# 创建两个Car对象,并传入品牌、型号和颜色
car1 = Car("Tesla", "Model 3", "red")
car2 = Car("Toyota", "Camry", "blue")
# 调用实例方法,获取汽车的详细信息
print(car1.get_car_info())
print(car2.get_car_info())
# 调用类方法,获取总的汽车数量
print("汽车数量为: {}台".format(Car.get_total_count()))
# 调用静态方法,检查颜色是否合法
print(Car.is_valid_color("red"))
print(Car.is_valid_color("yellow"))
5.2. 电视案例
定义一个电视的类,封装一些打开电视、关闭电视、切换频道的方法
# 定义一个电视类
class TV:
def __init__(self, brand, size):
'''初始化方法,用于创建TV对象时的初始设置'''
self.brand = brand # 品牌
self.size = size # 尺寸
self.is_on = False # 电视开关(默认关闭)
self.volume = 50 # 音量(默认50)
self.channel = 1 # 电视频道(默认1)
def turn_on(self):
'''封装一个实例方法,打开电视'''
self.is_on = True
def turn_off(self):
'''封装一个实例方法,关闭电视'''
self.is_on = False
def change_channel(self, channel):
'''封装一个实例方法,切换电视频道'''
self.channel = channel
def volume_up(self):
'''封装一个实例方法,增加音量'''
if self.volume < 100:
self.volume += 1
def volume_down(self):
'''封装一个实例方法,减小音量'''
if self.volume > 0:
self.volume -= 1
我们来调用这个类
# 创建一个TV对象
tv = TV("Sony", 55)
# 打开电视
tv.turn_on()
# 切换到第5个频道
tv.change_channel(5)
# 增加音量
tv.volume_up()
# 打印电视信息
print(f"品牌: {tv.brand}, 尺寸: {tv.size}, 频道: {tv.channel}, 音量: {tv.volume}")
# 关闭电视
tv.turn_off()
作者:一只勤劳的耗子