Python 入门笔记(十一)面向对象
面向对象基础
面向对象(OOP)这种烂大街东西我觉得应该没什么必要再赘述了,就简单地搬运一下菜鸟教程里面的说明
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 方法: 类中定义的函数。
- 类变量: 类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员: 类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写: 如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量: 定义在方法中的变量,只作用于当前实例的类。
- 实例变量: 在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
- 继承: 即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个 Dog 类型的对象派生自 Animal 类,这是模拟 “是一个(is-a)” 关系(例如,Dog 是一个 Animal)。
- 实例化: 创建一个类的实例,类的具体对象。
- 对象: 通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
杭助的 Guide 也可以看一下,OOP 是一种思想,很多语言都有支持
初尝类与实例
创建一个简单的类
先讲一下基本的模板
1 | class ClassName: # 类名的首字母要大写,这是约定,然后因为这里没有继承(从父类创建新类),所以后面没有括号 |
下面创建一个 Dog
类,它有名字和年龄两个属性,还有蹲下和打滚两个方法
1 | class Dog: |
__init__()
是一个特殊方法,它会在每次实例化的时候自动执行,它的第一个参数指代实例本身 (不一定是 self
,但是一般都用这个词),然后你就可以使用 self.name
来操纵实例内的 name
属性
下面的两个方法为了简单都只有打印语句的功能,和上面的方法一样,必须在开头多一个参数,来表示实例本身(就和 __init__()
一样),这是类中的方法与普通函数的一个特殊的区别
通过类创建和使用实例
访问属性
下面先来创建一个实例
1 | my_dog = Dog("Rex", 6) |
首先,通过 Dog()
来自动调用 __init__()
方法创建了一个 Dog
的实例 my_dog
,它的名称是 Rex
,年龄是 6
接下来,你就可以使用诸如 my_dog.name
的方式来访问实例中的属性了
调用方法
实例化一个类之后,你便可以调用其中的方法
比如命令小狗下蹲和打滚
1 | my_dog.sit() # Rex is now sitting. |
创建多个实例
一旦定义好了类,创建多少个实例都可以
1 | my_dog = Dog("Rex", 6) |
稍微深入
属性默认值
下面来编写一个表示汽车的类,它存储了有关汽车的信息,还有返回基本信息与打印里程的方法
1 | class Car: |
在这里设置了属性的默认值 self.odometer_reading = 0
,它表示汽车的初始里程为 0
修改属性的值
属性的值毋庸置疑是可以修改的,下面来看几个例子,以修改汽车的里程为例
直接手动修改
最为简单粗暴的方法
1 | my_new_car.odometer_reading = 23 # 手动修改实例的属性 |
通过方法更新新值
新加一个方法,用于修改里程
1 | def update_odometer(self, mileage): # 用于修改里程数 |
然后你可以这样使用它,这种方法明显更加优雅
1 | my_new_car.update_odometer(23) # 修改实例的属性 |
当然,你还可以扩展一下,增加一些逻辑,比如禁止将里程回滚
1 | def update_odometer(self, mileage): # 用于修改里程数 |
还可以更优雅嘛?可以的
1 | def increment_odometer(self, miles): # 用于增加里程数 |
使用这个方法,可以只给出里程的增量,然后这个方法会自动求值并修改属性
继承与重写
初尝继承
在编写类时,并非总要从空白开始,如果要编写的类是另一个现成类的特殊版本,可以使用继承
原有的类被称为父类或基类,而新类被称为子类或派生类,基本的格式如下
1 | class DerivedClassName(BaseClassName): |
子类与父类必须定义在一个作用域内,如果父类在另一个模块内,可以这样写
1 | class DerivedClassName(modname.BaseClassName): |
现在来尝试一下,从汽车类( Car
)的基础上创建一个新的电动车类( ElectricCar
),并且暂时不添加新的属性与方法
1 | class ElectricCar(Car): |
这里的 super()
为调用父类,你也可以选择下面这种写法,但是没有前者规范
1 | Car.__init__(self, name, model, year) # 有 self |
现在测试一下有没有问题,能否使用父类的方法
1 | my_tesla = ElectricCar('tesla', 'model s', 2019) |
没有问题,下面可以接着添加子类的特有属性和方法了
下面添加一个电动车特有的属性(电瓶容量),以及一个查看容量的方法
1 | class ElectricCar(Car): |
正如你所见,现在的子类已经能实现父类没有的功能了
还可以更优雅嘛?当然可以
当使用代码模拟实物的时候,你会发现要添加的细节越来越多,属性和方法以及文件会越来越长。在这种情况下,可以将类的一部分提取出来,作为一个单独的类
例如,在不断给 ElectricCar
添加细节的时候,有关电瓶的内容会越来越多,现在先尝试把这些内容提取出来,放到 Battery
类中,然后再将 Battery
作为 ElectricCar
的一个属性
1 | class Battery: |
然后可以这样使用
1 | my_tesla = ElectricCar('tesla', 'model s', 2019) |
方法重写
如果父类的某个方法不能满足子类的要求,子类中可以重新定义该方法,父类中的将被覆盖
1 | class Parent: # 定义父类 |
结果为
1 | 调用子类方法 |
多继承
Python 支持多继承形式,多继承的类定义形如下
1 | class DerivedClassName(Base1, Base2, Base3): |
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python 从左至右搜索
即方法在子类中未找到时,从左到右查找父类中是否包含方法
1 | #类定义 |
执行以上程序输出结果为:
1 | 我叫 Tim,我是一个演说家,我演讲的主题是 Python |
下划线的含义
私有与公有
这里可以康下面两个链接
运算符重载
Python 同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下
1 | class Vector: |
以上代码执行结果如下所示:
1 | Vector(7,8) |