Python基础-10.类

TOC

一、类的基础

面向对象的概念

面向对象是用“对象”来组织代码,把“数据”和“操作数据的方法”放在一起,并通过对象之间的关系来解决问题。

对象的核心概念:

  • 对象:现实世界事物在程序里的抽象,这个对象的范围可大也可小,可以是某个领域或者生物物种(比如:地球、食物、宠物等),也能是某个事物(比如:某个人、某只猫或者狗等),一切皆对象
  • :描述一类对象的“模版”,定义对象有什么属性和方法。
  • 封装:把数据和功能打包在对象里,对外提供简单接口,内部细节可隐藏。
  • 继承:子类可以复用父类的功能,也可以扩展或重写。
  • 多态:不同对象实现相同方法接口时,可以被统一使用(比如:狗的叫声是"汪汪汪"、猫的叫声是"喵喵喵")。

可以想象我们在游戏中设计一个狗的角色,我们可以通过以下构思来实现这个角色的对象特征:

  • 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完成了语文作业.
'''