Python基础-10.类
一、类的基础
面向对象的概念
面向对象是用“对象”来组织代码,把“数据”和“操作数据的方法”放在一起,并通过对象之间的关系来解决问题。
对象的核心概念:
- 对象:现实世界事物在程序里的抽象,这个对象的范围可大也可小,可以是某个领域或者生物物种(比如:地球、食物、宠物等),也能是某个事物(比如:某个人、某只猫或者狗等),一切皆对象。
- 类:描述一类对象的“模版”,定义对象有什么属性和方法。
- 封装:把数据和功能打包在对象里,对外提供简单接口,内部细节可隐藏。
- 继承:子类可以复用父类的功能,也可以扩展或重写。
- 多态:不同对象实现相同方法接口时,可以被统一使用(比如:狗的叫声是"汪汪汪"、猫的叫声是"喵喵喵")。
可以想象我们在游戏中设计一个狗的角色,我们可以通过以下构思来实现这个角色的对象特征:
- 1.从相貌特征中,狗是黄色的、有4条腿、身高100cm、体重15斤。
- 2.从行为上来描述,狗会跑、会打滚、会睡觉以及会叫。
【示例】定义一个狗的对象,相貌特征用变量来表示,行为特征用函数来表示。
class Dog:
color = 'yellow'
legs = 4
height = 100
weight = 15
def run(self):
print('狗狗在奔跑')
def roll(self):
print('狗狗在打滚')
def sleep(self):
print('狗狗在睡觉')
def call(self):
print('汪汪汪')
类的定义
Python中我们使用class关键字来定义类,在每个程序中可以定义多个类。
语法:
class 类名:
类属性(可多个)
类方法(可多个)
温馨提示:在定义类名的时候名字最好需要有标识度,最好名字可以一看就懂,来增加程序的可读性。建议由单词构成类名,建议每个单词的首字母大写,其它字母小写。
【示例】代码如下:
class Dog:
color = 'yellow'
legs = 4
height = 100
weight = 15
def dog_call(self):
print('汪汪汪!!!')
Dog().dog_call() # 调用类中的函数
''' 运行结果如下
汪汪汪!!!
'''
类的使用
1.获取类中的属性
我们可以直接获取类下面的类属性。
【示例】代码如下:
class A:
a = 8
b = 18.2
c = 'abc'
print('a:', A.a, '\nb:', A.b, '\nc:', A.c)
''' 运行结果如下
a: 8
b: 18.2
c: abc
'''
2.使用类中的方法
我们也可以像获取类属性的方法并调用。
【示例】代码如下:
class B:
def a(self):
print('调用B类中的a()方法')
def b(self):
print('调用B类中的b()方法')
B().a()
B().b()
''' 运行结果如下
调用B类中的a()方法
调用B类中的b()方法
'''
__init__()构造方法
在创建类的时候,我们可以在类里面定义一个__init__()函数,这个函数是一个特殊的类示例方法,称为构造函数或者叫构造方法。
构造函数的作用和特点:
- 初始化对象,会在使用
类名()创建对象时,__init__会被自动调用。 - 第一个参数必须是
self,表示新建的实例本身。 - 接收定义传入该类的参数列表,如果需要往该类传入参数可以在
__init__()函数中定义参数。
方法的使用:
【示例1】在类中定义构造函数
class TestDemo:
def __init__(self):
print('正在运行__init__()构造函数...')
name = 'Jack'
age = 18
print(f'name: {name}, age: {age}')
TestDemo()
''' 运行结果如下
正在运行__init__()构造函数...
name: Jack, age: 18
'''
【示例2】在构造函数中定义传递的参数
class TestDemo:
def __init__(self, a, b):
print('正在运行__init__()构造函数...')
add = a + b
print('a + b =', add)
TestDemo(54, 18)
''' 运行结果如下
正在运行__init__()构造函数...
a + b = 72
'''
self参数
self代表的是类的实例对象本身。当你调用一个对象的方法时,Python会自动把对象本身作为第一个参数穿进去。所以self就是谁调用方法,self就指向谁。
在定义类的过程中,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将self参数作为方法的第一个参数。
【示例】代码如下:
class Demo:
def __init__(self):
self.name = 'Jack'
self.age = 18
def a(self):
print(f'name: {self.name}, age: {self.age}')
demo = Demo() # 创建一个对象指向Demo类
demo.a() # 调用对象中a()方法
''' 运行结果如下
name: Jack, age: 18
'''
补充说明:
demo.a()在调用时,Python会自动把demo传给self,而demo又等于Demo()这个对象,所以,这里的self就是指的Demo这个类本身。而在__init()__函数中使用self.变量名定义的变量可以供该类中的所有带有self参数的函数使用。
二、类进阶知识
Python封装
在面向对象编程中,封装就是“把数据和方法包装在一起,并通过权限控制对外隐藏内部实现细节。
封装的目的:
- 隐藏复杂性,只暴露必要接口。
- 降低耦合,保证对象内部的一致性和安全性。
- 给未来修改提供空间(即使内部逻辑改了,对外接口不变,外部代码也不用改)。
Python没有像Java和C++那样的public、private、protected关键字,它主要靠命名约定和名称改写(name mangling) 实现封装。
1.公开属性(public)
默认所有属性和方法都是公开的。
【示例】
class A:
def __init__(self):
self.name = 'tom' # 公共属性
a = A()
print('获取A类中的name变量:', a.name) # 直接访问
'''运行结果如下
获取A类中的name变量: tom
'''
2.受保护属性(protected)
约定俗成:用 单下划线_开头,表示内部使用,不建议外部访问。并不会真的禁止外部访问。
【示例】
class B:
def __init__(self, name, age):
self.name = name
self._age = age # 受保护属性
def get_age(self):
return self._age
b = B("Jack", 20)
print('直接获取b的中变量:', b._age) # 虽然能访问,但不推荐
print('通过返回获取b的中变量', b.get_age()) # 推荐通过方法访问
'''运行结果如下
直接获取b的中变量: 20
通过返回获取b的中变量 20
'''
3.私有属性(private)
使用用双下划线__开头,Python会触发名称改写(name mangling),变成 _类名__属性名,从而避免子类和外部直接访问。
【示例】
class C:
def __init__(self, name, salary):
self.name = name
self.__salary = salary # 私有属性
def show(self):
print(f"{self.name} 的工资是: {self.__salary}")
c = C("Jack", 10000)
c.show()
print(f'强行访问,{c.name} 的工资是:', c._C__salary) # 可以强行访问,但不推荐
print('---------------------- 直接方法结果 ----------------------')
print('\n', c.__salary) # 报错 AttributeError
'''运行结果如下
Jack 的工资是: 10000
强行访问,Jack 的工资是: 10000
---------------------- 直接方法结果 ----------------------
Traceback (most recent call last):
File "/Users/renjie/Desktop/Walking_Life/devops-tools/test/test.py", line 13, in <module>
print(c.__salary) # 报错 AttributeError
AttributeError: 'C' object has no attribute '__salary'. Did you mean: '_C__salary'?
'''
总结:属性或者方法名前面加上单下划线
_是赋予受保护属性,并不影响正常访问。加上双下划线是__私有属性不能被外部直接访问,但是可以按照规则使用_类名__属性名的方式可以强制访问。
Python继承
继承是面向对象编程 的一个核心特性,允许一个类(子类/派生类)基于另一个类(父类/基类)创建。
继承的使用
基础语法:
class 父类名:
# 父类定义
pass
class 子类名(父类名):
# 子类继承父类
pass
【示例】创建一个动物类,再创建一个Dog类,Dog继承动物类
class Animal:
def speak(self, sound):
print(sound)
class Dog(Animal):
def run(self):
print('running...')
Dog().speak('汪汪汪')
Dog().run()
'''运行结果如下
汪汪汪
running...
'''
Dog类继承了Animal类之后,Python解释器会先去Dog中找以call为名的方法,如果找不到,它还会自动去Animal类中找。我们就可以通过Dog来调用Animal类的方法。
子类调用父类方法(super)
可以使用super()方法在子类中调用父类的方法。
【示例】根据上面的代码进行更改,在子类中调用父类方法
class Animal:
def speak(self, sound):
print(sound)
class Dog(Animal):
def __init__(self):
super().speak('汪汪汪') # 调用父类的speak方法
self.run()
def run(self):
print('running...')
Dog()
'''运行结果如下
汪汪汪
running...
'''
方法重写(override)
子类可以覆盖父类的方法,调用时,会优先使用子类的方法。
【示例】代码如下:
class Animal:
def speak(self):
print("动物叫")
class Dog(Animal):
def speak(self): # 重写
print("汪汪汪")
Dog().speak()
'''运行结果如下
汪汪汪
'''
多态(polymorphism)
多态是指不同子类对象对同一个方法表现出不同的行为。
【示例】代码如下:
class Cat:
def speak(self):
print('喵喵喵')
class Dog:
def speak(self):
print('汪汪汪')
def make_sound(animal):
animal.speak()
make_sound(Dog())
make_sound(Cat())
'''运行结果如下
汪汪汪
喵喵喵
'''
多重继承
Python允许一个类继承多个父类。
【示例】代码如下:
class A:
def foo(self):
print("A.foo")
class B:
def foo(self):
print("B.foo")
class C(A, B): # 多重继承A和B
pass
c = C()
c.foo() # 遵循MRO顺序
'''运行结果如下
A.foo
'''
Python使用C3线性化算法 处理多重继承时方法查找的顺序,可以通过.mro()查看。
print(C.mro())
'''运行结果如下
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
'''
继承、重新与多态配合
继承实现代码重用与是一个is-a关系。子类可覆盖(override)父类方法。多态:不同类实现同名方法,可统一调用。
【示例】代码如下:
# 定义动物类
class Animal:
def speak(self):
raise NotImplementedError
# 定义Dog类继承Animal类并重写speak方法
class Dog(Animal):
def speak(self):
return '汪汪汪'
# 定义Cat类继承Animal类并重写speak方法
class Cat(Animal):
def speak(self):
return '喵喵喵'
# 定义公共方法,通过传入对象选择调用哪个类的speak方法
def let_speak(a: Animal):
print(a.speak())
let_speak(Dog()); let_speak(Cat())
'''运行结果如下
汪汪汪
喵喵喵
'''
类的构造思路
1.顺序执行方法
使用类我们可以定义一个类,然后通过该类完成一系列任务的形式,比如平时把一个连贯的任务放在一个类下,一旦需要完成这个连贯的动作都可以使用该类方法。
【示例】定义一个类模拟上学一天做的事儿
class Today:
def __init__(self, name, city, time):
self.name = name
self.city = city
self.time = time
def start(self):
print(f'{self.name}在{self.city}开始了新的一天.')
self.get_up()
def get_up(self):
print(f'{self.name}在{self.time}起床.')
self.wash_up()
def wash_up(self):
print(f'{self.name}正在洗漱.')
self.go_school()
def go_school(self):
print(f'{self.name}正在去上学.')
self.go_home()
def go_home(self):
print(f'{self.name}完成今天的学习正在回家.')
day = Today('Lucy', 'NewYork', '8:00')
day.start()
''' 运行结果如下
Lucy在NewYork开始了新的一天.
Lucy在8:00起床.
Lucy正在洗漱.
Lucy正在去上学.
Lucy完成今天的学习正在回家.
'''
补充说明:也可以把所有调用方法的代码都放到一个函数里,方便代码可读。
2.逐步调用方法
可以把相同类别的方法都放到同类下进行调用使用(比如:都会用到相同参数的任务类,或者都是操作同一事物的方法)。
【示例】模拟完成各个学科的作业
class Homework:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def chinese(self):
print(f'{self.grade}年级学生{self.name}完成了语文作业.')
def math(self):
print(f'{self.grade}年级学生{self.name}完成了数学作业.')
def english(self):
print(f'{self.grade}年级学生{self.name}完成了英语作业.')
# 创建对象传入参数 name 和 grade
homework = Homework('Jack', 3)
homework.math() # 调用math方法
homework.english() # 调用english方法
homework.chinese() # 调用chinese方法
''' 运行结果如下
3年级学生Jack完成了数学作业.
3年级学生Jack完成了英语作业.
3年级学生Jack完成了语文作业.
'''