当前位置: 首页 > 科技观察

Python类、继承、多态分析

时间:2023-03-13 01:51:28 科技观察

类定义如果要定义一个类Point,表示一个二维坐标点:#point.pyclassPoint:def__init__(self,x=0,y=0):自己。x,self.y=x,y最基本的是__init__方法,相当于C++/Java的构造函数。带有双下划线__的方法是特殊方法,除了__init__还有很多,后面会介绍。参数self相当于C++的this,表示当前实例。所有的方法都有这个参数,但是调用的时候不需要指定。>>>frompointimport*>>>p=Point(10,10)#__init__被称为>>>type(p)>>>p.x,p.y(10,10)几乎所有特殊方法(包括__init__)被隐式调用(不是直接调用)。对于万物皆对象的Python,类本身当然也是对象:>>>type(Point)>>>dir(Point)['__class__','__delattr__','__dict__',...,'__init__',...]>>>Point.__class__Point是type的实例,就像p是Point的实例一样。现在添加方法集:classPoint:...defset(self,x,y):self.x,self.y=x,y>>>p=Point(10,10)>>>p.set(0,0)>>>p.x,p.y(0,0)p.set(...)其实只是一个语法糖,你也可以写成Point.set(p,...),这样就是很明显p是self参数:>>>Point.set(p,0,0)>>>p.x,p.y(0,0)值得注意的是self不是关键字,甚至可以换成其他名字,比如这样:classPoint:...defset(this,x,y):this.x,this.y=x,y与C++的区别在于“成员变量”必须以self.为前缀,否则会成为类属性(等效于C++静态成员),而不是对象属性。访问控制Python没有public/protected/private等访问控制。如果硬要表示“private”,习惯是加双下划线前缀。classPoint:def__init__(self,x=0,y=0):self.__x,self.__y=x,ydefset(self,x,y):self.__x,self.__y=x,ydef__f(self):pass__x,__y和__f等价于private:>>>p=Point(10,10)>>>p.__x...AttributeError:'Point'objecthasnoattribute'__x'>>>p.__f()...AttributeError:'Point'objecthasnoattribute'__f'_repr_AttempttoprintPointinstance:>>>p=Point(10,10)>>>p通常,这不是我们想要的输出,我们想要的是:>>>pPoint(10,10)可以通过添加一个特殊的方法__repr__来实现:classPoint:def__repr__(self):return'Point({},{})'.format(self.__x,self.__y)不难看出,交互模式在打印p的时候其实调用了repr(p):>>>repr(p)'Point(10,10)'_str_如果没有提供__str__,str()默认使用repr()的结果。两者都是对象的字符串表示,但有区别。简单来说,repr()的结果是面向解释器的,通常是合法的Python代码,比如Point(10,10);而str()的结果是面向用户的,比较简洁,比如(10,10)。根据这个原则,我们给Point提供__str__的定义如下:classPoint:def__str__(self):return'({},{})'.format(self.__x,self.__y)_add_添加两个坐标点是一个非常合理的需求。>>>p1=Point(10,10)>>>p2=Point(10,10)>>>p3=p1+p2Traceback(mostrecentcallast):File"",line1,inTypeError:unsupportedoperandtype(s)for+:'Point'和'Point'可以通过添加一个特殊的方法__add__来完成:classPoint:def__add__(self,other):returnPoint(self.__x+other.__x,self.__y+other.__y)>>>p3=p1+p2>>>p3Point(20,20)这就像C++中的运算符重载。Python的内置类型(例如字符串和列表)会“重载”+运算符。还有很多特殊的方法,这里就不一一介绍了。继承给出了教科书中最常见的例子之一。Circle和Rectangle继承自Shape,不同图形的面积计算方法不同。#shape.pyclassShape:defaarea(self):return0.0classCircle(Shape):def__init__(self,r=0.0):self.r=rdefarea(self):returnmath.pi*self.r*self.rclassRectangle(Shape):def__init__(self,a,b):self.a,self.b=a,bdefarea(self):returnsself.a*self.b用法比较简单:>>>fromshapeimport*>>>circle=Circle(3.0)>>>>circle.area()28.274333882308138>>>rectangle=Rectangle(2.0,3.0)>>>rectangle.area()6.0如果Circle没有定义自己的面积:classCircle(Shape):pass那么它将继承父类Shape区域:>>>Shape.areaisCircle.areaTrue一旦Circle定义了自己的区域,继承自Shape的区域就被覆盖了:>>>fromshapeimport*>>>Shape.areaisCircle.areaFalse通过类字典使这一点更加明显:>>>Shape.__dict__['area']>>>Circle.__dict__['area']所以,子类重写了父类的方法,其实就是绑定了相同的属性名称到不同的函数对象。可见Python没有override的概念。同理,即使Shape没有定义面积,也是可以的。Shape作为一个“接口”,不能通过语法来保证。您甚至可以动态添加方法:classCircle(Shape):...#defarea(self):#returnmath.pi*self.r*self.r#AddareamethodforCircle。Circle.area=lambdaself:math.pi*self.r*self.r动态语言一般都这么灵活,Python也不例外。Python官方教程《9.类》的第一句话是:与其他编程语言相比,Python的类机制增加了最少新语法和语义的类。Python以最少的新语法和语义实现了类机制。确实很棒,但对于C++/Java程序员来说也很不舒服。多态性如前所述,Python没有覆盖的概念。严格来说,Python不支持“多态性”。为了解决继承结构中的接口和实现的问题,或者说为了更好的使用Python进行面向接口的编程(设计模式所提倡的),我们需要人为地设定一些规范。请考虑Shape.area()除了简单的返回0.0之外还有更好的实现方式吗?以内置模块asyncio为例,AbstractEventLoop原则上是一个接口,类似于Java中的接口或者C++中的纯虚类,但是Python没有语法来保证这一点。为了体现AbstractEventLoop是一个接口,首先在名称上将其标记为抽象(Abstract),然后让每个方法都抛出一个异常NotImplementedError。classAbstractEventLoop:defrun_forever(self):raiseNotImplementedError...即便如此,你也不能禁止用户实例化AbstractEventLoop:loop=asyncio.AbstractEventLoop()try:loop.run_forever()exceptNotImplementedError:passC++可以通过纯虚函数或构造函数保护到防止接口被实例化,Java就更不用说了,接口就是接口,语法支持完备。您不能强制子类必须实现“接口”中定义的每个方法,C++的纯虚函数可以强制执行此操作(更不用说Java了)。即使子类“认为”自己实现了“接口”中的方法,也不能保证方法名是正确的。C++的override关键字就可以保证这一点(Java就更不用说了)。静态类型的缺失使得Python很难像C++/Java那样实现严格的多态检查机制。所以,面向接口的编程,对于Python来说,更多的是取决于程序员的素质。回到Shape的例子,和asyncio一样,我们把“接口”改成这样:classAbstractShape:defarea(self):raiseNotImplementedError,让它更像一个接口。super有时候,需要在子类中调用父类的方法。比如图形有颜色的属性,那么不妨给__init__增加一个参数color:classAbstractShape:def__init__(self,color):self.color=color,那么子类的__init__()也要相应的改:classCircle(AbstractShape):def__init__(self,color,r=0.0):super().__init__(color)self.r=r通过super将颜色传递给父类的__init__()。事实上,你不需要super:classCircle(AbstractShape):def__init__(self,color,r=0.0):AbstractShape.__init__(self,color)self.r=r但super是推荐的方式,因为它避免了hard编码也可以处理多重继承。