Python基础:18类和实例之二
1:绑定和非绑定
当存在一个实例时,方法才被认为是绑定到那个实例了。没有实例时方法就是未绑定的。在很多情况下,调用的都是一个绑定的方法。
调用非绑定方法并不经常用到,其中一个主要的场景是:派生一个子类,而且要覆盖父类的方法,这时需要调用那个父类中被覆盖掉的构造方法:
class EmplAddrBookEntry(AddrBookEntry):
'Employee Address Book Entry class' def __init__(self, nm, ph, em):
AddrBookEntry.__init__(self, nm, ph)
self.empid = id
self.email = em
EmplAddrBookEntry是AddrBookEntry 的子类,重载了__init__()。这是调用非绑定方法的最佳地方了。
当一个EmplAddrBookEntry被实例化,并且调用 __init__() 时,其与AddrBookEntry的实例只有很少的差别,主要是因为我们还没有机会来自定义我们的EmplAddrBookEntry 实例,以使它与AddrBookEntry 不同。所以,可以将EmplAddrBookEntry实例传递给AddrBookEntry的__init__。
子类中 __init__() 的第一行就是对父类__init__()的调用。通过父类名来调用它,并且传递给它 self 和其他所需要的参数。一旦调用返回,我们就能定义那些与父类不同的仅存在我们的(子)类中的(实例)定制。
2:静态方法和类方法
静态方法和类方法在Python2.2中引入。经典类及新式(new-style)类中都可以使用它。
Python中的静态方法和C++或者Java这些语言中的是一样的。它们仅是类中的函数(不需要实例)。
对于类方法而言,需要类而不是实例作为第一个参数,它是由解释器传给方法。类不需要特别地命名,类似self,不过很多人使用cls作为变量名字。
下面是在经典类中创建静态方法和类方法的一些例子(也可以把它们用在新式类中):
class TestStaticMethod:
def foo():
print 'calling static method foo()'
foo = staticmethod(foo) class TestClassMethod:
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
foo = classmethod(foo)
内建函数staticmethod和classmethod将它们转换成相应的类型,并且重新赋值给了相同的变量名。如果没有调用这两个函数,二者都会在Python 编译器中产生错误,显示需要带self 的常规方法声明。 可以通过类或者实例调用这些函数:
>>> tsm = TestStaticMethod()
>>>TestStaticMethod.foo()
calling static method foo() >>>tsm.foo()
calling static method foo() >>> tcm = TestClassMethod()
>>>TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod >>>tcm.foo()
calling class method foo()
foo() is part of class: TestClassMethod
通过使用修饰符,可以避免像上面那样的重新赋值:
class TestStaticMethod:
@staticmethod
def foo():
print 'calling static method foo()' class TestClassMethod:
@classmethod
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
3:有两种方法可以在代码中利用类。第一种是组合(composition)。就是让不同的类混合并加入到其它类中,来增加功能和代码重用性。另一种方法是通过派生。
组合的例子如下:
class NewAddrBookEntry(object):
'new address book entry class' def __init__(self, nm, ph):
self.name = Name(nm) #创建Name实例
self.phone = Phone(ph) #创建Phone实例
print 'Created instance for:', self.name
NewAddrBookEntry类由其它类组合而成。这就在一个类和其它组成类之间定义了一种“有一个”的关系。比如,我们的NewAddrBookEntry 类“有一个” Name 类实例和一个Phone实例。
4:子类和派生
OOP的更强大方面之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其它代码片段。允许类特征在子孙类或子类中进行继承。
新式类创建子类的语法:
class SubClassName (ParentClass1[, ParentClass2,...]):
'optional class documentation string'
class_suite
如果你的类没有从任何祖先类派生,可以使用object 作为父类的名字。
经典类的声明唯一不同之处在于其没有从祖先类派生--此时,没有圆括号:
class ClassicClassWithoutSuperclasses:
pass
下面还有一个简单的例子:
class Parent(object):
def parentMethod(self):
print 'calling parent method' class Child(Parent):
def childMethod(self):
print 'calling child method' >>> p = Parent()
>>> p.parentMethod()
calling parent method
>>> >>> c = Child()
>>> c.childMethod()
calling child method >>>c.parentMethod()
calling parent method
5:继承
一个子类可以继承它的基类的任何属性,不管是数据属性还是方法。举个例子如下。P 是一个没有属性的简单类。C 从P 继承而来,也没有属性:
class P(object):
pass class C(P):
pass >>> c = C()
>>> c.__class__
<class '__main__.C'> >>>C.__bases__
(<class '__main__.P'>,)
下面给 P 添加一些属性:
class P:
'P class'
def __init__(self):
print 'created an instance of', self.__class__.__name__ class C(P):
pass
现在P 有文档字符串__doc__和__init__:
>>> p = P()
created an instance of P >>> p.__class__
<class '__main__.P'> >>> P.__bases__
(<type 'object'>,) >>> P.__doc__
'P class' >>> c = C()
created an instance of C >>> c.__class__
<class '__main__.C'> >>> C.__bases__
(<class '__main__.P'>,) >>>C.__doc__
>>>
C没有声明__init__()方法,然而在类C 的实例c 被创建时,还是会有输出信息。原因在于C 继承了P 的__init__()。
需要注意的是,文档字符串对类,函数/方法,还有模块来说都是唯一的,所以特殊属性__doc__不会从基类中继承过来。
对任何(子)类,其__bases__类属性是一个包含其父类(parent)的集合的元组。
那些没有父类的类,它们的__bases__属性为空。下面我们看一下如何使用__bases__的。
>>> class A(object): pass
... >>> class B(A): pass
... >>> class C(B): pass
... >>> class D(A, B): pass
... >>> A.__bases__
(<type 'object'>, ) >>> B.__bases__
(<class '__main__.A'>,) >>> C.__bases__
(<class '__main__.B'>,) >>> D.__bases__
(<class '__main__.B'>, <class '__main__.A'>)
在上面的例子中,尽管C 是A 和B 的子类(通过B 传递继承关系),但C的父类是B,所以,只有B 会在C.__bases__中显示出来。
在父类 P 中再写一个函数,然后在其子类中对它进行覆盖:
class P(object):
def foo(self):
print 'Hi, I am P-foo()' >>> p = P()
>>> p.foo()
Hi, I am P-foo() class C(P):
def foo(self):
print 'Hi, I am C-foo()' >>> c = C()
>>> c.foo()
Hi, I am C-foo()
尽管C继承了P 的foo()方法,但因为C 定义了它自已的foo()方法,所以 P 中的foo() 方法被覆盖。
尽管父类中的foo被覆盖了,但是还是可以调用那个被覆盖的基类方法。这时就需要去调用一个未绑定的基类方法,需要明确给出子类的实例,例如下边:
>>> P.foo(c)
Hi, I am P-foo()
典型情况下,你不会以这种方式调用父类方法,你会在子类的重写方法里显式地调用基类方法。
class C(P):
def foo(self):
P.foo(self)
print 'Hi, I am C-foo()'
在这个(未绑定)方法调用中我们显式地传递了self. 一个更好的办法是使用super()内建方法:
class C(P):
def foo(self):
super(C, self).foo()
print 'Hi, I am C-foo()'
super()不但能找到基类方法,而且还为我们传进self:
>>> c = C()
>>> c.foo()
Hi, I am P-foo()
Hi, I am C-foo()
类似于上面的覆盖非特殊方法,当从一个带__init()__的类派生,如果不覆盖__init__(),它将会被继承并自动调用。但如果在子类中覆盖了__init__(),子类被实例化时基类的__init__()就不会被自动调用。
class P(object):
def __init__(self):
print "calling P's constructor" class C(P):
def __init__(self):
print "calling C's constructor" >>> c = C()
calling C's constructor
如果还想调用基类的 __init__(),需要明确指出,使用一个子类的实例去调用基类(未绑定)方法。相应地更新类C,会出现下面预期的执行结果:
class C(P):
def __init__(self):
P.__init__(self)
print "calling C's constructor" >>> c = C()
calling P's constructor
calling C's constructor
上边的例子中,子类的__init__()方法首先调用了基类的的__init__()方法。这是相当普遍(不是强制)的做法,用来设置初始化基类,然后可以执行子类内部的设置。super()内建函数引入到Python中,可以这样写:
class C(P):
def __init__(self):
super(C, self).__init__()
print "calling C's constructor"
使用super()的漂亮之处在于,不需要明确提供父类。这意味着如果改变了类继承关系,只需要改一行代码(class 语句本身)而不必在大量代码中去查找所有被修改的那个类的名字。
如果一个类的构造方法被重写,那么就需要调用超类(你所继承的类)的构造方法,否则对象可能不会被正确地初始化。比如下面的例子:
>>> class Bird:
... def __init__(self):
... self.hungry=True
... def eat(self):
... if self.hungry:
... print 'aaah...'
... self.hungry=False
... else:
... print 'no thanks' >>> b=Bird()
>>> b.eat()
aaah... >>> b.eat()
no thanks
现在考虑子类SongBird,它添加了唱歌的行为:
>>> class SongBird(Bird):
... def __init__(self):
... self.sound='Squawk'
... def sing(self):
... print self.sound
... >>> s=SongBird()
>>> s.sing()
Squawk
因为SongBird是Bird的一个子类,它继承了eat方法,但如果调用eat方法,就会产生一个问题:
>>> s.eat()
Traceback (most recent calllast):
File"<input>", line 1, in <module>
File"<input>", line 5, in eat
AttributeError:SongBird instance has no attribute 'hungry'
原因是:在SongBird中,构造方法被重写,但新的构造方法没有任何关于初始化hungry特性的代码。为了达到预期的效果,SongBird的构造方法必须调用其超类Bird的构造方法来确保进行基本的初始化。
6:继承标准类型
经典类中的一个问题是,不能对标准类型进行子类化。幸运的是,随着类型(types)和类(class)的统一和新式类的引入。这一点已经被修正。下面,介绍两个子类化Python 类型的相关例子。
一个处理浮点数的子类,它带两位小数位:
class RoundFloat(float):
def __new__(cls, val):
return float.__new__(cls, round(val, 2))
覆盖__new__()特殊方法来定制对象,使之和标准Python 浮点数(float)有一些区别。最好是使用super()内建函数去捕获对应的父类以调用它的__new()__方法,下面,对它进行这方面的修改:
class RoundFloat(float):
def __new__(cls, val):
return super(RoundFloat, cls).__new__(cls, round(val, 2))
下面是一些样例输出:
>>>RoundFloat(1.5955)
1.6 >>>RoundFloat(1.5945)
1.59 >>>RoundFloat(-1.9955)
-2.0
object.__new__(cls, ...),它会创建cls的实例,__new__是静态方法(因其特殊性,不需要显示声明)。而且它的第一个参数是要创建实例的类型。他返回一个cls的实例。
一般的用法中,创建子类的实例,可以调用父类的new:super(currentcls, cls).__new__(cls, ...)。比如:float.__new__(cls, 3.456),创建的就是cls的实例。
7:多重继承
Python允许子类继承多个基类。也就是多重继承。这里的难点在于:如何正确找到没有在当前(子)类定义的属性。也就是所谓的:方法解释顺序(MRO)。
在Python 2.2 以前的版本中,算法非常简单:深度优先,从左至右进行搜索,多重继承则取找到的第一个名字。
由于类,类型和内建类型的子类,都经过全新改造, 有了新的结构,这种算法不再可行。这样一种新的MRO 算法被开发出来,新的查询方法是采用广度优先,而不是深度优先。
下面的示例,展示经典类和新式类中,方法解释顺序有什么不同。
class P1: #(object)
def foo(self):
print 'called P1-foo()' class P2: #(object)
def foo(self):
print 'called P2-foo()'
def bar(self):
print 'called P2-bar()' class C1(P1, P2): pass class C2(P1, P2):
def bar(self):
print 'called C2-bar()' class GC(C1, C2): pass
上面这些类的关系如下图所示:
首先来使用经典类,可以验证经典类使用的解释顺序,深度优先,从左至右:
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> P1
called P1-foo() >>> gc.bar() # GC ==> C1 ==> P1 ==> P2
called P2-bar()
当调用foo()时,它首先在当前类(GC)中查找。如果没找到,就向上查找最亲的父类,C1。查找未遂,就继续沿树上访到父类P1,foo()被找到。
同样,对bar()来说,它通过搜索GC,C1,P1 然后在P2 中找到。因为使用这种解释顺序的缘故,C2.bar()根本就不会被搜索了。
如果需要调用C2 的bar()方法,则必须调用它的合法的全名,采用典型的非绑定方式去调用,并且提供一个合法的实例:
>>> C2.bar(gc)
called C2-bar()
对于新式类,取消类P1 和类P2 声明中的对(object)的注释,重新执行一下。新式方法的查询有一些不同:
>>> gc = GC()
>>> gc.foo() # GC==> C1 ==> C2 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> C2
called C2-bar()
与沿着继承树一步一步上溯不同,它首先查找同胞兄弟,采用一种广度优先的方式。当查找foo(),它检查GC,然后是C1 和C2,然后在P1 中找到。如果P1 中没有,查找将会到达P2。
然而,bar()的结果是不同的。它搜索GC 和C1,紧接着在C2 中找到了。这样,就不会再继续搜索到祖父P1 和P2。
新式类有一个__mro__属性,告诉你查找顺序是怎样的。它是类属性,实例没有该属性。它是新式类的属性,经典类没有:
>>> GC.__mro__
(<class '__main__.GC'>, <class'__main__.C1'>, <class '__main__.C2'>, <class '__main__.P1'>, <class '__main__.P2'>, <type'object'>)
为什么经典类MRO 会失败?在版本2.2 中,类型与类的统一,带来了一个新的“问题”,波及所有从object派生出来的(根)类,一个简单的继承结构变成了一个菱形。
比如,有经典类B 和C,C 覆盖了构造器,B 没有,D 从B 和C 继承而来:
class B:
pass class C:
def __init__(self):
print "the default constructor" class D(B, C):
pass
当我们实例化D,得到:
>>> d = D()
the default constructor
上图为B,C 和D 的类继承结构,现在把代码改为采用新式类的方式,问题也就产生了:
class B(object):
pass class C(object):
def __init__(self):
print "the default constructor"
由于在新式类中,需要出现基类,这样就在继承结构中,形成了一个菱形。D 的实例上溯时,不应当错过C, 但不能两次上溯到A(因为B 和C 都从A 派生)。
继承结构已变成了一个菱形,如果使用经典类的MRO,当实例化D 时,不再得到C.__init__()的结果,而是得到object.__init__()!这就是为什么MRO 需要修改的真正原因。
尽管我们看到了,在上面的例子中,类GC的属性查找路径被改变了,但你不需要担心会有大量的代码崩溃。经典类将沿用老式MRO,而新式类将使用它自己的MRO。
Python基础:18类和实例之二的更多相关文章
- python基础编程——类和实例
在了解类和实例之前,需要先了解什么是面向对象,什么又是面向过程.面向过程是以过程为中心实现一步步操作(相互调用,类似流水线思想):面向对象是以事物为中心,某个事物可以拥有自己的多个行为,而另一个事物也 ...
- Python基础(类和实例)
class Point(object): def __init__(self,name,score): self.__name = name self.__score = score def prin ...
- 二十六. Python基础(26)--类的内置特殊属性和方法
二十六. Python基础(26)--类的内置特殊属性和方法 ● 知识框架 ● 类的内置方法/魔法方法案例1: 单例设计模式 # 类的魔法方法 # 案例1: 单例设计模式 class Teacher: ...
- python学习_数据处理编程实例(二)
在上一节python学习_数据处理编程实例(二)的基础上数据发生了变化,文件中除了学生的成绩外,新增了学生姓名和出生年月的信息,因此将要成变成:分别根据姓名输出每个学生的无重复的前三个最好成绩和出生年 ...
- python基础——枚举类
python基础——枚举类 当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份: JAN = 1 FEB = 2 MAR = 3 ... NOV = 11 DEC = 12 好处是简单 ...
- python基础——定制类
python基础——定制类 看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的. __slots__我们已经知道怎么用了,__len__()方 ...
- python中的类和实例
今天花了两个多小时后搜索相关博客看了看python中有关类和实例的介绍,差不多大概明白了. python中的类和c++中的类是一样的,不同之处就是c++的类,如果含有成员变量,并且成员变量发生变化后, ...
- Python基础-类的探讨(class)
Python基础-类的探讨(class) 我们下面的探讨基于Python3,我实际测试使用的是Python3.2,Python3与Python2在类函数的类型上做了改变 1,类定义语法 Python ...
- 十八. Python基础(18)常用模块
十八. Python基础(18)常用模块 1 ● 常用模块及其用途 collections模块: 一些扩展的数据类型→Counter, deque, defaultdict, namedtuple, ...
随机推荐
- 如约而至(walk)
LCA大佬的做法: 考虑暴力的高斯消元,我们优化它. $\sum\limits_{j} gcd(i,j)^{c-d} i^d j^d x_j=b_i$ $\sum\limits_{j} gcd(i,j ...
- 转:Android检查设备是否联网
public static boolean isConnect(Context context) { ConnectivityManager connectionManager = (Connecti ...
- Eclipse配置Maven详细教程
一.使用eclipse自带的maven插件 首先,现在下载Eclipse Mars之后的版本,基本上都自带了maven插件,无需自己再安装maven. 有几个注意点: 1.默认的本地仓库的目录是在C: ...
- Mac+Webstorm 双更新后 webstorm无法使用内置svn
我终于营业了!!!!!! EachTime!!!! 我更新了mac系统后,就会莫名其妙的webstorm的svn无法使用 具体表现为无法更新和提交 具体报错为:Can't use Subversion ...
- 2019.8.1 NOIP模拟测试11 反思总结
延迟了一天来补一个反思总结 急匆匆赶回来考试,我们这边大家的状态都稍微有一点差,不过最后的成绩总体来看好像还不错XD 其实这次拿分的大都是暴力[?],除了某些专注于某道题的人以及远程爆踩我们的某学车神 ...
- Leetcode12.Integer to Roman整数转罗马数字
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即为两个并 ...
- thinkcmf 导航高亮制作方法(适用于多级导航)(通用)
平时用thinkcmf网站开发经常需要导航点击之后高亮,就写了一些实现方法分享一下. 思路很简单,先获取当前页面的顶级栏目的地址,然后与导航中的地址比较,相同的就加上一个class,把下面函数理解了不 ...
- UE4物理模块(一)---概述与可视化调试
UE4.21前的版本采用的是NVIDIA的PhysX做为其默认的物理引擎,用于计算3D世界的碰撞查询与物理模拟.自4.21版本开始改物理调用接口,但这并不是闲来重构代码,果然在2019GDC大会上放出 ...
- 高可用服务 AHAS 在消息队列 MQ 削峰填谷场景下的应用
在消息队列中,当消费者去消费消息的时候,无论是通过 pull 的方式还是 push 的方式,都可能会出现大批量的消息突刺.如果此时要处理所有消息,很可能会导致系统负载过高,影响稳定性.但其实可能后面几 ...
- python实现贝叶斯网络的概率推导(Probabilistic Inference)
写在前面 这是HIT2019人工智能实验三,由于时间紧张,代码没有进行任何优化,实验算法仅供参考. 实验要求 实现贝叶斯网络的概率推导(Probabilistic Inference) 具体实验指导书 ...