面向对象基础

面向对象(OOP)这种烂大街东西我觉得应该没什么必要再赘述了,就简单地搬运一下菜鸟教程里面的说明

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法: 类中定义的函数。
  • 类变量: 类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员: 类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写: 如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量: 定义在方法中的变量,只作用于当前实例的类。
  • 实例变量: 在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 继承: 即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个 Dog 类型的对象派生自 Animal 类,这是模拟 “是一个(is-a)” 关系(例如,Dog 是一个 Animal)。
  • 实例化: 创建一个类的实例,类的具体对象。
  • 对象: 通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

杭助的 Guide 也可以看一下,OOP 是一种思想,很多语言都有支持


初尝类与实例

创建一个简单的类

先讲一下基本的模板

1
2
3
4
5
6
class ClassName:  # 类名的首字母要大写,这是约定,然后因为这里没有继承(从父类创建新类),所以后面没有括号
<statement-1> # --第 1 条描述--
. # 类定义里面可以放一堆的属性(类中的变量)、方法(附着在这个类上的专属函数)
. # 属性可以直接定义,但是如果这样的话不够优雅
. # 比较常见的做法是在 __init__() 方法中定义,这样方便在创建的时候个性化每个实例的属性
<statement-N> # --第 N 条描述--

下面创建一个 Dog 类,它有名字和年龄两个属性,还有蹲下和打滚两个方法

1
2
3
4
5
6
7
8
9
10
11
class Dog:

def __init__(self, name, age): # 初始化属性
self.name = name
self.age = age

def sit(self): # 模拟蹲下
print(f"{self.name} is now sitting.")

def roll_over(self): # 模拟打滚
print(f"{self.name} rolled over!")

__init__() 是一个特殊方法,它会在每次实例化的时候自动执行,它的第一个参数指代实例本身 (不一定是 self ,但是一般都用这个词),然后你就可以使用 self.name 来操纵实例内的 name 属性

下面的两个方法为了简单都只有打印语句的功能,和上面的方法一样,必须在开头多一个参数,来表示实例本身(就和 __init__() 一样),这是类中的方法与普通函数的一个特殊的区别

通过类创建和使用实例

访问属性

下面先来创建一个实例

1
2
3
4
my_dog = Dog("Rex", 6)

print(f"My dog's name is {my_dog.name}.") # My dog's name is Rex.
print(f"My dog is {my_dog.age} years old.") # My dog is 6 years old.

首先,通过 Dog() 来自动调用 __init__() 方法创建了一个 Dog 的实例 my_dog ,它的名称是 Rex ,年龄是 6

接下来,你就可以使用诸如 my_dog.name 的方式来访问实例中的属性了

调用方法

实例化一个类之后,你便可以调用其中的方法

比如命令小狗下蹲和打滚

1
2
my_dog.sit()        # Rex is now sitting.
my_dog.roll_over() # Rex rolled over!

创建多个实例

一旦定义好了类,创建多少个实例都可以

1
2
3
4
5
6
7
8
9
10
my_dog = Dog("Rex", 6)
your_dog = Dog("Lucy", 3)

print(f"My dog's name is {my_dog.name}.") # My dog's name is Rex.
print(f"My dog is {my_dog.age} years old.") # My dog is 6 years old.
my_dog.sit() # Rex is now sitting.

print(f"\nYour dog's name is {your_dog.name}.") # Your dog's name is Lucy.
print(f"Your dog is {your_dog.age} years old.") # Your dog is 3 years old.
your_dog.sit() # Lucy is now sitting.

稍微深入

属性默认值

下面来编写一个表示汽车的类,它存储了有关汽车的信息,还有返回基本信息与打印里程的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Car:
def __init__(self, name, model, year): # 初始化对象属性
self.name = name
self.model = model
self.year = year
self.odometer_reading = 0 # 设置汽车里程数的默认值为 0

def get_descriptive_name(self): # 返回整洁的描述性信息
long_name = str(self.year) + ' ' + self.name + ' ' + self.model
return long_name.title()

def read_odometer(self): # 打印一条消息,指出汽车里程
print("This car has " + str(self.odometer_reading) + " miles on it.")


my_new_car = Car('audi', 'a4', 2016) # 实例化

print(my_new_car.get_descriptive_name()) # 2016 Audi A4
my_new_car.read_odometer() # This car has 0 miles on it.

在这里设置了属性的默认值 self.odometer_reading = 0 ,它表示汽车的初始里程为 0

修改属性的值

属性的值毋庸置疑是可以修改的,下面来看几个例子,以修改汽车的里程为例

直接手动修改

最为简单粗暴的方法

1
2
my_new_car.odometer_reading = 23 # 手动修改实例的属性
my_new_car.read_odometer() # This car has 23 miles on it.

通过方法更新新值

新加一个方法,用于修改里程

1
2
def update_odometer(self, mileage): # 用于修改里程数
self.odometer_reading = mileage

然后你可以这样使用它,这种方法明显更加优雅

1
my_new_car.update_odometer(23) # 修改实例的属性

当然,你还可以扩展一下,增加一些逻辑,比如禁止将里程回滚

1
2
3
4
5
def update_odometer(self, mileage): # 用于修改里程数
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")

还可以更优雅嘛?可以的

1
2
def increment_odometer(self, miles): # 用于增加里程数
self.odometer_reading += miles

使用这个方法,可以只给出里程的增量,然后这个方法会自动求值并修改属性


继承与重写

初尝继承

在编写类时,并非总要从空白开始,如果要编写的类是另一个现成类的特殊版本,可以使用继承

原有的类被称为父类或基类,而新类被称为子类或派生类,基本的格式如下

1
2
3
4
5
6
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>

子类与父类必须定义在一个作用域内,如果父类在另一个模块内,可以这样写

1
class DerivedClassName(modname.BaseClassName):

现在来尝试一下,从汽车类( Car )的基础上创建一个新的电动车类( ElectricCar ),并且暂时不添加新的属性与方法

1
2
3
class ElectricCar(Car):
def __init__(self, name, model, year):
super().__init__(name, model, year) # 没有 self

这里的 super() 为调用父类,你也可以选择下面这种写法,但是没有前者规范

1
Car.__init__(self, name, model, year) # 有 self

现在测试一下有没有问题,能否使用父类的方法

1
2
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name()) # 2019 Tesla Model S

没有问题,下面可以接着添加子类的特有属性和方法了


下面添加一个电动车特有的属性(电瓶容量),以及一个查看容量的方法

1
2
3
4
5
6
7
8
9
class ElectricCar(Car):
def __init__(self, name, model, year):
super().__init__(name, model, year)
self.battery_size = 70 # 看这里

def describe_battery(self):
print("This car has a " + str(self.battery_size) + "-kWh battery.")

print(my_tesla.battery_size) # 70,成功返回

正如你所见,现在的子类已经能实现父类没有的功能了


还可以更优雅嘛?当然可以

当使用代码模拟实物的时候,你会发现要添加的细节越来越多,属性和方法以及文件会越来越长。在这种情况下,可以将类的一部分提取出来,作为一个单独的类

例如,在不断给 ElectricCar 添加细节的时候,有关电瓶的内容会越来越多,现在先尝试把这些内容提取出来,放到 Battery 类中,然后再将 Battery 作为 ElectricCar 的一个属性

1
2
3
4
5
6
7
8
9
10
11
12
class Battery:
def __init__(self, battery_size=70):
self.battery_size = battery_size

def describe_battery(self):
print("This car has a " + str(self.battery_size) + "-kWh battery.")


class ElectricCar(Car):
def __init__(self, name, model, year):
super().__init__(name, model, year)
self.battery = Battery()

然后可以这样使用

1
2
my_tesla = ElectricCar('tesla', 'model s', 2019)
my_tesla.battery.describe_battery() # This car has a 70-kWh battery.

方法重写

如果父类的某个方法不能满足子类的要求,子类中可以重新定义该方法,父类中的将被覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
class Parent:        # 定义父类
def myMethod(self):
print('调用父类方法')


class Child(Parent): # 定义子类
def myMethod(self):
print('调用子类方法')


c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child, c).myMethod() # 用子类对象调用父类已被覆盖的方法

结果为

1
2
调用子类方法
调用父类方法

多继承

Python 支持多继承形式,多继承的类定义形如下

1
2
3
4
5
6
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>

需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python 从左至右搜索

即方法在子类中未找到时,从左到右查找父类中是否包含方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))

#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))

#另一个类,多重继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))

#多重继承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)

test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法

执行以上程序输出结果为:

1
我叫 Tim,我是一个演说家,我演讲的主题是 Python

下划线的含义

私有与公有

这里可以康下面两个链接

运算符重载

Python 同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b

def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)

def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

以上代码执行结果如下所示:

1
Vector(7,8)