详解抽象之类
相较于函数,类则是更高级别的抽象结构,类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础。类是一种用户定义类型,也称类类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象,类的实质是一种数据类型。
类的定义
与其它面向对象编程语言类似,在 Python 中,类具有多态、封装、继承。不过,Python 中没有重载,类的定义细节也具有明显差异。定义类的一般形式如下:
class ClassName:
<statement-1>
.
.
.
<statement-N>
上面提到,类的本质是一种数据结构,一个类通常包含数据成员和函数成员。数据成员用于刻画类所描述的一类事物的属性,如描述人,一般用姓名、年龄、性别、学历等属性进行刻画,这就是数据成员;函数成员用于完成具体的任务,如查询、设置人名、打印基本信息等。如下实例:
#定义一个简单的类,描述一个人的基本信息
classPerson:
#定义类的数据成员:姓名,年龄
name=''
age=0
#定义一个函数:打印类实例的基本信息
defprintPersonInfo(self):
print('person-info:{name:%s, age:%d}'%(self.name,self.age))
#定义一个简单的函数
defhello(self):
print("hello world!")
#实例化,创建一个对象
p1 = Person()
#访问类的属性:数据成员,访问语法obj.X
print("name:",p1.name)
print("age:",p1.age)
#访问类的函数
p1.printPersonInfo()
p1.hello()
执行结果:
name:
age: 0
person-info:{name:, age:0}
helloworld!
self 参数
上述实例中,Person 类定义了两个函数,其定义形式与上一篇介绍的函数存在明显区别:类中的函数必须有一个额外的参数 self,并且 self 参数必须放在第一个参数的位置。
那么,对于一个实例化的对象,self 参数代表什么呢?来看一个例子。
#定义一个简单的类,描述一个人的基本信息
classPerson:
#定义类的数据成员:姓名,年龄
name=''
age=0
#定义一个函数:打印类实例的基本信息
defprintPersonInfo(self):
print('name:',self.name)
print('self:',self)
print('self class:',self.__class__)
#实例化,创建一个对象
p1 = Person()
#访问类的函数
p1.printPersonInfo()
执行结果:
name:
self:<__main__.Person object at 0x00000000067B5F98>
selfclass:
从执行结果可以看出,self 的内容是一个地址,它代表当前实例,也就是当前对象的地址。需要说明的是,self 参数并不是 Python 的保留关键字,而是为了便于理解,按照惯例命名而来。事实上,换做其它名字也可以(须遵循规则:必须是类函数的第一个参数)。
实例化
上面小节实例中,我们创建了一个 Person 类的对象:p1=Person(),通过对象可以访问类的属性和调用类的函数,语法形式为:obj.name,其中 name 代表类的属性名或函数名。
上述例子中存在一个疑点,不知读者是否注意到,例子中实例化对象的操作并不是显式调用构造函数完成的,如下代码:
p1= Person()
类中并没有定义名为 Person() 的函数,Person 是类名,在进行实例化创建对象的时候,会自动调用__init()__函数。该函数用于创建对象,并赋予所创建对象初始状态。
上述例子中,做了很多简化,创建的对象的所有属性都是默认值,在实际应用中,通常会采取更有效的方式来赋予对象初始状态。如下实例:
#定义一个简单的类,描述一个人的基本信息
classPerson:
#定义类的数据成员:姓名,年龄
name=''
age=0
#定义构造函数,用于创建一个类实例,也就是类的具体对象
#通过参数传递,可以赋予对象初始状态
def__init__(self,name,age):
self.name = name
self.age = age
#定义一个函数:打印类实例的基本信息
defprintPersonInfo(self):
print('person-info:{name:%s, age:%d}'%(self.name,self.age))
#实例化,创建两个对象,默认调用构造函数:__init__()
p1 = Person("Zhang San",12)
p2 = Person("Li Si",13)
#访问类的属性:数据成员,访问语法obj.X
print("name:",p1.name)
print("age:",p1.age)
#调用函数
p1.printPersonInfo()
p2.printPersonInfo()
运行结果:
name: Zhang San
age: 12
person-info:{name:Zhang San, age:12}
person-info:{name:Li Si, age:13}
Python 中类定义与 Java、C++ 的差别
从上面的例子中可以发现,Python 中类的定义与 Java 和 C++ 的区别:
定义形式,Python 没有修饰符,只有关键词 class,Java 和 C++ 则有修饰符(非必须);
构造函数,Python 没有重载特性,只能定义一个构造函数,且函数名为__init__,若不定义构造函数,则默认为__init__(self),Java、C++ 则具有重载特性,可定义多个构造函数,且构造函数名必须与类名一致;
形参定义形式不同,Python 类的方法,self 参数为必须参数;
不必声明域,上面的例子中,声明了域 (name,age),事实上,Python 可以不声明域,例子如下
#定义一个简单的类,描述一个人的基本信息
classPerson:
#定义构造函数,用于创建一个类实例,也就是类的具体对象
def__init__(self,name,age):
self.name = name
self.age = age
#定义一个函数:打印类实例的基本信息
defprintPersonInfo(self):
print('person-info:{name:%s, age:%d}'%(self.name,self.age))
继承
继承可谓一种带有褒义的懒惰行为,一个最直观的好处就是减少编写重复代码,通过继承,子类可以重用父类中的函数和数据成员。当然,继承的意义远不止于此,这里就不展开了。
关于继承,通常将实施继承行为的类称为子类(Child Class)或者派生类(Derived Class),被继承的类称为父类(Parent Class)或者基类(Base Class)。与 Java、C++ 相比,Python 中继承的一般形式颇为简洁:
classchildClassName(parentClassName):
.
.
.
下面结合实例来看一下,定义一个类 Occupation 和一个继承 Occupation 的类 Person,继承的定义形式为:Person(Occupation),无需关键词声明。
#定义一个类Occupation,描述职业
classOccupation:
#定义构造函数
def__init__(self,salary,industry):
self.salary = salary
self.industry = industry
defprintOccupationInfo(self):
print('Occupation-info:{salary:%d, industry:%s}'%(self.salary,self.industry))
#定义一个简单的类Person,继承自类Occupation
classPerson(Occupation):
def__init__(self,name,age):
self.name = name
self.age = age
#定义一个函数:打印类实例的基本信息
defprintPersonInfo(self):
print('person-info:{name:%s, age:%d}'%(self.name,self.age))
#创建一个子类对象
temp = Person('Wu-Jing',38)
#访问父类的数据成员
temp.salary = 21000
temp.industry = "IT"
#分别调用本身和父类的函数
temp.printOccupationInfo()
temp.printPersonInfo()
执行结果:
Occupation-info:{salary:21000, industry:IT}
person-info:{name:Wu-Jing, age:38}
多继承
一些场景下,一个子类可能需要继承多个父类,
举个例子:有三个类分别描述职
业信息,购物信息,银行账户信息,现在定义一个类
Person来描述一个人,
显然,Person涉及上述三个类的信息,为了重复利用
代码,降低开发难度,可以直接继承上述三个类,这便是多继承的应用。
如上所述,
多继承定义形式如下:
classchildClassName(parentClassName1,parentClassName2,…):
.
.
.
关于多继承,实例如下:
#定义一个类BankAccount,描述银行账户
classBankAccount:
def__init__(self,number, balance):
self.number = number
self.balance = balance
#计算并返回年利息
defgetAnnualInterest(self):
returnself.balance*0.042
#定义一个类Occupation,描述职业
classOccupation:
def__init__(self,salary,industry):
self.salary = salary
self.industry = industry
defprintOccupationInfo(self):
print('Occupation-info:{salary:%d, industry:%s}'%(self.salary,self.industry))
#定义一个类Person,继承自类BankAccount和BankAccount
classPerson(Occupation,BankAccount):
def__init__(self,name,age):
self.name = name
self.age = age
#定义一个函数:打印类实例的基本信息
defprintPersonInfo(self):
print('person-info:{name:%s, age:%d}'%(self.name,self.age))
#创建一个子类对象
temp = Person('Wu-Jing',38)
#访问父类数据成员
temp.number = 622202050201
temp.balance = 1000000.99
temp.salary = 21000
temp.industry = "IT"
#分别调用本身和父类的函数
temp.printOccupationInfo()
temp.printPersonInfo()
print('Annual interest:',temp.getAnnualInterest())
执行结果:
Occupation-info:{salary:21000, industry:IT}
person-info:{name:Wu-Jing, age:38}
Annualinterest: 42000.041580000005
需要注意的是,多继承中,子类继承了不同父类中的属性和函数,这些属性和函数可能存在同名的情况,在子类使用这些同名的函数或属性时,在没有指定的情况下,Python 将根据一定顺序进行搜索:首先搜索子类,如果未找到则根据多继承定义的顺序,从左至右在父类中查找。
如下实例:
#定义一个类 BankAccount,描述银行账户
classBankAccount:
defprintInfo(self):
print('BankAccount-info')
#定义一个类 Occupation,描述职业
classOccupation:
defprintInfo(self):
print('Occupation-info')
#定义一个类 Person,继承自类 BankAccount 和 BankAccount
classPerson(Occupation,BankAccount):
def__init__(self,name,age):
self.name = name
self.age = age
defprintPersonInfo(self):
print('person-info')
#创建一个子类对象
temp = Person('Wu-Jing',38)
#调用父类中的函数
temp.printInfo()
执行结果:
Occupation-info
很明显,根据定义顺序,优先调用父类 Occupation 中的 printInfo()。
函数的重写
一些场景下,从父类继承来的函数并不能完全满足需求,需要在子类中对其进行修改,这就是重写的概念:在子类中重写父类中的函数,当子类对象调用该名称的函数时,会调用子类中重写的函数,父类中的同名函数将被覆盖。
实例如下:
#定义一个类Occupation,描述职业
classOccupation:
#定义构造函数
def__init__(self,salary,industry):
self.salary = salary
self.industry = industry
defprintInfo(self):
print('salary:%d, industry:%s}'%(self.salary,self.industry))
#定义一个简单的类Person,继承自类Occupation
classPerson(Occupation):
def__init__(self,name,age):
self.name = name
self.age = age
#定义一个函数:打印类实例的基本信息
defprintInfo(self):
print('name:%s, age:%d'%(self.name,self.age))
print('salary:%d, industry:%s'%(self.salary,self.industry))
#创建一个子类对象
temp = Person('Wu-Jing',38)
#访问父类的数据成员
temp.salary = 21000
temp.industry = "IT"
#分别调用函数printInfo()
temp.printInfo()
执行结果:
name:Wu-Jing, age:38
salary:21000, industry:IT
私有属性与私有方法
前面几节的实例中,类的属性和函数都是“公有”的,可以通过类对象直接访问。
但是,在某些场景下,我们并不希望对外暴露类的内
部细节,为了限制外部访问,我们可以将对应的属性和函数设置为私有。
将类的属性和函数设置为私有的一般形
式为以下两种。
1.定义私有属性
__attribute:属性名前面加两个下划线,
即声明该属性为私有,不能在类的外部直接访问,
在类内部访问时用
self.__attribute。
2.定义私有函数
__function:函数名前面加两个下划线,即声明该函数为私有,不能在类的外部直
接访问,在类内部访问时用 self.__ function。
实例如下:
#定义一个简单的类,描述一个人的基本信息
classPerson:
#定义两个私有属性name,age
def__init__(self,name,age):
self.__name = name
self.__age = age
#定义公有函数,在类外部可以访问
defgetName(self):
self.__fun()
returnself.__name
defgetAge(self):
returnself.__age
#定义一个私有函数,只能在类内部使用
def__fun(self):
print('hello')
#实例化
p1 = Person("Zhang San",12)
#访问类的私有属性和私有函数,将会报错
print("name:",p1.__age)
print("age:",p1.__name)
p1.__fun()
对于私有属性和私有函数,如果需要在类外访问,可以通过公有函数实现,这与
Java 和 C++ 是一致的。
如下实例:
#定义一个简单的类,描述一个人的基本信息
classPerson:
#定义两个私有属性name,age
def__init__(self,name,age):
self.__name = name
self.__age = age
#定义公有函数,在类外部可以访问
defgetName(self):
self.__fun()
returnself.__name
defgetAge(self):
returnself.__age
#定义一个私有函数,只能在类内部使用
def__fun(self):
print('hello')
#实例化
p1 = Person("Zhang San",12)
#访问类的公有函数
print("name:",p1.getName())
执行结果:
hello
name: Zhang San