面向对象程序设计

面向对象不仅是一种语言特性,更是一种程序设计思想。在C++中已经研究了很多面向对象的思想了,所以这篇文章的学习中更注重的是”Python中的面向对象”。

面向对象的三大核心内容是封装、继承与多态。封装就是使用类,继承与多态也要使用类来实现。

类与封装

Python中定义一个类的方式是:

1
2
3
class ClassName:
属性
方法

使用类可以实例化很多个对象或实例:instance_name=ClassName()

两种对象与两种属性

两种对象

Python中一切皆对象——类其实也是对象。创建类其实就是创建了一个类对象,它由type类实例化而来:

Untitled

使用类实例化的对象或实例也是对象,它是对应的类类型的对象,这点很好理解。

Untitled

类对象只有一个,而使用类实例化的实例对象可以有很多个。

两种属性

Python中与类相关的有类对象与实例对象,对应的,类中的属性有类属性与实例属性。

类属性就是在类的公共部分定义的属性,类对象和实例对象都能访问到类属性;实例属性在方法(实例方法)中定义,前面使用self加以限定,表明这是一个实例属性:

1
2
3
4
5
class Student:
amount=100 # 类属性
def __init__(self,name='Sato',age=18):
self.name=name # 实例属性
self.age=age

为什么Python要分类属性和实例属性,而不像C++一样直接用一个属性?主要目的还是为了更好地模拟现实世界:类属性可以设置为一个群体的属性,如班里学生的数量;实例属性则设置为个体的属性,如学生的姓名、年龄等。

  1. 对于类对象和每个实例对象,类属性是公用的。改一个其他都会变。

    1
    2
    3
    4
    5
    # 通过类对象修改类属性,实例对象再访问类属性就会发生变化
    std1=Student(name='Sato')
    print(std1.amount) # 输出100
    Student.amount+=1
    print(std1.amount) # 输出101
  2. 类对象访问不到实例属性,且对于不同的实例,实例属性不互通。

    1
    2
    3
    4
    # 两个实例对应的实例属性不互通
    std1=Student(name='Sato')
    std2=Student(name='Prexius')
    print(std1.name+' VS '+std2.name)
  3. 使用实例对象.属性可以修改或添加实例属性——如果存在这个实例属性,那么就修改;如果不存在,那么就添加这个实例属性。需要注意如果实例属性和类属性重名,那么实例属性会覆盖掉类属性。只有删除这个实例属性才能恢复对类属性的访问。

    在第一点中没有通过实例对象来修改实例属性正是因为这个原因:

    1
    2
    3
    4
    std1=Student(name='Sato')
    std1.amount+=1
    print(Student.amount) # 依然输出100
    print(std1.amount) # 输出101

    std1.amount相当于添加了一个名为amount的实例属性覆盖掉了原来的类属性,所以二者不再互通了。

    要验证通过实例对象也能修改类属性,需要使用类方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Student:
    amount=100 # 类属性
    def __init__(self,name='Sato',age=18):
    self.name=name # 实例属性
    self.age=age
    @classmethod
    def add(cls): cls.amount += 1

    std1=Student(name='Sato')
    std1.add()
    print(Student.amount) # 输出101
    print(std1.amount) # 输出101

一切皆对象

如何理解python中一切皆对象呢?看type,object,class的关系

Untitled

Python中一切皆对象。如这些常用类型和类一样也是type的实例。

有这样一个很好的例子:(__base__代表基类)

1
2
3
4
5
6
7
8
9
class A:pass
a = A()

print(A.__bases__) # (<class 'object'>,)
print(object.__bases__) # ()
print(type(a)) # <class '__main__.A'>
print(type(A)) # <class 'type'>
print(type(object)) # <class 'type'>
print(type.__bases__) # (<class 'object'>,)

可以推出原型链和继承链:

Untitled

结论是所有对象都是type的实例,所有对象都是继承于object的子类。

三种方法

类中可以有三种:实例方法、类方法和静态方法

实例方法

类中直接定义的方法就是实例方法,它有一个默认的形参self指向调用这方法的实例,类对象不能调用实例方法。

1
2
3
4
5
6
7
8
9
10
11
12
class Student:
__amount = 100 # 类属性
def __init__(self, name='Sato', age=18):
self.name = name # 实例属性
self.age = age
def get_amount(self): # 实例方法
return self.__amount

std = Student()
print(std.get_amount()) # 实例对象调用实例方法
try: Student.get_amount()
except: print("类对象不能调用实例方法")

类方法

类方法使用@classmethod修饰符加以限定,类方法有一个默认的形参cls,指向类对象本身。所以类对象和实例对象都可以调用类方法,实例方法主要使用类方法来修改类属性的值。

1
2
3
4
5
6
7
8
9
10
11
12
class Student:
amount=100 # 类属性
def __init__(self,name='Sato',age=18):
self.name=name # 实例属性
self.age=age
@classmethod
def add(cls): cls.amount += 1

std1=Student(name='Sato')
std1.add()
print(Student.amount) # 输出101
print(std1.amount) # 输出101

静态方法

静态方法没有cls,也没有self。它和类外面的函数的区别是它可以访问到类中的私有属性,但必须使用类名.类属性的方式。类对象和实例对象都可以调用。

1
2
3
4
5
6
7
8
9
10
11
class Student:
__amount = 100 # 类属性
def __init__(self, name='Sato', age=18):
self.name = name # 实例属性
self.age = age
@staticmethod
def get_amount():
return Student.__amount
print(Student.get_amount())
std = Student()
print(std.get_amount())

[Python]实例方法、类方法、静态方法

构造与析构

Python的构造函数是__init__,析构函数是__del__。分别用于初始化对象时赋值和清除对象的空间。

1
2
3
4
5
6
7
class Student:
def __init__(self):
print("执行了构造函数")
def __del__(self):
print("执行了析构函数")
std = Student() # 执行了构造函数
del std # 执行了析构函数

此外还有一个__new__方法,它的作用是创建并返回对象。与__init__相比,__new__完成的是初始化的工作,而__init__完成的是初始化后的赋值工作。

继承与派生

继承

一个类的默认父类是object,可以通过__base__来验证。

所以说,前面定义Student类写完整应该如下:

1
2
class Student(object):
pass

将父类放在括号里面就行了。

继承、派生、组合、多态与多态性

如果父类有构造函数,子类也有构造函数,那么需要在子类的构造函数中手动调用父类的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
class People(object):
def __init__(self,name,age):
self.name=name
self.age=age

class Student(People):
__amount=100
def __init__(self,name,age,std_id):
People.__init__(self,name,age) # 调用父类的构造函数
self.std_id=std_id

std1=Student('Sato',20,'202001')

派生

可以在子类中重写父类方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class People(object):
def __init__(self,name,age):
self.name=name
self.age=age
def identity(self):
print("我是人")

class Student(People):
__amount=100

def __init__(self,name,age,std_id):
People.__init__(self,name,age)
self.std_id=std_id
def identity(self):
print("我是学生")

std1=Student('Sato',20,'202001')
std1.identity() # 我是学生