Python 面向对象编程
类和对象
定义类
Python支持面向对象编程,下面是一个例子。我们可以看到,在Python中声明类和其他语言差不多。不过实际上差别还是挺大的。
首先,Python没有严格意义上的构造函数,只有一个__init__(self,XXX)
函数,该函数和构造函数的功能差不多,用来初始化对象的状态。之后创建对象的时候,直接使用类名和参数列表来创建,这样会调用初始化函数来创建对象。
特别要提一点,所有的Python类的实例函数的第一个参数必须是self
,这个参数类似于Java的this
关键字,指代当前对象。如果我们调用类上的方法a.f()
,那么a
这个实例就会传递给self
参数。
下面介绍一下Python的实例字段。实例字段使用self.XXX
来定义。Python不能像Java那样静态的声明字段。如果有需要,直接使用self.
加字段名即可,这也是动态语言的一个特点。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f'Person(name:{self.name},age:{self.age})'
person=Person('yitian',24)
print(person)
上面这个例子还定义了一个__str__(self)
函数,这个函数和Java的toString()
函数类似,当输出的时候就会自动调用这个函数将对象转换为可读的字符串形式。Python中还有很多__XXX__
形式的惯例函数,肩负着不同的职责。
共享字段和私有字段
首先需要说明,Python中没有public
、private
这样的字段访问修饰符,也就是说只要你想,你可以调用对象上的任意字段和方法。这里说的都只是一种编码的契约,我们在编写Python类的时候也要遵循这些契约,才能写出合格的代码来。
前面已经说了,实例字段使用self.
来访问。如果在类中编写没有self
的变量,那么这些变量就是类变量,可以在该类的所有对象之间共享。这个概念类似Java的静态字段。下面的population
就是一个共享字段的例子。
class Person:
population = 0
def __init__(self, name, age):
self.name = name
self.age = age
Person.population += 1
def __str__(self):
return f'Person(name:{self.name},age:{self.age})'
yitian = Person('yitian', 24)
zhang3 = Person('zhang3', 25)
print(yitian)
print(f'population:{Person.population}')
最后来说说私有字段。私有字段惯例上需要添加下划线_
前缀。虽然这些“私有变量”也可以在类外边访问,但是我们千万不要这么做。私有字段作为类的内部实现,随时可能存在变化的可能,不应该向外部暴露。我们的代码中也不应该依赖其他类库的私有变量。
结构体
有时候我们可能需要结构体或者数据类这一概念,也就是将相关的变量封装到一个类中。在Python中可以定义一个空类,然后创建对象,并动态赋值。
print('--------------结构体--------------')
class StudentInfo:
pass
info = StudentInfo()
info.name = 'yitian'
info.age = 24
info.gender = 'male'
print(f'info({info.name},{info.age},{info.gender})')
继承
单继承
支持定义类的语言一般也都支持继承,不然要这么个功能有什么用。如果要继承某个或某些类,在类定义上使用括号指定要继承的基类。如果需要访问基类的成员,使用基类名
加点访问符.
来访问。
class Student(Person):
def __init__(self, id, name, age):
Person.__init__(self, name, age)
self.id = id
def __str__(self):
return f'Student(id:{self.id},name:{self.name},age:{self.age})'
def introduce_myself(self):
print(f"I'm {self.name}, I'm {self.age} years old student.")
xiaoming = Student(1, 'xiaoming', 14)
print(xiaoming)
xiaoming.introduce_myself()
继承层次
按照C++的概念,Python类的所有函数都是虚的,也就是说在子类中重写的所有函数,都会父类的同名函数。如果需要调用父类的版本,需要使用父类名.XXX
的方式来访问。例如,如果我们要访问xiaoming的父类自我介绍。就需要使用下面的语句。
# 调用父类版本
Person.introduce_myself(xiaoming)
Python提供了两个内置函数isinstance
和issubclass
来帮我们判断类的继承关系。用法很简单,下面的结果依次是:真真真假。
print('--------------继承关系--------------')
print(f'xiaoming is Student:{isinstance(xiaoming,Student)}')
print(f'xiaoming is Person:{isinstance(xiaoming,Person)}')
print(f'Student is Person:{issubclass(Student,Person)}')
print(f'Person is Student:{issubclass(Person,Student)}')
多重继承
最后再来说说多重继承。多重继承的类签名类似下面这样。当我们访问子类的成员时,Python会先查找子类中存不存在该成员。如果不存在的话在查找父类,如果父类不存在就查找父类的父类,直到查到头为止。如果到这时候还没查找到就会抛出异常。
对于多重继承的话,这个过程可以简单的看成从左到右的、深度优先的查找过程:如果子类中不存在该成员,就先从Base1开始查找,如果Base1和它的所有父类都没有,再从Base2开始查找,以此类推。当然实际情况略微复杂一点,因为Python会检查类继承层次是否存在相同的父类,并确保相同的父类只访问一次。
class DerivedClassName(Base1, Base2, Base3):
迭代器和生成器
迭代器
在很多编程语言中都有迭代器的概念,迭代器可以在for-loop
循环中使用。一般情况下迭代器会有next()
和hasNext()
等类似的方法,确定什么时候应该停止迭代,什么时候返回元素。
在Python中需要使用__next__(self)
函数来执行迭代,如果到了末尾则需要抛出StopIteration
异常。如果编写了__next__(self)
函数,我们就可以让__iter__(self):
函数返回自身。这样一个迭代器就写好了,我们可以在for循环等地方使用了。
print('--------------迭代器--------------')
class IterableObj:
def __init__(self, data):
self.data = data
self.index = -1
def __iter__(self):
return self
def __next__(self):
if self.index == len(self.data) - 1:
raise StopIteration
self.index += 1
return self.data[self.index]
obj1 = IterableObj([1, 2, 3])
for i in obj1:
print(i, end=' ')
print()
Python还包含了两个内置函数iter()
和next()
用于创建迭代器和执行迭代。下面是使用列表迭代的例子。
list1 = [1, 2, 3]
iter1 = iter(list1)
e1 = next(iter1)
e2 = next(iter1)
e3 = next(iter1)
print('List:', e1, e2, e3)
生成器
迭代器虽然使用比较简单,但还是挺麻烦的。我们可以使用生成器更简单的创建迭代器。生成器其实就是一个函数,不过这个函数比较特殊,它不使用return
返回结果,而是使用yield
返回一系列值。当我们在循环中或者使用next()
函数调用生成器的时候,每次调用生成器都会使用yield
返回一个值。
print('--------------生成器--------------')
def even_generator(data):
index = 0
while index < len(data):
if data[index] % 2 == 0:
yield data[index]
index += 1
integer_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(f'even_generator:{[i for i in even_generator(integer_list)]}')
从这个例子我们可以看到,生成器确实比迭代器更方便。
生成器表达式
生成器表达式其实和列表解析表达式差不多,只不过列表解析表达式使用方括号,而生成器表达式使用小括号。另外,生成器表达式返回的是一个生成器,而列表解析表达式返回的是列表。除此之外,它们在迭代的时候结果完全相同。
不过,由于生成器不是一次性生成所有值,所以当迭代的序列非常大的时候,最好使用生成器表达式而不是列表解析表达式。
print('--------------生成器表达式--------------')
odd_generator = (i for i in range(1, 11) if i % 2 != 0)
odd_list = [i for i in range(1, 11) if i % 2 != 0]
print(f'generator type:{type(odd_generator)}')
print(f'list type:{type(odd_list)}')
结果如下。
--------------生成器表达式--------------
generator type:<class 'generator'>
list type:<class 'list'>