最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

python之继承案例教程

XAMPP案例 admin 257浏览 0评论

00Python

ding=utf-8

 

#基本上,内置类型的方法不会调用子类覆盖的方法。内置类型dict 的__init__ 和__update__ 方法会忽略我们覆盖的__setitem__ 方法

class DoppelDict(dict):

def __setitem__(self, key, value):

super().__setitem__(key, [value] * 2) # DoppelDict.__setitem__ 方法会重复存入的值(只是为了提供易于观察的效果)。它把职责委托给超类。

 

dd = DoppelDict(one=1) # 继承自dict 的__init__ 方法显然忽略了我们覆盖的__setitem__ 方法:’one’ 的值没有重复。

print(dd) #{‘one’: 1}

dd[‘two’] = 2 #  [] 运算符会调用我们覆盖的__setitem__ 方法,按预期那样工作:’two’ 对应的是两个重复的值,即[2, 2]。

print(dd) #{‘one’: 1, ‘two’: [2, 2]}

dd.update(three=3) # 继承自dict 的update 方法也不使用我们覆盖的__setitem__ 方法:’three’ 的值没有重复。

print(dd) #{‘three’: 3, ‘one’: 1, ‘two’: [2, 2]}

#原生类型的这种行为违背了面向对象编程的一个基本原则:始终应该从实例(self)所属的类开始搜索方法,即使在超类实现的类中调用也是如此。在这种糟糕的局面中,__missing__ 方法却能按预期方式工作,不过这只是特例。

# 不只实例内部的调用有这个问题(self.get() 不调用self.__getitem__()),内置类型的方法调用的其他类的方法,如果被覆盖了,也不会被调用。dict.update 方法会忽略AnswerDict.__getitem__ 方法

class AnswerDict(dict):

def __getitem__(self, key): # 不管传入什么键,AnswerDict.__getitem__ 方法始终返回42。

return 42

ad = AnswerDict(a=’foo’) # ad 是AnswerDict 的实例,以(‘a’, ‘foo’) 键值对初始化。

print(ad[‘a’]) # ad[‘a’] 返回42,这与预期相符。42

d = {}

d.update(ad) # d 是dict 的实例,使用ad 中的值更新d。

print(d[‘a’]) # dict.update 方法忽略了AnswerDict.__getitem__ 方法。’foo’

print(d) #{‘a’: ‘foo’}

 

#直接子类化内置类型(如dict、list 或str)容易出错,因为内置类型的方法通常会忽略用户覆盖的方法。不要子类化内置类型,用户自己定义的类应该继承collections 模块中的类,例如UserDict、UserList 和UserString,这些类做了特殊设计,因此易于扩展。

#如果不子类化dict,而是子类化collections.UserDict,问题便迎刃而解了。DoppelDict2 和AnswerDict2 能像预期那样使用,因为它们扩展的是UserDict,而不是dict

import collections

 

class DoppelDict2(collections.UserDict):

def __setitem__(self, key, value):

super().__setitem__(key, [value] * 2)

 

dd = DoppelDict2(one=1)

print(dd) #{‘one’: [1, 1]}

dd[‘two’] = 2

print(dd) #{‘two’: [2, 2], ‘one’: [1, 1]}

dd.update(three=3)

print(dd) #{‘two’: [2, 2], ‘three’: [3, 3], ‘one’: [1, 1]}

 

class AnswerDict2(collections.UserDict):

def __getitem__(self, key):

return 42

 

ad = AnswerDict2(a=’foo’)

print(ad[‘a’]) #42

d = {}

d.update(ad)

print(d[‘a’]) #42

print(d) #{‘a’: 42}

 

 

#任何实现多重继承的语言都要处理潜在的命名冲突,这种冲突由不相关的祖先类实现同名方法引起。这种冲突称为“菱形问题”,diamond.py:A、B、C 和D 四个类

class A:

def ping(self):

print(‘ping:’, self)

 

class B(A):

def pong(self):

print(‘pong:’, self)

 

class C(A):

def pong(self):

print(‘PONG:’, self)

 

class D(B, C):

def ping(self):

super().ping()

print(‘post-ping:’, self)

def pingpong(self):

self.ping()

super().ping()

self.pong()

super().pong()

C.pong(self)

#注意,B 和C 都实现了pong 方法,二者之间唯一的区别是,C.pong 方法输出的是大写的PONG。在D 的实例上调用d.pong() 方法的话,必须使用类名限定方法调用来避免这种歧义。在D 实例上调用pong 方法的两种方式

#from diamond import *

d = D()

d.pong() # 直接调用d.pong() 运行的是B 类中的版本。pong: <__main__.D object at 0x000001BBACA59340>

C.pong(d) # 超类中的方法都可以直接调用,此时要把实例作为显式参数传入。PONG: <__main__.D object at 0x000001BBACA59340>

#Python 能区分d.pong() 调用的是哪个方法,是因为Python 会按照特定的顺序遍历继承图。这个顺序叫方法解析顺序(Method Resolution Order,MRO)。类都有一个名为__mro__ 的属性,它的值是一个元组,按照方法解析顺序列出各个超类,从当前类一直向上,直到object 类。D 类的__mro__ 属性如下

print(D.__mro__)#(<class ‘__main__.D’>, <class ‘__main__.B’>, <class ‘__main__.C’>, <class ‘__main__.A’>, <class ‘object’>)

#若想把方法调用委托给超类,推荐的方式是使用内置的super() 函数。有时可能需要绕过方法解析顺序,直接调用某个超类的方法——这样做有时更方便。D.ping 方法可以这样写:

def ping(self):

A.ping(self) # 而不是super().ping()

print(‘post-ping:’, self)

#注意,直接在类上调用实例方法时,必须显式传入self 参数,因为这样访问的是未绑定方法(unbound method)。然而,使用super() 最安全,也不易过时。调用框架或不受自己控制的类层次结构中的方法时,尤其适合使用super()。使用super() 调用方法时,会遵守方法解析顺序, 使用super() 函数调用ping 方法

d = D()

d.ping() # D 类的ping 方法做了两次调用。ping: <__main__.D object at 0x000001D4B45AB3D0> # 第一个调用是super().ping();super 函数把ping 调用委托给A 类;这一行由A.ping输出。post-ping: <__main__.D object at 0x000001D4B45AB3D0> # 第二个调用是print(‘post-ping:’, self),输出的是这一行。

#D 实例上调用pingpong 方法得到的结果,pingpong 方法的5 个调用

d = D()

d.pingpong()

“””

ping: <diamond.D object at 0x10bf235c0> # 第一个调用是self.ping(),运行的是D 类的ping 方法,输出这一行和下一行。

post-ping: <diamond.D object at 0x10bf235c0>

ping: <diamond.D object at 0x10bf235c0> # 第二个调用是super().ping(),跳过D 类的ping 方法,找到A 类的ping 方法。

pong: <diamond.D object at 0x10bf235c0> # 第三个调用是self.pong(),根据__mro__,找到的是B 类实现的pong 方法。

pong: <diamond.D object at 0x10bf235c0> # 第四个调用是super().pong(),也根据__mro__,找到B 类实现的pong 方法。

PONG: <diamond.D object at 0x10bf235c0> # 第五个调用是C.pong(self),忽略__mro__,找到的是C 类实现的pong 方法。

“””

 

 

# 处理多重继承,

#1. 把接口继承和实现继承区分开,使用多重继承时,一定要明确一开始为什么创建子类。主要原因可能有:• 继承接口,创建子类型,实现“是什么”关系• 继承实现,通过重用避免代码重复其实这两条经常同时出现,不过只要可能,一定要明确意图。通过继承重用代码是实现细节,通常可以换用组合和委托模式。而接口继承则是框架的支柱。

#2. 使用抽象基类显式表示接口,现代的Python 中,如果类的作用是定义接口,应该明确把它定义为抽象基类。我们要创建abc.ABC 或其他抽象基类的子类。

#3. 通过混入重用代码,如果一个类的作用是为多个不相关的子类提供方法实现,从而实现重用,但不体现“是什么”关系,应该把那个类明确地定义为混入类(mixin class)。从概念上讲,混入不定义新类型,只是打包方法,便于重用。混入类绝对不能实例化,而且具体类不能只继承混入类。混入类应该提供某方面的特定行为,只实现少量关系非常紧密的方法。

#4. 在名称中明确指明混入,因为在Python 中没有把类声明为混入的正规方式,所以强烈推荐在名称中加入…Mixin后缀。Tkinter 没有采纳这个建议,如果采纳的话,XView 会变成XViewMixin,Pack 会变成PackMixin

#5. 抽象基类可以作为混入,反过来则不成立,抽象基类可以实现具体方法,因此也可以作为混入使用。不过,抽象基类会定义类型,而混入做不到。此外,抽象基类可以作为其他类的唯一基类,而混入决不能作为唯一的超类,除非继承另一个更具体的混入——真实的代码很少这样做。抽象基类有个局限是混入没有的:抽象基类中实现的具体方法只能与抽象基类及其超类中的方法协作。这表明,抽象基类中的具体方法只是一种便利措施,因为这些方法所做的一切,用户调用抽象基类中的其他方法也能做到。

#6. 不要子类化多个具体类,具体类可以没有,或最多只有一个具体超类。 也就是说,具体类的超类中除了这一个具体超类之外,其余的都是抽象基类或混入。例如,在下述代码中,如果Alpha 是具体类,那么Beta 和Gamma 必须是抽象基类或混入:

#class MyConcreteClass(Alpha, Beta, Gamma):

“””这是一个具体类,可以实例化。”””

# ……更多代码……

#7. 为用户提供聚合类,“如果一个类的结构主要继承自混入,自身没有添加结构或行为,那么这样的类称为聚合类。”如果抽象基类或混入的组合对客户代码非常有用,那就提供一个类,使用易于理解的方式把它们结合起来。Grady Booch 把这种类称为聚合类(aggregate class)。例如,下面是tkinter.Widget 类的完整代码:

#class Widget(BaseWidget, Pack, Place, Grid):

“””Internal class.

Base class for a widget which can be positioned with the

geometry managers Pack, Place or Grid.”””

#pass

#Widget 类的定义体是空的,但是这个类提供了有用的服务:把四个超类结合在一起,这样需要创建新小组件的用户无需记住全部混入,也不用担心声明class 语句时有没有遵守特定的顺序

#8. “优先使用对象组合,而不是类继承”,优先使用组合能让设计更灵活。例如,对tkinter.Widget 类来说,它可以不从全部几何管理器中继承方法,而是在小组件实例中维护一个几何管理器引用,然后通过它调用方法。毕竟,小组件“不是”几何管理器,但是可以通过委托使用相关的服务。这样,我们可以放心添加新的几何管理器,不必担心会触动小组件类的层次结构,也不必担心名称冲突。即便是单继承,这个原则也能提升灵活性,因为子类化是一种紧耦合,而且较高的继承树容易倒。组合和委托可以代替混入,把行为提供给不同的类,但是不能取代接口继承去定义类型层次结构。

 

#在Django 中,视图是可调用的对象,它的参数是表示HTTP 请求的对象,返回值是一个表示HTTP 响应的对象。

#基于类的视图,而且还通过基类、混入和拿来即用的具体类提供了一些通用视图类。这些基类和混入在django.views.generic 包的base 模块里。位于顶部的两个类,View 和TemplateResponseMixin,负责完全不同的工作。

#View 是所有视图(可能是个抽象基类)的基类,提供核心功能,如dispatch 方法。这个方法委托具体子类实现的处理方法(handler),如get、head、post 等,处理不同的HTTP动词。RedirectView 类只继承View,可以看到,它实现了get、head、post 等方法。View 的具体子类应该实现处理方法,但它们为什么不在View 接口中呢?原因是:子类只需实现它们想支持的处理方法。TemplateView 只用于显示内容,因此它只实现了get 方法。如果把HTTP POST 请求发给TemplateView,经继承的View.dispatch 方法检查,它没有post 处理方法,因此会返回HTTP 405 Method Not Allowed(不允许使用的方法)响应。TemplateResponseMixin 提供的功能只针对需要使用模板的视图。例如,RedirectView没有主体内容,因此它不需要模板,也就没有继承这个混入。TemplateResponseMixin为TemplateView 和django.views.generic 包中定义的使用模板渲染的其他视图。

#最重要的类是ListView。这是一个聚合类,不含任何代码(定义体中只有一个文档字符串)。ListView 实例有个object_list 属性,模板会迭代它显示页面的内容,通常是数据库查询返回的多个对象。生成这个可迭代对象列表的相关功能都由MultipleObjectMixin 提供。这个混入还提供了复杂的分页逻辑,即在一页中显示部分结果,并提供指向其他页面的链接。假设你想创建一个使用模板渲染的视图,但是会生成一组JSON 格式的对象,此时用得到BaseListView 类。这个类提供了易于使用的扩展点,把View 和MultipleObjectMixin 的功能整合在一起,避免了模板机制的开销。与Tkinter 相比,Django 基于类的视图API 是多重继承更好的示例。尤其是,Django 的混入类易于理解:各个混入的目的明确,而且名称的后缀都是…Mixin。Django 用户还没有完全拥抱基于类的视图。很多人确实在使用,但是用法有限,把它们当成黑盒;需要新功能时,很多Django 程序员依然选择编写单块视图函数,负责处理所有事务,而不尝试重用基视图和混入。

转载请注明:XAMPP中文组官网 » python之继承案例教程

您必须 登录 才能发表评论!