python笔记(6)---面向对象(2)

面向对象[2]

继承和多态

继承

对于python中的类的使用在py2的时候一个普通的类的写法如下:

class Myclass(object):
      pass

其中object是Myclass应该继承的类的名字,类的继承可以导致多态,类本身的特性导致了封装

那么继承的写法如何?,如下所示:

class A:
    def __init__(self):
        pass
    
class B(A):
    def __init__(self):
        super(B,self).__init__()

上面的例子我们首先定义了一个类A,这个类A是一个普通的类A,然后定义了一个类B,这个类B继承于类A,写法只是在类B的名字上加上一个括号写上需要被继承的类的名字即可,并且在继承类的init函数中加上super语句用于对基类部分进行初始化

多态

多态的发生有下面的条件满足即可:

  1. 发生继承
  2. 子类重写父类的方法(子类中的方法函数和父类中的名字参数全部一致)

看一个例子:

#食物类
class food:
     def get(self):
       print("I am food class")
#继承food类
class nood(food):
#重写父类方法get
    def __init__(self):
        super(nood,self).__init__()
    def get(self):
       
       print("I am nood class")
class meat(food):
    def __init__(self):
        super(meat,self).__init__() 
    def get(self):
        print("I am meat class")

在上面我们定义类的继承关系如下:

类nood继承于food 类meat继承于food 并且两个类都重写了父类的方法函数get()

我们定义一个函数

def fun(s):
    s.get()
food f
nood n
meat m
fun(f)
fun(n)
fun(m)

可以观察到打印的是不同的信息,fun(f)打印的是"I am food class",fun(m)打印的是"I am meat class",不同的对象打印的是不同的东西,但是这样你会说,那我只需要定义两个不同的类,然后再里面定义两个相同的名字的函数就可以了哇.

要回答上面的问题,得考虑类得继承使得子类对父类得成员访问得问题:
前面已经说到了怎么在一个类中定义一个私有变量:(变量名前加上双下划线即可)

看下面得一个例子:

class A:
    def __init__(self,a,b):
        self.__a=a
        self.b=b
    def get(self):
        print("I am class A")
class B(A):
    def __init__(self,a,b):
        super(B,self).__init__(a,b)

    def get(self):
        print("I am class B")

    def fun(self):
        print(self.__a)#错误 __a是A中的私有成员
a=A(1,2)
b=B(1,2)
print(a.__a)#错误用户不能访问类中的私有成员
print(b.__a)#错误 理由同上

在上面得例子中我们把__a定义为私有变量,在B中是不可访问得,但是b是公有的所以会被继承下去,所以我们可以在B类中访问类A的公有部分,但是不可以访问私有的部分!!!
但是如果要在外面使用的时候查看对象的私有成员的部分,可以采用如下的方式:

print(A(1,3)._A__a)# '_类名__变量'的方式进行打印

注:在python中所有变量和方法一律默认为public的


类的其他功能方法

对于python中的变量来讲比如 a=10,b=5,你可以使用a+b(这会返回15),a*b,a-b之类的四则运算的操作符,类似的还有取余的运算和list的[index]返回对应的位置的值,但是如果对于自定义对象而言呢?,一个自定义的类初始化的对象之间如何使用这些个操作符来完成一系列操作呢?答案是这需要类的设计者定义一些方法去自定义对象之间的加法,减法,乘法等的运算,(如果你有学过c++,这和c++的运算符重载有着相同的目的和相似的操作,你只需要将self看作this指针即可)


考虑下面的一个场景,你定义了一个向量类比如说是vector,你创建了两个向量的对象分别是
$v_1,v_2$那么你要定义向量的加法乘法比如$v_1+v_2,v_1-v_2$如何定义,python中有类似于__init__()的一些功能函数实现这些复杂的操作:

init: 构造函数,在生成对象时调用
del : 析构函数,释放对象时使用
repr : 打印,转换
setitem : 按照索引赋值
getitem: 按照索引获取值
len: 获得长度
cmp: 比较运算
call: 调用
add: 加运算
sub: 减运算
mul: 乘运算
div: 除运算
mod: 求余运算
pow: 幂
上面的函数都有类似于__init__()双下划线的形式,并且名字不能发生改变.

向量类的设计

下面我们开始着手定义一个向量类:

class vector:
    def __init__(self,x):
        '''
        type(x)=list
        '''
        self.v=x 
    def __add__(self,y):
        #相当于重载'+'运算符 
        #self是加号左边的对象,y是加号右边的对象
        #两边的对象都是vector类型的
        if len(self.v)!=len(y.v):
            return "Error!!!"
        return vector([self.v[i]+y.v[i] for i in range(len(self.v))]) 
        #返回值是vector类型
        #也可以返回其他类型的值,例如list
v1=vector([1,2,3])
v2=vector([1,2,3])
print((v1+v2).v)

在上面我们定义了__add__()函数使得$v_1+v_2$变得可能,并且加完之后返回一个新的vector对象

output: [2,4,6]

向量之间不仅仅存在加法而且还存在乘法:(定义如下的函数添加到类中:)

def __mul__(self,y):
    if len(self.v)!=len(y.v):
            return "Error!!!"
    #向量的内积运算
    return sum([self.v[i]*y.v[i] for i in range(len(self.v))])
print(v1*v2)

output:14

如果要打印向量的长度如何打印,一种方法是定义一个getlen()的函数:

def getlen(self):
    return len(self.v) #返回list的长度

但是这个的实质其实是调用的python内置的len()函数用于返回长度,如果你直接使用:

print(len(v1))

会报错如下:

object of type 'vector' has no len()

也就是说这个对象没有len的属性,我们定义如下的函数在类中:

def __len__(self):
    return len(self.v)
print(len(v1))

增加了__len__()函数之后的代码就不会报错了,并且可以正确的返回向量的长度

还有问题是如果我们需要按照索引修改向量的值,或者根据索引返回向量的值怎么做?

在类中增加函数如下:

#根据索引修改值
def __setitem__(self,index,value):
        self.v[index]=value
#根据索引返回值
def __getitem__(self,index):
        return self.v[index]

观察上面两个函数:

  1. setitem 有两个参数 一个是index 一个是value,也就是说你可以根据index 设置value 你可以理解为数组的形式,给对象增加了一'[]'运算符,此时你可以用v1[index]=value来对向量的每一个分量的值赋值操作

  2. getitem 只有一个参数index 如果你将这个函数理解为普通函数的话,那么不同的是调用方法的()运算符变为了[] 根据你输入的index设置返回值

v1=vector([1,2,3])
v1[2]=4 #setitem 函数的作用 1为index  value=4
print(v1[0]) #getitem 的作用 0为index

全部的vector类的代码如下:

class vector:
    def __init__(self,x):
        '''
        type(x)=list
        '''
        self.v=x 
    def __add__(self,y):#相当于重载'+'运算符
        #self是加号左边的对象,y是加号右边的对象
        #两边的对象都是vector类型的
        if len(self.v)!=len(y.v):
            return "Error!!!"
        return vector([self.v[i]+y.v[i] for i in range(len(self.v))]) #返回值是vector类型
        #也可以返回其他类型的值,例如list
    def __mul__(self,y):
        if len(self.v)!=len(y.v):
            return "Error!!!"
        return sum([self.v[i]*y.v[i] for i in range(len(self.v))])
    def __len__(self):
        return len(self.v)
    def __setitem__(self,index,value):
        self.v[index]=value
    def __getitem__(self,index):
        return self.v[index]

括号运算符的重载:

考虑一个类我们希望他的对象可以像c++的仿函数那样的性质,也就是说有一个A的对象 a 我们可以通过括号运算符 a()来完成一些操作的话,我们可以使用__call__()函数进行实现:

class A:
    def __call__():
        print("calling __call__() function!!!")
a=A()
a()

运行上面的代码即可调用类A中的__call__()函数,这有点类似于c++中的括号运算符的重载

posted @ 2020-11-23 19:47  差三岁  阅读(98)  评论(0编辑  收藏  举报