flash个人网站源码,网站首页设计收费,网站建设公司推荐5788,qq推广功能在哪开Python 语言在设计之初#xff0c;就定位为一门面向对象的编程语言#xff0c;“Python 中一切皆对象”就是对 Python 这门编程语言的完美诠释。
类和对象是 Python 的重要特征#xff0c;相比其它面向对象语言#xff0c;Python 很容易就可以创建出一个类和对象。同时就定位为一门面向对象的编程语言“Python 中一切皆对象”就是对 Python 这门编程语言的完美诠释。
类和对象是 Python 的重要特征相比其它面向对象语言Python 很容易就可以创建出一个类和对象。同时Python 也支持面向对象的三大特征封装、继承和多态。
本章不仅会教你掌握 Python 类和对象的基本语法还可以带你深入底层了解 Python 面向对象的实现原理。
一、什么是面向对象Python面向对象一切皆对象
读者肯定听过 Python 中“一切皆对象”的说法但可能并不了解它的具体含义只是在学习的时候听说 Python 是面向对象的编程语言本节将向大家详细介绍 Python 面向对象的含义。 面向对象编程是在面向过程编程的基础上发展来的它比面向过程编程具有更强的灵活性和扩展性。面向对象编程是程序员发展的分水岭很多初学者会因无法理解面向对象而放弃学习编程。 面向对象编程Object-oriented Programming简称 OOP是一种封装代码的方法。其实在前面章节的学习中我们已经接触了封装比如说将乱七八糟的数据扔进列表中这就是一种简单的封装是数据层面的封装把常用的代码块打包成一个函数这也是一种封装是语句层面的封装。 代码封装其实就是隐藏实现功能的具体代码仅留给用户使用的接口就好像使用计算机用户只需要使用键盘、鼠标就可以实现一些功能而根本不需要知道其内部是如何工作的。 本节所讲的面向对象编程也是一种封装的思想不过显然比以上两种封装更先进它可以更好地模拟真实世界里的事物将其视为对象并把描述特征的数据和代码块函数封装到一起。 打个比方若在某游戏中设计一个乌龟的角色应该如何来实现呢使用面向对象的思想会更简单可以分为如下两个方面进行描述
从表面特征来描述例如绿色的、有 4 条腿、重 10 kg、有外壳等等。从所具有的的行为来描述例如它会爬、会吃东西、会睡觉、会将头和四肢缩到壳里等等。
如果将乌龟用代码来表示则其表面特征可以用变量来表示其行为特征可以通过建立各种函数来表示。参考代码如下所示
class tortoise:bodyColor 绿色footNum 4weight 10hasShell True#会爬def crawl(self):print(乌龟会爬)#会吃东西def eat(self):print(乌龟吃东西)#会睡觉def sleep(self):print(乌龟在睡觉)#会缩到壳里def protect(self):print(乌龟缩进了壳里) 注意以上代码仅是为了演示面向对象的编程思想具体细节后续会做详细介绍。 因此从某种程序上相比较只用变量或只用函数使用面向对象的思想可以更好地模拟现实生活中的事物。 不仅如此在 Python 中所有的变量其实也都是对象包括整形int、浮点型float、字符串str、列表(list)、元组(tuple)、字典dict和集合set。以字典dict为例它包含多个函数供我们使用例如使用 keys() 获取字典中所有的键使用 values() 获取字典中所有的值使用 item() 获取字典中所有的键值对等等。
面向对象相关术语
在系统学习面向对象编程之前初学者要了解有关面向对象的一些术语。当和其他人讨论代码的时候或者尝试查找我们遇到的问题的解决方案时知道正确的术语会很有帮助。 面向对象中常用术语包括
类可以理解是一个模板通过它可以创建出无数个具体实例。比如前面编写的 tortoise 表示的只是乌龟这个物种通过它可以创建出无数个实例来代表各种不同特征的乌龟这一过程又称为类的实例化。对象类并不能直接使用通过类创建出的实例又称对象才能使用。这有点像汽车图纸和汽车的关系图纸本身类并不能为人们使用通过图纸创建出的一辆辆车对象才能使用。属性类中的所有变量称为属性。例如tortoise 这个类中bodyColor、footNum、weight、hasShell 都是这个类拥有的属性。方法类中的所有函数通常称为方法。不过和函数所有不同的是类方法至少要包含一个 self 参数后续会做详细介绍。例如tortoise 类中crawl()、eat()、sleep()、protect() 都是这个类所拥有的方法类方法无法单独使用只能和类的对象一起使用。
二、Python class定义类入门必读
前面已经提到类仅仅充当图纸的作用本身并不能直接拿来用而只有根据图纸造出的实际物品对象才能直接使用。因此Python 程序中类的使用顺序是这样的
创建定义类也就是制作图纸的过程创建类的实例对象根据图纸造出实际的物品通过实例对象实现特定的功能。
本节先教大家如何创建定义一个类如何使用定义好的类将放到后续章节进行讲解。
Python类的定义
Python 中定义一个类使用 class 关键字实现其基本语法格式如下
class 类名多个≥0类属性...多个≥0类方法...
注意无论是类属性还是类方法对于类来说它们都不是必需的可以有也可以没有。另外Python 类中属性和方法所在的位置是任意的即它们之间并没有固定的前后次序。 和变量名一样类名本质上就是一个标识符因此我们在给类起名字时必须让其符合 Python 的语法。有读者可能会问用 a、b、c 作为类的类名可以吗从 Python 语法上讲是完全没有问题的但作为一名合格的程序员我们必须还要考虑程序的可读性。 因此在给类起名字时最好使用能代表该类功能的单词例如用“Student”作为学生类的类名甚至如果必要可以使用多个单词组合而成例如初学者定义的第一个类的类名可以是“TheFirstDemo”。 注意如果由单词构成类名建议每个单词的首字母大写其它字母小写。 给类起好名字之后其后要跟有冒号表示告诉 Python 解释器下面要开始设计类的内部功能了也就是编写类属性和类方法。 其实类属性指的就是包含在类中的变量而类方法指的是包含类中的函数。换句话说类属性和类方法其实分别是包含类中的变量和函数的别称。需要注意的一点是同属一个类的所有类属性和类方法要保持统一的缩进格式通常统一缩进 4 个空格。 Python 变量和函数的使用前面已经做了详细的介绍这里不再重复赘述。 通过上面的分析可以得出这样一个结论即 Python 类是由类头class 类名和类体统一缩进的变量和函数构成。例如下面程序定义一个 TheFirstDemo 类
class TheFirstDemo:这是一个学习Python定义的第一个类# 下面定义了一个类属性add http://c.biancheng.net# 下面定义了一个say方法def say(self, content):print(content)
和函数一样我们也可以为类定义说明文档其要放到类头之后类体之前的位置如上面程序中第二行的字符串就是 TheFirstDemo 这个类的说明文档。 另外分析上面的代码可以看到我们创建了一个名为 TheFirstDemo 的类其包含了一个名为 add 的类属性。注意根据定义属性位置的不同在各个类方法之外定义的变量称为类属性或类变量如 add 属性而在类方法中定义的属性称为实例属性或实例变量它们的区别和用法可阅读六、Python类变量和实例变量类属性和实例属性。 同时TheFirstDemo 类中还包含一个 say() 类方法细心的读者可能已经看到该方法包含两个参数分别是 self 和 content。可以肯定的是content 参数就只是一个普通参数没有特殊含义但 self 比较特殊并不是普通的参数它的作用会在后续章节中详细介绍。 更确切地说say() 是一个实例方法除此之外Python 类中还可以定义类方法和静态方法这 3 种类方法的区别和具体用法可阅读六、Python类变量和实例变量类属性和实例属性。 事实上我们完全可以创建一个没有任何类属性和类方法的类换句话说Python 允许创建空类例如
class Empty:pass
可以看到如果一个类没有任何类属性和类方法那么可以直接用 pass 关键字作为类体即可。但在实际应用中很少会创建空类因为空类没有任何实际意义。
三、Python __init__()类构造方法
在创建类时我们可以手动添加一个 __init__() 方法该方法是一个特殊的类实例方法称为构造方法或构造函数。 构造方法用于创建对象时使用每当创建一个类的实例对象时Python 解释器都会自动调用它。Python 类中手动添加构造方法的语法格式如下
def __init__(self,...):代码块
注意此方法的方法名中开头和结尾各有 2 个下划线且中间不能有空格。Python 中很多这种以双下划线开头、双下划线结尾的方法都具有特殊的意义后续会一一为大家讲解。 另外__init__() 方法可以包含多个参数但必须包含一个名为 self 的参数且必须作为第一个参数。也就是说类的构造方法最少也要有一个 self 参数。例如仍以 TheFirstDemo 类为例添加构造方法的代码如下所示
class TheFirstDemo:这是一个学习Python定义的第一个类#构造方法def __init__(self):print(调用构造方法)# 下面定义了一个类属性add http://c.biancheng.net# 下面定义了一个say方法def say(self, content):print(content)
注意即便不手动为类添加任何构造方法Python 也会自动为类添加一个仅包含 self 参数的构造方法。 仅包含 self 参数的 __init__() 构造方法又称为类的默认构造方法。 在上面代码的后面顶头不缩进直接添加如下代码 zhangsan TheFirstDemo() 这行代码的含义是创建一个名为 zhangsan 的 TheFirstDemo 类对象。运行代码可看到如下结果 调用构造方法 显然在创建 zhangsan 这个对象时隐式调用了我们手动创建的 __init__() 构造方法。 不仅如此在 __init__() 构造方法中除了 self 参数外还可以自定义一些参数参数之间使用逗号“,”进行分割。例如下面的代码在创建 __init__() 方法时额外指定了 2 个参数
class CLanguage:这是一个学习Python定义的一个类def __init__(self,name,add):print(name,的网址为:,add)
#创建 add 对象并传递参数给构造函数
add CLanguage(C语言中文网,http://c.biancheng.net)
注意由于创建对象时会调用类的构造方法如果构造函数有多个参数时需要手动传递参数传递方式如代码中所示后续部分会做详细讲解。 运行以上代码执行结果为 C语言中文网 的网址为: http://c.biancheng.net 可以看到虽然构造方法中有 self、name、add 3 个参数但实际需要传参的仅有 name 和 add也就是说self 不需要手动传递参数。 关于 self 参数第五部分会做详细介绍这里只需要知道在创建类对象时无需给 self 传参即可。 四、Python类对象的创建和使用
通过前面章节的学习我们已经学会如何定义一个类但要想使用它必须创建该类的对象。 创建类对象的过程又称为类的实例化。 1、Python类的实例化
对已定义好的类进行实例化其语法格式如下 类名(参数) 定义类时如果没有手动添加 __init__() 构造方法又或者添加的 __init__() 中仅有一个 self 参数则创建类对象时的参数可以省略不写。 例如如下代码创建了名为 CLanguage 的类并对其进行了实例化
class CLanguage :# 下面定义了2个类变量name C语言中文网add http://c.biancheng.netdef __init__(self,name,add):#下面定义 2 个实例变量self.name nameself.add addprint(name,网址为,add)# 下面定义了一个say实例方法def say(self, content):print(content)
# 将该CLanguage对象赋给clanguage变量
clanguage CLanguage(C语言中文网,http://c.biancheng.net)
在上面的程序中由于构造方法除 self 参数外还包含 2 个参数且这 2 个参数没有设置默认参数因此在实例化类对象时需要传入相应的 name 值和 add 值self 参数是特殊参数不需要手动传值Python 会自动传给它值。 类变量和实例变量简单地理解定义在各个类方法之外包含在类中的变量为类变量或者类属性定义在类方法中的变量为实例变量或者实例属性二者的具体区别和用法可阅读第六部分、Python类变量和实例变量。 2、Python类对象的使用
定义的类只有进行实例化也就是使用该类创建对象之后才能得到利用。总的来说实例化后的类对象可以执行以下操作
访问或修改类对象具有的实例变量甚至可以添加新的实例变量或者删除已有的实例变量调用类对象的方法包括调用现有的方法以及给类对象动态添加方法。
1类对象访问变量或方法
使用已创建好的类对象访问类中实例变量的语法格式如下 类对象名.变量名 使用类对象调用类中方法的语法格式如下 对象名.方法名(参数) 注意对象名和变量名以及方法名之间用点 . 连接。 例如下面代码演示了如何通过 clanguage 对象调用类中的实例变量和方法
clanguage CLanguage(C语言中文网,http://c.biancheng.net)
#输出name和add实例变量的值
print(clanguage.name,clanguage.add)
#修改实例变量的值
clanguage.namePython教程
clanguage.addhttp://c.biancheng.net/python
#调用clanguage的say()方法
clanguage.say(人生苦短我用Python)
#再次输出name和add的值
print(clanguage.name,clanguage.add)
程序运行结果为 C语言中文网 网址为 http://c.biancheng.net
C语言中文网 http://c.biancheng.net
人生苦短我用Python
Python教程 http://c.biancheng.net/python 2给类对象动态添加/删除变量
Python 支持为已创建好的对象动态增加实例变量方法也很简单举个例子
# 为clanguage对象增加一个money实例变量
clanguage.money 159.9
print(clanguage.money)
运行结果为 159.9 可以看到通过直接增加一个新的实例变量并为其赋值就成功地为 clanguage 对象添加了 money 变量。 既然能动态添加那么是否能动态删除呢答案是肯定的使用 del 语句即可实现例如
#删除新添加的 money 实例变量
del clanguage.money
#再次尝试输出 money此时会报错
print(clanguage.money)
运行程序会发现结果显示 AttributeError 错误
Traceback (most recent call last):File C:/Users/mengma/Desktop/1.py, line 29, in moduleprint(clanguage.money)
AttributeError: CLanguage object has no attribute money
3给类对象动态添加方法 注意初学者在理解下面内容之前需明白 self 参数的含义和作用可阅读《Python self用法》详细了解。 Python 也允许为对象动态增加方法。以本节开头的 Clanguage 类为例由于其内部只包含一个 say() 方法因此该类实例化出的 clanguage 对象也只包含一个 say() 方法。但其实我们还可以为 clanguage 对象动态添加其它方法。 需要注意的一点是为 clanguage 对象动态增加的方法Python 不会自动将调用者自动绑定到第一个参数即使将第一个参数命名为 self 也没用。例如如下代码
# 先定义一个函数
def info(self):print(---info函数---, self)
# 使用info对clanguage的foo方法赋值动态绑定方法
clanguage.foo info
# Python不会自动将调用者绑定到第一个参数
# 因此程序需要手动将调用者绑定为第一个参数
clanguage.foo(clanguage) # ①# 使用lambda表达式为clanguage对象的bar方法赋值动态绑定方法
clanguage.bar lambda self: print(--lambda表达式--, self)
clanguage.bar(clanguage) # ②
上面的第 5 行和第 11 行代码分别使用函数、lambda 表达式为 clanguage 对象动态增加了方法但对于动态增加的方法Python 不会自动将方法调用者绑定到它们的第一个参数因此程序必须手动为第一个参数传入参数值如上面程序中 ① 号、② 号代码所示。 有读者可能会问有没有不用手动给 self 传值的方法呢通过借助 types 模块下的 MethodType 可以实现仍以上面的 info() 函数为例
def info(self,content):print(C语言中文网地址为%s % content)
# 导入MethodType
from types import MethodType
clanguage.info MethodType(info, clanguage)
# 第一个参数已经绑定了无需传入
clanguage.info(http://c.biancheng.net)
可以看到由于使用 MethodType 包装 info() 函数时已经将该函数的 self 参数绑定为 clanguage因此后续再使用 info() 函数时就不用再给 self 参数绑定值了。
五、Python self用法详解
在定义类的过程中无论是显式创建类的构造方法还是向类中添加实例方法都要求将 self 参数作为方法的第一个参数。例如定义一个 Person 类
class Person:def __init__(self): print(正在执行构造方法) # 定义一个study()实例方法 def study(self,name): print(name,正在学Python)
那么self 到底扮演着什么样的角色呢本节就对 self 参数做详细的介绍。 事实上Python 只是规定无论是构造方法还是实例方法最少要包含一个参数并没有规定该参数的具体名称。之所以将其命名为 self只是程序员之间约定俗成的一种习惯遵守这个约定可以使我们编写的代码具有更好的可读性大家一看到 self就知道它的作用。 那么self 参数的具体作用是什么呢打个比方如果把类比作造房子的图纸那么类实例化后的对象是真正可以住的房子。根据一张图纸类我们可以设计出成千上万的房子类对象每个房子长相都是类似的都有相同的类变量和类方法但它们都有各自的主人那么如何对它们进行区分呢 当然是通过 self 参数它就相当于每个房子的门钥匙可以保证每个房子的主人仅能进入自己的房子每个类对象只能调用自己的类变量和类方法。 如果你接触过其他面向对象的编程语言例如 C其实 Python 类方法中的 self 参数就相当于 C 中的 this 指针。 也就是说同一个类可以产生多个对象当某个对象调用类方法时该对象会把自身的引用作为第一个参数自动传给该方法换句话说Python 会自动绑定类方法的第一个参数指向调用该方法的对象。如此Python解释器就能知道到底要操作哪个对象的方法了。 因此程序在调用实例方法和构造方法时不需要手动为第一个参数传值。例如更改前面的 Person 类如下所示
class Person:def __init__(self):print(正在执行构造方法)# 定义一个study()实例方法def study(self):print(self,正在学Python)
zhangsan Person()
zhangsan.study()
lisi Person()
lisi.study()
上面代码中study() 中的 self 代表该方法的调用者即谁调用该方法那么 self 就代表谁。因此该程序的运行结果为
正在执行构造方法
__main__.Person object at 0x0000021ADD7D21D0 正在学Python
正在执行构造方法
__main__.Person object at 0x0000021ADD7D2E48 正在学Python
另外对于构造函数中的 self 参数其代表的是当前正在初始化的类对象。举个例子
class Person:name xxxdef __init__(self,name):self.namenamezhangsan Person(zhangsan)
print(zhangsan.name)
lisi Person(lisi)
print(lisi.name)
运行结果为 zhangsan
lisi 可以看到zhangsan 在进行初始化时调用的构造函数中 self 代表的是 zhangsan而 lisi 在进行初始化时调用的构造函数中 self 代表的是 lisi。 值得一提的是除了类对象可以直接调用类方法还有一种函数调用的方式例如
class Person:def who(self):print(self)
zhangsan Person()
#第一种方式
zhangsan.who()
#第二种方式
who zhangsan.who
who()#通过 who 变量调用zhangsan对象中的 who() 方法
运行结果为
__main__.Person object at 0x0000025C26F021D0
__main__.Person object at 0x0000025C26F021D0
显然无论采用哪种方法self 所表示的都是实际调用该方法的对象。 总之无论是类中的构造函数还是普通的类方法实际调用它们的谁则第一个参数 self 就代表谁。 六、Python类变量和实例变量类属性和实例属性
无论是类属性还是类方法都无法像普通变量或者函数那样在类的外部直接使用它们。我们可以将类看做一个独立的空间则类属性其实就是在类体中定义的变量类方法是在类体中定义的函数。 前面章节提到过在类体中根据变量定义的位置不同以及定义的方式不同类属性又可细分为以下 3 种类型
类体中、所有函数之外此范围定义的变量称为类属性或类变量类体中所有函数内部以“self.变量名”的方式定义的变量称为实例属性或实例变量类体中所有函数内部以“变量名变量值”的方式定义的变量称为局部变量。 不仅如此类方法也可细分为实例方法、静态方法和类方法第七部分会做详细介绍。 那么类变量、实例变量以及局部变量之间有哪些不同呢接下来就围绕此问题做详细地讲解。
1、类变量类属性
类变量指的是在类中但在各个类方法外定义的变量。举个例子
class CLanguage :# 下面定义了2个类变量name C语言中文网add http://c.biancheng.net# 下面定义了一个say实例方法def say(self, content):print(content)
上面程序中name 和 add 就属于类变量。 类变量的特点是所有类的实例化对象都同时共享类变量也就是说类变量在所有实例化对象中是作为公用资源存在的。类方法的调用方式有 2 种既可以使用类名直接调用也可以使用类的实例化对象调用。 比如在 CLanguage 类的外部添加如下代码
#使用类名直接调用
print(CLanguage.name)
print(CLanguage.add)
#修改类变量的值
CLanguage.name Python教程
CLanguage.add http://c.biancheng.net/python
print(CLanguage.name)
print(CLanguage.add)
程序运行结果为 C语言中文网
http://c.biancheng.net
Python教程
http://c.biancheng.net/python 可以看到通过类名不仅可以调用类变量也可以修改它的值。 当然也可以使用类对象来调用所属类中的类变量此方式不推荐使用原因后续会讲。例如在 CLanguage 类的外部添加如下代码
clang CLanguage()
print(clang.name)
print(clang.add)
运行程序结果为 C语言中文网
http://c.biancheng.net 注意因为类变量为所有实例化对象共有通过类名修改类变量的值会影响所有的实例化对象。例如在 CLanguage 类体外部添加如下代码
print(修改前各类对象中类变量的值)
clang1 CLanguage()
print(clang1.name)
print(clang1.add)
clang2 CLanguage()
print(clang2.name)
print(clang2.add)print(修改后各类对象中类变量的值)
CLanguage.name Python教程
CLanguage.add http://c.biancheng.net/python
print(clang1.name)
print(clang1.add)
print(clang2.name)
print(clang2.add)
程序运行结果为 修改前各类对象中类变量的值
C语言中文网
http://c.biancheng.net
C语言中文网
http://c.biancheng.net
修改后各类对象中类变量的值
Python教程
http://c.biancheng.net/python
Python教程
http://c.biancheng.net/python 显然通过类名修改类变量会作用到所有的实例化对象例如这里的 clang1 和 clang2。 注意通过类对象是无法修改类变量的。通过类对象对类变量赋值其本质将不再是修改类变量的值而是在给该对象定义新的实例变量在讲实例变量时会进行详细介绍。 值得一提的是除了可以通过类名访问类变量之外还可以动态地为类和对象添加类变量。例如在 CLanguage 类的基础上添加以下代码
clang CLanguage()
CLanguage.catalog 13
print(clang.catalog)
运行结果为 13 2、实例变量实例属性
实例变量指的是在任意类方法内部以“self.变量名”的方式定义的变量其特点是只作用于调用方法的对象。另外实例变量只能通过对象名访问无法通过类名访问。 举个例子
class CLanguage :def __init__(self):self.name C语言中文网self.add http://c.biancheng.net# 下面定义了一个say实例方法def say(self):self.catalog 13
此 CLanguage 类中name、add 以及 catalog 都是实例变量。其中由于 __init__() 函数在创建类对象时会自动调用而 say() 方法需要类对象手动调用。因此CLanguage 类的类对象都会包含 name 和 add 实例变量而只有调用了 say() 方法的类对象才包含 catalog 实例变量。 例如在上面代码的基础上添加如下语句
clang CLanguage()
print(clang.name)
print(clang.add)
#由于 clang 对象未调用 say() 方法因此其没有 catalog 变量下面这行代码会报错
#print(clang.catalog)clang2 CLanguage()
print(clang2.name)
print(clang2.add)
#只有调用 say()才会拥有 catalog 实例变量
clang2.say()
print(clang2.catalog)
运行结果为 C语言中文网
http://c.biancheng.net
C语言中文网
http://c.biancheng.net
13 前面讲过通过类对象可以访问类变量但无法修改类变量的值。这是因为通过类对象修改类变量的值不是在给“类变量赋值”而是定义新的实例变量。例如在 CLanguage 类体外添加如下程序 clang CLanguage()
#clang访问类变量
print(clang.name)
print(clang.add)clang.name Python教程
clang.add http://c.biancheng.net/python
#clang实例变量的值
print(clang.name)
print(clang.add)
#类变量的值
print(CLanguage.name)
print(CLanguage.add) 程序运行结果为 C语言中文网
http://c.biancheng.net
Python教程
http://c.biancheng.net/python
C语言中文网
http://c.biancheng.net 显然通过类对象是无法修改类变量的值的本质其实是给 clang 对象新添加 name 和 add 这 2 个实例变量。 类中实例变量和类变量可以同名但这种情况下使用类对象将无法调用类变量它会首选实例变量这也是不推荐“类变量使用对象名调用”的原因。 另外和类变量不同通过某个对象修改实例变量的值不会影响类的其它实例化对象更不会影响同名的类变量。例如 class CLanguage :name xxx #类变量add http:// #类变量def __init__(self):self.name C语言中文网 #实例变量self.add http://c.biancheng.net #实例变量# 下面定义了一个say实例方法def say(self):self.catalog 13 #实例变量
clang CLanguage()
#修改 clang 对象的实例变量
clang.name python教程
clang.add http://c.biancheng.net/python
print(clang.name)
print(clang.add)clang2 CLanguage()
print(clang2.name)
print(clang2.add)
#输出类变量的值
print(CLanguage.name)
print(CLanguage.add) 程序运行结果为 python教程
http://c.biancheng.net/python
C语言中文网
http://c.biancheng.net
xxx
http:// 不仅如此Python 只支持为特定的对象添加实例变量。例如在之前代码的基础上为 clang 对象添加 money 实例变量实现代码为 clang.money 30
print(clang.money) 3、局部变量
除了实例变量类方法中还可以定义局部变量。和前者不同局部变量直接以“变量名值”的方式进行定义例如 class CLanguage :# 下面定义了一个say实例方法def count(self,money):sale 0.8*moneyprint(优惠后的价格为,sale)
clang CLanguage()
clang.count(100) 通常情况下定义局部变量是为了所在类方法功能的实现。需要注意的一点是局部变量只能用于所在函数中函数执行完成后局部变量也会被销毁。
七、Python实例方法、静态方法和类方法详解包含区别和用法
和类属性一样类方法也可以进行更细致的划分具体可分为类方法、实例方法和静态方法。 和类属性的分类不同对于初学者来说区分这 3 种类方法是非常简单的即采用 classmethod 修饰的方法为类方法采用 staticmethod 修饰的方法为静态方法不用任何修改的方法为实例方法。 其中 classmethod 和 staticmethod 都是函数装饰器后续会对其做详细介绍。 接下来就给大家详细的介绍这 3 种类方法。
1、Python类实例方法
通常情况下在类中定义的方法默认都是实例方法。前面章节中我们已经定义了不只一个实例方法。不仅如此类的构造方法理论上也属于实例方法只不过它比较特殊。 比如下面的类中就用到了实例方法 class CLanguage:#类构造方法也属于实例方法def __init__(self):self.name C语言中文网self.add http://c.biancheng.net# 下面定义了一个say实例方法def say(self):print(正在调用 say() 实例方法) 实例方法最大的特点就是它最少也要包含一个 self 参数用于绑定调用此方法的实例对象Python 会自动完成绑定。实例方法通常会用类对象直接调用例如 clang CLanguage()
clang.say() 运行结果 正在调用 say() 实例方法 当然Python 也支持使用类名调用实例方法但此方式需要手动给 self 参数传值。例如 #类名调用实例方法需手动给 self 参数传值
clang CLanguage()
CLanguage.say(clang) 运行结果为 正在调用 say() 实例方法 有关使用类名直接调用实例方法的更多介绍可阅读第八部分Python类调用实例方法一节。 2、Python类方法
Python 类方法和实例方法相似它最少也要包含一个参数只不过类方法中通常将其命名为 clsPython 会自动将类本身绑定给 cls 参数注意绑定的不是类对象。也就是说我们在调用类方法时无需显式为 cls 参数传参。
和 self 一样cls 参数的命名也不是规定的可以随意命名只是 Python 程序员约定俗称的习惯而已。
和实例方法最大的不同在于类方法需要使用classmethod修饰符进行修饰例如 class CLanguage:#类构造方法也属于实例方法def __init__(self):self.name C语言中文网self.add http://c.biancheng.net#下面定义了一个类方法classmethoddef info(cls):print(正在调用类方法,cls) 注意如果没有 classmethod则 Python 解释器会将 fly() 方法认定为实例方法而不是类方法。 类方法推荐使用类名直接调用当然也可以使用实例对象来调用不推荐。例如在上面 CLanguage 类的基础上在该类外部添加如下代码 #使用类名直接调用类方法
CLanguage.info()
#使用类对象调用类方法
clang CLanguage()
clang.info() 运行结果为 正在调用类方法 class __main__.CLanguage
正在调用类方法 class __main__.CLanguage 3、Python类静态方法
静态方法其实就是我们学过的函数和函数唯一的区别是静态方法定义在类这个空间类命名空间中而函数则定义在程序所在的空间全局命名空间中。 静态方法没有类似 self、cls 这样的特殊参数因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此类的静态方法中无法调用任何类属性和类方法。 静态方法需要使用staticmethod修饰例如 class CLanguage:staticmethoddef info(name,add):print(name,add) 静态方法的调用既可以使用类名也可以使用类对象例如 #使用类名直接调用静态方法
CLanguage.info(C语言中文网,http://c.biancheng.net)
#使用类对象调用静态方法
clang CLanguage()
clang.info(Python教程,http://c.biancheng.net/python) 运行结果为 C语言中文网 http://c.biancheng.net
Python教程 http://c.biancheng.net/python 在实际编程中几乎不会用到类方法和静态方法因为我们完全可以使用函数代替它们实现想要的功能但在一些特殊的场景中例如工厂模式中使用类方法和静态方法也是很不错的选择。
八、Python类调用实例方法
通过前面的学习类方法大体分为 3 类分别是类方法、实例方法和静态方法其中实例方法用的是最多的。我们知道实例方法的调用方式其实有 2 种既可以采用类对象调用也可以直接通过类名调用。 通常情况下我们习惯使用类对象调用类中的实例方法。但如果想用类调用实例方法不能像如下这样 class CLanguage: def info(self): print(我正在学 Python)
#通过类名直接调用实例方法
CLanguage.info() 运行上面代码程序会报出如下错误 Traceback (most recent call last):File D:\python3.6\demo.py, line 5, in moduleCLanguage.info()
TypeError: info() missing 1 required positional argument: self 其中最后一行报错信息提示我们调用 info() 类方式时缺少给 self 参数传参。这意味着和使用类对象调用实例方法不同通过类名直接调用实例方法时Python 并不会自动给 self 参数传值。 读者想想也应该明白self 参数需要的是方法的实际调用者是类对象而这里只提供了类名当然无法自动传值。 因此如果想通过类名直接调用实例方法就必须手动为 self 参数传值。例如修改上面的代码为 class CLanguage:def info(self):print(我正在学 Python)
clang CLanguage()
#通过类名直接调用实例方法
CLanguage.info(clang) 再次运行程序结果为 我正在学 Python 可以看到通过手动将 clang 这个类对象传给了 self 参数使得程序得以正确执行。实际上这里调用实例方法的形式完全是等价于 clang.info()。 值得一提的是上面的报错信息只是让我们手动为 self 参数传值但并没有规定必须传一个该类的对象其实完全可以任意传入一个参数例如 class CLanguage:def info(self):print(self,正在学 Python)
#通过类名直接调用实例方法
CLanguage.info(zhangsan) 运行结果为 zhangsan 正在学 Python 可以看到zhangsan 这个字符串传给了 info() 方法的 self 参数。显然无论是 info() 方法中使用 self 参数调用其它类方法还是使用 self 参数定义新的实例变量胡乱的给 self 参数传参都将会导致程序运行崩溃。 总的来说Python 中允许使用类名直接调用实例方法但必须手动为该方法的第一个 self 参数传递参数这种调用方法的方式被称为“非绑定方法”。 用类的实例对象访问类成员的方式称为绑定方法而用类名调用类成员的方式称为非绑定方法。 九、浅谈Python类命名空间 为什么说Python类是独立的命名空间
前面已经不只一次提到Python 类体中的代码位于独立的命名空间称为类命名空间中。换句话说所有用 class 关键字修饰的代码块都可以看做是位于独立的命名空间中。 和类命名空间相对的是全局命名空间即整个 Python 程序默认都位于全局命名空间中。而类体则独立位于类命名空间中。 我们一开始学习类时就已经提到类其实是由多个类属性和类方法构成而类属性其实就是定义在类这个独立空间中的变量而类方法其实就是定义在类空间中的函数和定义在全局命名空间中的变量和函数相比并没有明显的不同。 举个例子 #全局空间定义变量
name C语言中文网
add http://c.biancheng.net
# 全局空间定义函数
def say ():print(我在学习Python--全局)class CLanguage:# 定义CLanguage空间的say函数def say():print(我在学习Python--CLanguage独立空间)# 定义CLanguage空间的catalog变量name C语言中文网add http://c.biancheng.net
#调用全局的变量和函数
print(name,add)
say()#调用类独立空间的变量和函数
print(CLanguage.name,CLanguage.add)
CLanguage.say() 运行结果为 C语言中文网 http://c.biancheng.net
我在学习Python--全局
C语言中文网 http://c.biancheng.net
我在学习Python--CLanguage独立空间 可以看到相比位于全局命名空间的变量和函数位于类命名空间中的变量和函数在使用时只需要标注 CLanguage 前缀即可。 甚至Python 还允许直接在类命名空间中编写可执行程序例如输出语句、分支语句等例如 class CLanguage:#直接编写可执行代码print(正在执行 CLanguage 类空间中的代码)for i in range(5):print(i) 运行结果为 正在执行 CLanguage 类空间中的代码
0
1
2
3
4 显然上面这些位于类命名空间的可执行程序和位于全局命令空间相比并没有什么不同。 但需要注意的一点是当使用类对象调用类方法时在传参方面是和外界的函数有区别的因为 Python 会自动为第一个参数绑定方法的调用者而位于全局空间中的函数则必须显式为第一个参数传递参数。 十、什么是描述符Python描述符详解
Python 中通过使用描述符可以让程序员在引用一个对象属性时自定义要完成的工作。 本质上看描述符就是一个类只不过它定义了另一个类中属性的访问方式。换句话说一个类可以将属性管理全权委托给描述符类。 描述符是 Python 中复杂属性访问的基础它在内部被用于实现 property、方法、类方法、静态方法和 super 类型。 描述符类基于以下 3 个特殊方法换句话说这 3 个方法组成了描述符协议
__set__(self, obj, typeNone)在设置属性时将调用这一方法本节后续用 setter 表示__get__(self, obj, value)在读取属性时将调用这一方法本节后续用 getter 表示__delete__(self, obj)对属性调用 del 时将调用这一方法。
其中实现了 setter 和 getter 方法的描述符类被称为数据描述符反之如果只实现了 getter 方法则称为非数据描述符。 实际上在每次查找属性时描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用注意不要和 __getattr__() 弄混。也就是说每次使用类对象.属性或者 getattr(类对象属性值)的调用方式时都会隐式地调用 __getattribute__()它会按照下列顺序查找该属性
验证该属性是否为类实例对象的数据描述符如果不是就查看该属性是否能在类实例对象的 __dict__ 中找到最后查看该属性是否为类实例对象的非数据描述符。
为了表达清楚这里举个例子 #描述符类
class revealAccess:def __init__(self, initval None, name var):self.val initvalself.name namedef __get__(self, obj, objtype):print(Retrieving,self.name)return self.valdef __set__(self, obj, val):print(updating,self.name)self.val val
class myClass:x revealAccess(10,var x)y 5
m myClass()
print(m.x)
m.x 20
print(m.x)
print(m.y) 运行结果为 Retrieving var x
10
updating var x
Retrieving var x
20
5 从这个例子可以看到如果一个类的某个属性有数据描述符那么每次查找这个属性时都会调用描述符的 __get__() 方法并返回它的值同样每次在对该属性赋值时也会调用 __set__() 方法。
注意虽然上面例子中没有使用 __del__() 方法但也很容易理解当每次使用 del 类对象.属性或者 delattr(类对象属性)语句时都会调用该方法。 除了使用描述符类自定义类属性被调用时做的操作外还可以使用 property() 函数或者 property 装饰器它们会在后续章节做详细介绍。 十一、Python property()函数定义属性
前面章节中我们一直在用“类对象.属性”的方式访问类中定义的属性其实这种做法是欠妥的因为它破坏了类的封装原则。正常情况下类包含的属性应该是隐藏的只允许通过类提供的方法来间接实现对类属性的访问和操作。 因此在不破坏类封装原则的基础上为了能够有效操作类中的属性类中应包含读或写类属性的多个 getter或 setter方法这样就可以通过“类对象.方法(参数)”的方式操作属性例如 class CLanguage: #构造函数 def __init__(self,name): self.name name #设置 name 属性值的函数 def setname(self,name): self.name name #访问nema属性值的函数 def getname(self): return self.name #删除name属性值的函数 def delname(self): self.namexxx
clang CLanguage(C语言中文网)
#获取name属性值
print(clang.getname())
#设置name属性值
clang.setname(Python教程)
print(clang.getname())
#删除name属性值
clang.delname()
print(clang.getname()) 运行结果为 C语言中文网
Python教程
xxx 可能有读者觉得这种操作类属性的方式比较麻烦更习惯使用“类对象.属性”这种方式。 庆幸的是Python 中提供了 property() 函数可以实现在不破坏类封装原则的前提下让开发者依旧使用“类对象.属性”的方式操作类中的属性。 property() 函数的基本使用格式如下 属性名property(fgetNone, fsetNone, fdelNone, docNone) 其中fget 参数用于指定获取该属性值的类方法fset 参数用于指定设置该属性值的方法fdel 参数用于指定删除该属性值的方法最后的 doc 是一个文档字符串用于说明此函数的作用。 注意在使用 property() 函数时以上 4 个参数可以仅指定第 1 个、或者前 2 个、或者前 3 个当前也可以全部指定。也就是说property() 函数中参数的指定并不是完全随意的。 例如修改上面的程序为 name 属性配置 property() 函数 class CLanguage:#构造函数def __init__(self,n):self.__name n#设置 name 属性值的函数def setname(self,n):self.__name n#访问nema属性值的函数def getname(self):return self.__name#删除name属性值的函数def delname(self):self.__namexxx#为name 属性配置 property() 函数name property(getname, setname, delname, 指明出处)
#调取说明文档的 2 种方式
#print(CLanguage.name.__doc__)
help(CLanguage.name)
clang CLanguage(C语言中文网)
#调用 getname() 方法
print(clang.name)
#调用 setname() 方法
clang.namePython教程
print(clang.name)
#调用 delname() 方法
del clang.name
print(clang.name) 运行结果为
Help on property:指明出处C语言中文网
Python教程
xxx
注意在此程序中由于 getname() 方法中需要返回 name 属性如果使用 self.name 的话其本身又被调用 getname()这将会先入无限死循环。为了避免这种情况的出现程序中的 name 属性必须设置为私有属性即使用 __name前面有 2 个下划线。 有关类属性和类方法的属性设置分为共有属性、保护属性、私有属性后续会做详细介绍。 当然property() 函数也可以少传入几个参数。以上面的程序为例我们可以修改 property() 函数如下所示
name property(getname, setname)
这意味着name 是一个可读写的属性但不能删除因为 property() 函数中并没有为 name 配置用于函数该属性的方法。也就是说即便 CLanguage 类中设计有 delname() 函数这种情况下也不能用来删除 name 属性。 同理还可以像如下这样使用 property() 函数
name property(getname) # name 属性可读不可写也不能删除
name property(getname, setname,delname) #name属性可读、可写、也可删除就是没有说明文档
十二、Python property装饰器详解
既要保护类的封装特性又要让开发者可以使用“对象.属性”的方式操作操作类属性除了使用 property() 函数Python 还提供了 property 装饰器。通过 property 装饰器可以直接通过方法名来访问方法不需要在方法名后添加一对“”小括号。 property 的语法格式如下
property
def 方法名(self)代码块
例如定义一个矩形类并定义用 property 修饰的方法操作类中的 area 私有属性代码如下
class Rect:def __init__(self,area):self.__area areapropertydef area(self):return self.__area
rect Rect(30)
#直接通过方法名来访问 area 方法
print(矩形的面积是,rect.area)
运行结果为
矩形的面积为 30
上面程序中使用 property 修饰了 area() 方法这样就使得该方法变成了 area 属性的 getter 方法。需要注意的是如果类中只包含该方法那么 area 属性将是一个只读属性。 也就是说在使用 Rect 类时无法对 area 属性重新赋值即运行如下代码会报错
rect.area 90
print(修改后的面积,rect.area)
运行结果为
Traceback (most recent call last):File C:\Users\mengma\Desktop\1.py, line 10, in modulerect.area 90
AttributeError: cant set attribute
而要想实现修改 area 属性的值还需要为 area 属性添加 setter 方法就需要用到 setter 装饰器它的语法格式如下
方法名.setter
def 方法名(self, value):代码块
例如为 Rect 类中的 area 方法添加 setter 方法代码如下
area.setter
def area(self, value):self.__area value
再次运行如下代码
rect.area 90
print(修改后的面积,rect.area)
运行结果为
修改后的面积: 90
这样area 属性就有了 getter 和 setter 方法该属性就变成了具有读写功能的属性。 除此之外还可以使用 deleter 装饰器来删除指定属性其语法格式为
方法名.deleter
def 方法名(self):代码块
例如在 Rect 类中给 area() 方法添加 deleter 方法实现代码如下
area.deleter
def area(self):self.__area 0
然后运行如下代码
del rect.areaprint(删除后的area值为,rect.area)
运行结果为
删除后的area值为 0
十三、Python封装机制及实现方法
不光是 Python大多数面向对象编程语言诸如 C、Java 等都具备 3 个典型特征即封装、继承和多态。其中本节重点讲解 Python 类的封装特性继承和多态会在后续详细讲解。 简单的理解封装Encapsulation即在设计类时刻意地将一些属性和方法隐藏在类的内部这样在使用此类时将无法直接以“类对象.属性名”或者“类对象.方法名(参数)”的形式调用这些属性或方法而只能用未隐藏的类方法间接操作这些隐藏的属性和方法。 就好比使用电脑我们只需要学会如何使用键盘和鼠标就可以了不用关心内部是怎么实现的因为那是生产和设计人员该操心的。 注意封装绝不是将类中所有的方法都隐藏起来一定要留一些像键盘、鼠标这样可供外界使用的类方法。 那么类为什么要进行封装这样做有什么好处呢 首先封装机制保证了类内部数据结构的完整性因为使用类的用户无法直接看到类中的数据结构只能使用类允许公开的数据很好地避免了外部对内部数据的影响提高了程序的可维护性。 除此之外对一个类实现良好的封装用户只能借助暴露出来的类方法来访问数据我们只需要在这些暴露的方法中加入适当的控制逻辑即可轻松实现用户对类中属性或方法的不合理操作。 并且对类进行良好的封装还可以提高代码的复用性。
Python 类如何进行封装
和其它面向对象的编程语言如 C、Java不同Python 类中的变量和函数不是公有的类似 public 属性就是私有的类似 private这 2 种属性的区别如下
public公有属性的类变量和类函数在类的外部、类内部以及子类后续讲继承特性时会做详细介绍中都可以正常访问private私有属性的类变量和类函数只能在本类内部使用类的外部以及子类都无法使用。
但是Python 并没有提供 public、private 这些修饰符。为了实现类的封装Python 采取了下面的方法
默认情况下Python 类中的变量和方法都是公有public的它们的名称前都没有下划线_如果类中的变量和函数其名称以双下划线“__”开头则该变量函数为私有变量私有函数其属性等同于 private。
除此之外还可以定义以单下划线“_”开头的类属性或者类方法例如 _name、_display(self)这种类属性和类方法通常被视为私有属性和私有方法虽然它们也能通过类对象正常访问但这是一种约定俗称的用法初学者一定要遵守。 注意Python 类中还有以双下划线开头和结尾的类方法例如类的构造函数__init__(self)这些都是 Python 内部定义的用于 Python 内部调用。我们自己定义类属性或者类方法时不要使用这种格式。 例如如下程序示范了 Python 的封装机制
class CLanguage :def setname(self, name):if len(name) 3:raise ValueError(名称长度必须大于3)self.__name namedef getname(self):return self.__name#为 name 配置 setter 和 getter 方法name property(getname, setname)def setadd(self, add):if add.startswith(http://):self.__add addelse:raise ValueError(地址必须以 http:// 开头) def getadd(self):return self.__add#为 add 配置 setter 和 getter 方法add property(getadd, setadd)#定义个私有方法def __display(self):print(self.__name,self.__add)clang CLanguage()
clang.name C语言中文网
clang.add http://c.biancheng.net
print(clang.name)
print(clang.add)
程序运行结果为
C语言中文网
http://c.biancheng.net
上面程序中CLanguage 将 name 和 add 属性都隐藏了起来但同时也提供了可操作它们的“窗口”也就是各自的 setter 和 getter 方法这些方法都是公有public的。 不仅如此以 add 属性的 setadd() 方法为例通过在该方法内部添加控制逻辑即通过调用 startswith() 方法控制用户输入的地址必须以“http://”开头否则程序将会执行 raise 语句抛出 ValueError 异常。 有关 raise 的具体用法后续章节会做详细的讲解这里可简单理解成如果用户输入不规范程序将会报错。 通过此程序的运行逻辑不难看出通过对 CLanguage 类进行良好的封装使得用户仅能通过暴露的 setter() 和 getter() 方法操作 name 和 add 属性而通过对 setname() 和 setadd() 方法进行适当的设计可以避免用户对类中属性的不合理操作从而提高了类的可维护性和安全性。 细心的读者可能还发现CLanguage 类中还有一个 __display() 方法由于该类方法为私有private方法且该类没有提供操作该私有方法的“窗口”因此我们无法在类的外部使用它。换句话说如下调用 __display() 方法是不可行的
#尝试调用私有的 display() 方法
clang.__display()
这会导致如下错误
Traceback (most recent call last):File D:\python3.6\1.py, line 33, in moduleclang.__display()
AttributeError: CLanguage object has no attribute __display 那么类似 __display() 这样的类方法就没有办法调用了吗下一节将会进行描述。 十四、Python封装底层实现原理详解通俗易懂
事实上Python 封装特性的实现纯属“投机取巧”之所以类对象无法直接调用以双下划线开头命名的类属性和类方法是因为其底层实现时Python 偷偷改变了它们的名称。 前面章节中我们定义了一个 CLanguage 类定义如下
class CLanguage :def setname(self, name):if len(name) 3:raise ValueError(名称长度必须大于3)self.__name namedef getname(self):return self.__name#为 name 配置 setter 和 getter 方法name property(getname, setname)def setadd(self, add):if add.startswith(http://):self.__add addelse:raise ValueError(地址必须以 http:// 开头)def getadd(self):return self.__add#为 add 配置 setter 和 getter 方法add property(getadd, setadd)#定义个私有方法def __display(self):print(self.__name,self.__add)
注意在这个类中有一个 __display() 方法由于其是私有方法且该类没有提供任何调用该方法的“接口”因此在目前看来此方法根本无法在类外部调用。也就是说如下调用 __display() 方法是不可行的
clang CLanguage()
#尝试调用私有的 display() 方法
clang.__display()
这会导致如下错误
Traceback (most recent call last):File D:\python3.6\1.py, line 33, in moduleclang.__display()
AttributeError: CLanguage object has no attribute __display
那么是不是类似 display() 这种的私有方法真的没有方法调用吗如果你深入了解 Python 封装机制的底层实现原理就可以调用它。事实上对于以双下划线开头命名的类属性或类方法Python 在底层实现时将它们的名称都偷偷改成了 _类名__属性方法名 的格式。 就以 CLanguage 类中的 __display() 为例Python 在底层将其方法名偷偷改成了“_CLanguage__display()”。例如在 CLanguage 类的基础上执行如下代码
clang CLanguage()
#调用name的setname()方法
clang.name C语言中文网
#调用add的setadd()方法
clang.add http://c.biancheng.net
#直接调用隐藏的display()方法
clang._CLanguage__display()
输出结果为
C语言中文网 http://c.biancheng.net
不仅如此那些原本我们认为是私有的类属性例如 __name 和 __add其底层的名称也改成了“_类名__属性名”的这种格式。例如
clang CLanguage()
clang.name C语言中文网
clang.add http://c.biancheng.net
#直接调用 name 和 add 私有属性
print(clang._CLanguage__name,clang._CLanguage__add)
运行结果为
C语言中文网 http://c.biancheng.net
甚至于我们还可以通过这种方式修改 clang 对象的私有属性例如
clang._CLanguage__name Python教程
clang._CLanguage__add http://c.biancheng.net/python
print(clang._CLanguage__name,clang._CLanguage__add)
输出结果为
Python教程 http://c.biancheng.net/python
总结
Python 类中所有的属性和方法都是公有public属性如果希望 Python 底层修改类属性或者类方法的名称以此将它们隐藏起来只需将它们的名称前添加双下划线“__”即可。
十五、Python继承机制及其使用
Python 类的封装、继承、多态 3 大特性前面部分已经详细介绍了 Python 类的封装本节继续讲解 Python 类的继承机制。 继承机制经常用于创建和现有类功能类似的新类又或是新类只需要在现有类基础上添加一些成员属性和方法但又不想直接将现有类代码复制给新类。也就是说通过使用继承这种机制可以轻松实现类的重复使用。 举个例子假设现有一个 Shape 类该类的 draw() 方法可以在屏幕上画出指定的形状现在需要创建一个 Form 类要求此类不但可以在屏幕上画出指定的形状还可以计算出所画形状的面积。要创建这样的类笨方法是将 draw() 方法直接复制到新类中并添加计算面积的方法。实现代码如下所示
class Shape:def draw(self,content):print(画,content)
class Form:def draw(self,content):print(画,content)def area(self):#....print(此图形的面积为...)
当然还有更简单的方法就是使用类的继承机制。实现方法为让 Form 类继承 Shape 类这样当 Form 类对象调用 draw() 方法时Python 解释器会先去 Form 中找以 draw 为名的方法如果找不到它还会自动去 Shape 类中找。如此我们只需在 Form 类中添加计算面积的方法即可示例代码如下
class Shape:def draw(self,content):print(画,content)
class Form(Shape):def area(self):#....print(此图形的面积为...)
上面代码中class Form(Shape) 就表示 Form 继承 Shape。 Python 中实现继承的类称为子类被继承的类称为父类也可称为基类、超类。因此在上面这个样例中Form 是子类Shape 是父类。子类继承父类时只需在定义子类时将父类可以是多个放在子类之后的圆括号里即可。语法格式如下
class 类名(父类1, 父类2, ...)#类定义部分 注意如果该类没有显式指定继承自哪个类则默认继承 object 类object 类是 Python 中所有类的父类即要么是直接父类要么是间接父类。另外Python 的继承是多继承机制和 C 一样即一个子类可以同时拥有多个直接父类。 注意有读者可能还听说过“派生”这个词汇它和继承是一个意思只是观察角度不同而已。换句话话继承是相对子类来说的即子类继承自父类而派生是相对于父类来说的即父类派生出子类。 了解了继承机制的含义和语法之后下面代码演示了继承机制的用法
class People:def say(self):print(我是一个人名字是,self.name)
class Animal:def display(self):print(人也是高级动物)
#同时继承 People 和 Animal 类
#其同时拥有 name 属性、say() 和 display() 方法
class Person(People, Animal):pass
zhangsan Person()
zhangsan.name 张三
zhangsan.say()
zhangsan.display()
运行结果结果为
我是一个人名字是 张三
人也是高级动物
可以看到虽然 Person 类为空类但由于其继承自 People 和 Animal 这 2 个类因此实际上 Person 并不空它同时拥有这 2 个类所有的属性和方法。 没错子类拥有父类所有的属性和方法即便该属性或方法是私有private的。至于为什么可阅读《Python封装实现原理》一节。 关于Python的多继承
事实上大部分面向对象的编程语言都只支持单继承即子类有且只能有一个父类。而 Python 却支持多继承C也支持多继承。 和单继承相比多继承容易让代码逻辑复杂、思路混乱一直备受争议中小型项目中较少使用后来的 Java、C#、PHP 等干脆取消了多继承。 使用多继承经常需要面临的问题是多个父类中包含同名的类方法。对于这种情况Python 的处置措施是根据子类继承多个父类时这些父类的前后次序决定即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。 举个例子
class People:def __init__(self):self.name Peopledef say(self):print(People类,self.name)class Animal:def __init__(self):self.name Animaldef say(self):print(Animal类,self.name)
#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People, Animal):passzhangsan Person()
zhangsan.name 张三
zhangsan.say()
程序运行结果为
People类 张三
可以看到当 Person 同时继承 People 类和 Animal 类时People 类在前因此如果 People 和 Animal 拥有同名的类方法实际调用的是 People 类中的。 虽然 Python 在语法上支持多继承但逼不得已建议大家不要使用多继承。 十六、Python MRO方法解析顺序详解 在子类中Python到底是如何找到父类的属性和方法的
我们知道Python 类是支持多继承的一个类的方法和属性可能定义在当前类也可能定义在基类。针对这种情况当调用类方法或类属性时就需要对当前类以及它的基类进行搜索以确定方法或属性的位置而搜索的顺序就称为方法解析顺序。方法解析顺序Method Resolution Order简称 MRO。对于只支持单继承的编程语言来说MRO 很简单就是从当前类开始逐个搜索它的父类而对于 Python它支持多继承MRO 相对会复杂一些。 实际上Python 发展至今经历了以下 3 种 MRO 算法分别是
从左往右采用深度优先搜索DFS的算法称为旧式类的 MRO自 Python 2.2 版本开始新式类在采用深度优先搜索算法的基础上对其做了优化自 Python 2.3 版本对新式类采用了 C3 算法。由于 Python 3.x 仅支持新式类所以该版本只使用 C3 算法。 有关旧式类和新式类的讲解可阅读《Python super()使用注意事项》一文。 有读者可能会好奇为什么 MRO 弃用了前两种算法而选择最终的 C3 算法呢原因很简单前 2 种算法都存在一定的问题。
1、旧式类MRO算法
在使用旧式类的 MRO 算法时以下面代码为例程序一:
class A:def method(self):print(CommonA)
class B(A):pass
class C(A):def method(self):print(CommonC)
class D(B, C):pass
print(D().method())
通过分析可以想到此程序中的 4 个类是一个“菱形”继承的关系当使用 D 类对象访问 method() 方法时根据深度优先算法搜索顺序为 D-B-A-C-A。 旧式类的 MRO 可通过使用 inspect 模块中的 getmro(类名) 函数直接获取。例如 inspect.getmro(D) 表示获取 D 类的 MRO。 因此使用旧式类的 MRO 算法最先搜索得到的是基类 A 中的 method() 方法即在 Python 2.x 版本中此程序的运行结果为
CommonA
但是这个结果显然不是想要的我们希望搜索到的是 C 类中的 method() 方法。
2、新式类MRO算法
为解决旧式类 MRO 算法存在的问题Python 2.2 版本推出了新的计算新式类 MRO 的方法它仍然采用从左至右的深度优先遍历但是如果遍历中出现重复的类只保留最后一个。 仍以上面程序为例通过深度优先遍历其搜索顺序为 D-B-A-C-A由于此顺序中有 2 个 A因此仅保留后一个简化后得到最终的搜索顺序为 D-B-C-A。 新式类可以直接通过 类名.__mro__ 的方式获取类的 MRO也可以通过 类名.mro() 的形式旧式类是没有 __mro__ 属性和 mro() 方法的。 可以看到这种 MRO 方式已经能够解决“菱形”继承的问题但是可能会违反单调性原则。所谓单调性原则是指在类存在多继承时子类不能改变基类的 MRO 搜索顺序否则会导致程序发生异常。 例如分析如下程序程序二
class X(object):pass
class Y(object):pass
class A(X,Y):pass
class B(Y,X):pass
class C(A, B):pass
通过进行深度遍历得到搜索顺序为 C-A-X-object-Y-object-B-Y-object-X-object再进行简化相同取后者得到 C-A-B-Y-X-object。 下面来分析这样的搜索顺序是否合理我们来看下各个类中的 MRO
对于 A其搜索顺序为 A-X-Y-object对于 B其搜索顺序为 B-Y-X-object对于 C其搜索顺序为 C-A-B-X-Y-object。
可以看到B 和 C 中X、Y 的搜索顺序是相反的也就是说当 B 被继承时它本身的搜索顺序发生了改变这违反了单调性原则。
3、MRO C3
为解决 Python 2.2 中 MRO 所存在的问题Python 2.3 采用了 C3 方法来确定方法解析顺序。多数情况下如果某人提到 Python 中的 MRO指的都是 C3 算法。 在 Python 2.3 及后续版本中运行程序一得到如下结果
CommonC
运行程序二会产生如下异常
Traceback (most recent call last):File C:\Users\mengma\Desktop\demo.py, line 9, in moduleclass C(A, B):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases X, Y
由此可见C3 可以有效解决前面 2 种算法的问题。那么C3 算法是怎样实现的呢 以程序一为主C3 把各个类的 MRO 记为如下等式
类 AL[A] merge(A , object)类 BL[B] [B] merge(L[A] , [A])类 CL[C] [C] merge(L[A] , [A])类 DL[D] [D] merge(L[B] , L[C] , [B] , [C]) 注意以类 A 等式为例其中 merge 包含的 A 称为 L[A] 的头剩余元素这里仅有一个 object称为尾。 这里的关键在于 merge它的运算方式如下
检查第一个列表的头元素如 L[A] 的头记作 H。若 H 未出现在 merge 中其它列表的尾部则将其输出并将其从所有列表中删除然后回到步骤 1否则取出下一个列表的头部记作 H继续该步骤。
重复上述步骤直至列表为空或者不能再找出可以输出的元素。如果是前一种情况则算法结束如果是后一种情况Python 会抛出异常。 由此可以计算出类 B 的 MRO其计算过程为
L[B] [B] merge(L[A],[A]) [B] merge([A,object],[A]) [B,A] merge([object]) [B,A,object]
同理其他类的 MRO 也可以轻松计算得出。这里不再赘述有兴趣的读者可自行推算。
十七、Python父类方法重写入门必读
前面讲过在 Python 中子类继承了父类那么子类就拥有了父类所有的类属性和类方法。通常情况下子类会在此基础上扩展一些新的类属性和类方法。 但凡事都有例外我们可能会遇到这样一种情况即子类从父类继承得来的类方法中大部分是适合子类使用的但有个别的类方法并不能直接照搬父类的如果不对这部分类方法进行修改子类对象无法使用。针对这种情况我们就需要在子类中重复父类的方法。 举个例子鸟通常是有翅膀的也会飞因此我们可以像如下这样定义个和鸟相关的类
class Bird:#鸟有翅膀def isWing(self):print(鸟有翅膀)#鸟会飞def fly(self):print(鸟会飞)
但是对于鸵鸟来说它虽然也属于鸟类也有翅膀但是它只会奔跑并不会飞。针对这种情况可以这样定义鸵鸟类
class Ostrich(Bird):# 重写Bird类的fly()方法def fly(self):print(鸵鸟不会飞)
可以看到因为 Ostrich 继承自 Bird因此 Ostrich 类拥有 Bird 类的 isWing() 和 fly() 方法。其中isWing() 方法同样适合 Ostrich但 fly() 明显不适合因此我们在 Ostrich 类中对 fly() 方法进行重写。 重写有时又称覆盖是一个意思指的是对类中已有方法的内部实现进行修改。 在上面 2 段代码的基础上添加如下代码并运行
class Bird:#鸟有翅膀def isWing(self):print(鸟有翅膀)#鸟会飞def fly(self):print(鸟会飞)
class Ostrich(Bird):# 重写Bird类的fly()方法def fly(self):print(鸵鸟不会飞)# 创建Ostrich对象
ostrich Ostrich()
#调用 Ostrich 类中重写的 fly() 类方法
ostrich.fly()
运行结果为
鸵鸟不会飞
显然ostrich 调用的是重写之后的 fly() 类方法。
如何调用被重写的方法
事实上如果我们在子类中重写了从父类继承来的类方法那么当在类的外部通过子类对象调用该方法时Python 总是会执行子类中重写的方法。 这就产生一个新的问题即如果想调用父类中被重写的这个方法该怎么办呢 很简单前面讲过Python 中的类可以看做是一个独立空间而类方法其实就是出于该空间中的一个函数。而如果想要全局空间中调用类空间中的函数只需要在调用该函数时备注类名即可。举个例子
class Bird:#鸟有翅膀def isWing(self):print(鸟有翅膀)#鸟会飞def fly(self):print(鸟会飞)
class Ostrich(Bird):# 重写Bird类的fly()方法def fly(self):print(鸵鸟不会飞)# 创建Ostrich对象
ostrich Ostrich()
#调用 Bird 类中的 fly() 方法
Bird.fly(ostrich)
程序运行结果为
鸟会飞
此程序中需要大家注意的一点是使用类名调用其类方法Python 不会为该方法的第一个 self 参数自定绑定值因此采用这种调用方法需要手动为 self 参数赋值。 通过类名调用实例方法的这种方式又被称为未绑定方法。 十八、如何使用Python继承机制子类化内置类型提高开发效率
我们知道Python 中内置有一个 object 类它是所有内置类型的共同祖先也是所有没有显式指定父类的类包括用户自定义的的共同祖先。因此在实际编程过程中如果想实现与某个内置类型具有类似行为的类时最好的方法就是将这个内置类型子类化。内置类型子类化其实就是自定义一个新类使其继承有类似行为的内置类通过重定义这个新类实现指定的功能。 举个例子如下所示创建了一个名为 newDict 的类其中 newDictError 是自定义的异常类
class newDictError(ValueError):如果向newDict 添加重复值则引发此异常class newDict(dict):不接受重复值的字典def __setitem__(self,key,value):if value in self.values():if ((key in self and self[key]!value) or (key not in self)):raise newDictError(这个值已经存在并对应不同的键)super().__setitem__(key,value)
demoDict newDict()
demoDict[key]value
demoDict[other_key]value2
print(demoDict)
demoDict[other_key]value
print(demoDict)
运行结果为
{key: value, other_key: value2}
Traceback (most recent call last):File C:\Users\mengma\Desktop\demo.py, line 15, in moduledemoDict[other_key]valueFile C:\Users\mengma\Desktop\demo.py, line 9, in __setitem__raise newDictError(这个值已经存在并对应不同的键)
newDictError: 这个值已经存在并对应不同的键
可以看到newDict 是 Python 中 dict 类型的子类所以其大部分行为都和 dict 内置类相同唯一不同之处在于newDict 不允许字典中多个键对应相同的值。如果用户试图添加具有相同值的新元素则会引发 newDictError 异常并给出提示信息。 由于目前尚未学习如何处理异常因此这里没有 newDictError 做任何处理异常处理会在后续章节做详细讲解。 另外如果查看现有代码你会发现其实很多类都是对 Python 内置类的部分实现它们作为子类的速度更快代码更整洁。 比如list 类型用来管理序列如果一个类需要在内部处理序列那么就可以对 list 进行子类化示例代码如下
class myList(list):def __init__(self,name):self.name namedef dir(self,nesting 0):offset * nestingprint(%s%s/ % (offset,self.name))for element in self:if hasattr(element , dir):element.dir(nesting 1)else:print(%s %s % (offset,element))demoList myList(C语言中文网)
demoList.append(http://c.biancheng.net)
print(demoList.dir())
运行结果如下
C语言中文网/
http://c.biancheng.net
None
其实除了 Python 中常用的基本内置类型collections 模块中还额外提供了很多有用的容器这些容器可以满足大部分情况。
十九、Python super()函数调用父类的构造方法
前面不止一次讲过Python 中子类会继承父类所有的类属性和类方法。严格来说类的构造方法其实就是实例方法因此毫无疑问父类的构造方法子类同样会继承。 但我们知道Python 是一门支持多继承的面向对象编程语言如果子类继承的多个父类中包含同名的类实例方法则子类对象在调用该方法时会优先选择排在最前面的父类中的实例方法。显然构造方法也是如此。 举个例子
class People:def __init__(self,name):self.name namedef say(self):print(我是人名字为,self.name)class Animal:def __init__(self,food):self.food fooddef display(self):print(我是动物,我吃,self.food)
#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People, Animal):passper Person(zhangsan)
per.say()
#per.display()
运行结果结果为
我是人名字为 zhangsan
上面程序中Person 类同时继承 People 和 Animal其中 People 在前。这意味着在创建 per 对象时其将会调用从 People 继承来的构造函数。因此我们看到上面程序在创建 per 对象的同时还要给 name 属性进行赋值。 但如果去掉最后一行的注释运行此行代码Python 解释器会报如下错误
Traceback (most recent call last):File D:\python3.6\Demo.py, line 18, in moduleper.display()File D:\python3.6\Demo.py, line 11, in displayprint(我是动物,我吃,self.food)
AttributeError: Person object has no attribute food
这是因为从 Animal 类中继承的 display() 方法中需要用到 food 属性的值但由于 People 类的构造方法“遮蔽”了Animal 类的构造方法使得在创建 per 对象时Animal 类的构造方法未得到执行所以程序出错。 反过来也是如此如果将第 13 行代码改为如下形式
class Person(Animal, People)
则在创建 per 对象时会给 food 属性传值。这意味着per.display() 能顺序执行但 per.say() 将会报错。 针对这种情况正确的做法是定义 Person 类自己的构造方法等同于重写第一个直接父类的构造方法。但需要注意如果在子类中定义构造方法则必须在该方法中调用父类的构造方法。 在子类中的构造方法中调用父类构造方法的方式有 2 种分别是
类可以看做一个独立空间在类的外部调用其中的实例方法可以向调用普通函数那样只不过需要额外备注类名此方式又称为未绑定方法使用 super() 函数。但如果涉及多继承该函数只能调用第一个直接父类的构造方法。 也就是说涉及到多继承时在子类构造函数中调用第一个父类构造方法的方式有以上 2 种而调用其它父类构造方法的方式只能使用未绑定方法。 值得一提的是Python 2.x 中super() 函数的使用语法格式如下
super(Class, obj).__init__(...)
其中Class 值得是子类的类名obj 通常指的就是 self。 但在 Python 3.x 中super() 函数有一种更简单的语法格式推荐大家使用这种格式
super().__init__(...)
在掌握 super() 函数用法的基础上我们可以尝试修改上面的程序
class People:def __init__(self,name):self.name namedef say(self):print(我是人名字为,self.name)
class Animal:def __init__(self,food):self.food fooddef display(self):print(我是动物,我吃,self.food)
class Person(People, Animal):#自定义构造方法def __init__(self,name,food):#调用 People 类的构造方法super().__init__(name)#super(Person,self).__init__(name) #执行效果和上一行相同#People.__init__(self,name)#使用未绑定方法调用 People 类构造方法#调用其它父类的构造方法需手动给 self 传值Animal.__init__(self,food)
per Person(zhangsan,熟食)
per.say()
per.display()
运行结果为
我是人名字为 zhangsan
我是动物,我吃 熟食
可以看到Person 类自定义的构造方法中调用 People 类构造方法可以使用 super() 函数也可以使用未绑定方法。但是调用 Animal 类的构造方法只能使用未绑定方法。
二十、Python super()使用注意事项包含新式类和旧式类的区别
切记super()只能在新式类中使用
前面已经讲解了 super() 函数的用法值得一提的是Python 2 中 super() 函数的用法和 Python 3 大致相同唯一的区别在于Python 2 中不能使用零参数形式的格式必须提供至少一个参数。 对于想要编写跨版本兼容代码的程序员来说还要注意一件事即 Python 2 中的 super() 函数只适用于新式类在旧式类中不能使用 super()。 那么什么是旧式类和新式类呢在早期版本的 Python 中所有类并没有一个共同的祖先 object如果定义一个类但没有显式指定其祖先那么就被解释为旧式类例如
class oldStyle1:pass
class oldStyle2:pass
其中oldStyle1 和 oldStyle2 都属于旧式类。 Python 2.x 版本中为了向后兼容保留了旧式类。该版本中的新式类必须显式继承 object 或者其他新式类
class newStyleClass(object):pass
class newStyleClass(newStyleClass):pass
显然以上两个类都属于新式类。而在 Python 3.x 版本中不再保留旧式类的概念。因此没有继承任何其他类的类都隐式地继承自 object。 可以说在 Python 3.x 中显式声明某个类继承自 object 似乎是冗余的。但如果考虑跨版本兼容那么就必须将 object 作为所有基类的祖先因为如果不这么做的话这些类将被解释为旧式类最终会导致难以诊断的问题。
二十一、Python super()使用注意事项
Python 中由于基类不会在 __init__() 中被隐式地调用需要程序员显式调用它们。这种情况下当程序中包含多重继承的类层次结构时使用 super 是非常危险的往往会在类的初始化过程中出现问题。
1、混用super与显式类调用
分析如下程序C 类使用了 __init__() 方法调用它的基类会造成 B 类被调用了 2 次
class A:def __init__(self):print(A,end )super().__init__()
class B:def __init__(self):print(B,end )super().__init__()
class C(A,B):def __init__(self):print(C,end )A.__init__(self)B.__init__(self)
print(MRO:,[x.__name__ for x in C.__mro__])
C()
运行结果为
MRO: [C, A, B, object]
C A B B
出现以上这种情况的原因在于C 的实例调用 A.__init__(self)使得 super(A,self).__init__() 调用了 B.__init__() 方法。换句话说super 应该被用到整个类的层次结构中。 但是有时这种层次结构的一部分位于第三方代码中我们无法确定外部包的这些代码中是否使用 super()因此当需要对某个第三方类进行子类化时最好查看其内部代码以及 MRO 中其他类的内部代码。
2、不同种类的参数
使用 super 的另一个问题是初始化过程中的参数传递。如果没有相同的签名一个类怎么能调用其基类的 __init__() 代码呢这会导致下列问题
class commonBase:def __init__(self):print(commonBase)super().__init__()class base1(commonBase):def __init__(self):print(base1)super().__init__()class base2(commonBase):def __init__(self):print(base2)super().__init__()class myClass(base1,base2):def __init__(self,arg):print(my base)super().__init__(arg)
myClass(10)
运行结果为
my base
Traceback (most recent call last):File C:\Users\mengma\Desktop\demo.py, line 20, in modulemyClass(10)File C:\Users\mengma\Desktop\demo.py, line 19, in __init__super().__init__(arg)
TypeError: __init__() takes 1 positional argument but 2 were given
一种解决方法是使用 *args 和 **kwargs 包装的参数和关键字参数这样即使不使用它们所有的构造函数也会传递所有参数如下所示
class commonBase:def __init__(self,*args,**kwargs):print(commonBase)super().__init__()class base1(commonBase):def __init__(self,*args,**kwargs):print(base1)super().__init__(*args,**kwargs)class base2(commonBase):def __init__(self,*args,**kwargs):print(base2)super().__init__(*args,**kwargs)class myClass(base1,base2):def __init__(self,arg):print(my base)super().__init__(arg)
myClass(10)
运行结果为
my base
base1
base2
commonBase
不过这是一种很糟糕的解决方法由于任何参数都可以传入所有构造函数都可以接受任何类型的参数这会导致代码变得脆弱。另一种解决方法是在 MyClass 中显式地使用特定类的 __init__() 调用但这无疑会导致第一种错误。
3、总结
如果想要避免程序中出现以上的这些问题这里给出几点建议
尽可能避免使用多继承可以使用一些设计模式来替代它super 的使用必须一致即在类的层次结构中要么全部使用 super要么全不用。混用 super 和传统调用是一种混乱的写法如果代码需要兼容 Python 2.x在 Python 3.x 中应该显式地继承自 object。在 Python 2.x 中没有指定任何祖先地类都被认定为旧式类。调用父类时应提前查看类的层次结构也就是使用类的 __mro__ 属性或者 mro() 方法查看有关类的 MRO。
二十二、Python __slots__限制类实例动态添加属性和方法
通过学习《Python类变量和实例变量》一节了解了如何动态的为单个实例对象添加属性甚至如果必要的话还可以为所有的类实例对象统一添加属性通过给类添加属性。 那么Python 是否也允许动态地为类或实例对象添加方法呢答案是肯定的。我们知道类方法又可细分为实例方法、静态方法和类方法Python 语言允许为类动态地添加这 3 种方法但对于实例对象则只允许动态地添加实例方法不能添加类方法和静态方法。 为单个实例对象添加方法不会影响该类的其它实例对象而如果为类动态地添加方法则所有的实例对象都可以使用。 举个例子
class CLanguage:pass
#下面定义了一个实例方法
def info(self):print(正在调用实例方法)
#下面定义了一个类方法
classmethod
def info2(cls):print(正在调用类方法)
#下面定义个静态方法
staticmethod
def info3():print(正在调用静态方法)#类可以动态添加以上 3 种方法会影响所有实例对象
CLanguage.info info
CLanguage.info2 info2
CLanguage.info3 info3clang CLanguage()
#如今clang 具有以上 3 种方法
clang.info()
clang.info2()
clang.info3()#类实例对象只能动态添加实例方法不会影响其它实例对象
clang1 CLanguage()
clang1.info info
#必须手动为 self 传值
clang1.info(clang1)
程序输出结果为
正在调用实例方法
正在调用类方法
正在调用静态方法
正在调用实例方法
显然动态给类或者实例对象添加属性或方法是非常灵活的。但与此同时如果胡乱地使用也会给程序带来一定的隐患即程序中已经定义好的类如果不做任何限制是可以做动态的修改的。 庆幸的是Python 提供了 __slots__ 属性通过它可以避免用户频繁的给实例对象动态地添加属性或方法。 再次声明__slots__ 只能限制为实例对象动态添加属性和方法而无法限制动态地为类添加属性和方法。 __slots__ 属性值其实就是一个元组只有其中指定的元素才可以作为动态添加的属性或者方法的名称。举个例子
class CLanguage:__slots__ (name,add,info)
可以看到 CLanguage 类中指定了 __slots__ 属性这意味着该类的实例对象仅限于动态添加 name、add、info 这 3 个属性以及 name()、add() 和 info() 这 3 个方法。 注意对于动态添加的方法__slots__ 限制的是其方法名并不限制参数的个数。 比如在 CLanguage 类的基础上添加如下代码并运行
def info(self,name):print(正在调用实例方法,self.name)
clang CLanguage()
clang.name C语言中文网
#为 clang 对象动态添加 info 实例方法
clang.info info
clang.info(clang,Python教程)
程序运行结果为
正在调用实例方法 C语言中文网
还是在 CLanguage 类的基础上添加如下代码并运行
def info(self,name):print(正在调用实例方法,self.name)
clang CLanguage()
clang.name C语言中文网
clang.say info
clang.say(clang,Python教程)
运行程序显示如下信息
Traceback (most recent call last):File D:\python3.6\1.py, line 9, in moduleclang.say info
AttributeError: CLanguage object has no attribute say
显然根据 __slots__ 属性的设置CLanguage 类的实例对象是不能动态添加以 say 为名称的方法的。 另外本节前面提到__slots__ 属性限制的对象是类的实例对象而不是类因此下面的代码是合法的
def info(self):print(正在调用实例方法)CLanguage.say info
clang CLanguage()
clang.say()
程序运行结果为
正在调用实例方法 当然还可以为类动态添加类方法和静态方法这里不再给出具体实例读者可自行编写代码尝试。 此外__slots__ 属性对由该类派生出来的子类也是不起作用的。例如如下代码
class CLanguage:__slots__ (name,add,info)
#Clanguage 的空子类
class CLangs(CLanguage):pass
#定义的实例方法
def info(self):print(正在调用实例方法)
clang CLangs()
#为子类对象动态添加 say() 方法
clang.say info
clang.say(clang)
运行结果为
正在调用实例方法
显然__slots__ 属性只对当前所在的类起限制作用。 因此如果子类也要限制外界为其实例对象动态地添加属性和方法必须在子类中设置 __slots__ 属性。 注意如果为子类也设置有 __slots__ 属性那么子类实例对象允许动态添加的属性和方法是子类中 __slots__ 属性和父类 __slots__ 属性的和。 二十三、Python type()函数动态创建类
我们知道type() 函数属于 Python 内置函数通常用来查看某个变量的具体类型。其实type() 函数还有一个更高级的用法即创建一个自定义类型也就是创建一个类。 type() 函数的语法格式有 2 种分别如下
type(obj)
type(name, bases, dict)
以上这 2 种语法格式各参数的含义及功能分别是
第一种语法格式用来查看某个变量类对象的具体类型obj 表示某个变量或者类对象。第二种语法格式用来创建类其中 name 表示类的名称bases 表示一个元组其中存储的是该类的父类dict 表示一个字典用于表示类内定义的属性或者方法。
对于使用 type() 函数查看某个变量或类对象的类型由于很简单这里不再做过多解释直接给出一个样例
#查看 3.4 的类型
print(type(3.4))
#查看类对象的类型
class CLanguage:pass
clangs CLanguage()
print(type(clangs))
输出结果为
class float
class __main__.CLanguage
这里重点介绍 type() 函数的另一种用法即创建一个新类先来分析一个样例
#定义一个实例方法
def say(self):print(我要学 Python)
#使用 type() 函数创建类
CLanguage type(CLanguage,(object,),dict(say say, name C语言中文网))
#创建一个 CLanguage 实例对象
clangs CLanguage()
#调用 say() 方法和 name 属性
clangs.say()
print(clangs.name) 注意Python 元组语法规定当 (object,) 元组中只有一个元素时最后的逗号,不能省略。 可以看到此程序中通过 type() 创建了类其类名为 CLanguage继承自 objects 类且该类中还包含一个 say() 方法和一个 name 属性。 有读者可能会问如何判断 dict 字典中添加的是方法还是属性很简单如果该键值对中值为普通变量如 C语言中文网则表示为类添加了一个类属性反之如果值为外部定义的函数如 say() 则表示为类添加了一个实例方法。 运行上面的程序其输出结果为
我要学 Python
C语言中文网
可以看到使用 type() 函数创建的类和直接使用 class 定义的类并无差别。事实上我们在使用 class 定义类时Python 解释器底层依然是用 type() 来创建这个类。
二十四、Python MetaClass元类详解
MetaClass元类本质也是一个类但和普通类的用法不同它可以对类内部的定义包括类属性和类方法进行动态的修改。可以这么说使用元类的主要目的就是为了实现在创建类时能够动态地改变类中定义的属性或者方法。 不要从字面上去理解元类的含义事实上 MetaClass 中的 Meta 这个词根起源于希腊语词汇 meta包含“超越”和“改变”的意思。 举个例子根据实际场景的需要我们要为多个类添加一个 name 属性和一个 say() 方法。显然有多种方法可以实现但其中一种方法就是使用 MetaClass 元类。 如果在创建类时想用 MetaClass 元类动态地修改内部的属性或者方法则类的创建过程将变得复杂先创建 MetaClass 元类然后用元类去创建类最后使用该类的实例化对象实现功能。 和前面章节创建的类不同如果想把一个类设计成 MetaClass 元类其必须符合以下条件
必须显式继承自 type 类类中需要定义并实现 __new__() 方法该方法一定要返回该类的一个实例对象因为在使用元类创建类时该 __new__() 方法会自动被执行用来修改新建的类。
讲了这么多读者可能对 MetaClass 元类的功能还是比较懵懂。没关系我们先尝试定义一个 MetaClass 元类
#定义一个元类
class FirstMetaClass(type):# cls代表动态修改的类# name代表动态修改的类名# bases代表被动态修改的类的所有父类# attr代表被动态修改的类的所有属性、方法组成的字典def __new__(cls, name, bases, attrs):# 动态为该类添加一个name属性attrs[name] C语言中文网attrs[say] lambda self: print(调用 say() 实例方法)return super().__new__(cls,name,bases,attrs)
此程序中首先可以断定 FirstMetaClass 是一个类。其次由于该类继承自 type 类并且内部实现了 __new__() 方法因此可以断定 FirstMetaCLass 是一个元类。 有关 __new__() 的具体用法可阅读《Python __new__()方法》一节。 可以看到在这个元类的 __new__() 方法中手动添加了一个 name 属性和 say() 方法。这意味着通过 FirstMetaClass 元类创建的类会额外添加 name 属性和 say() 方法。通过如下代码可以验证这个结论
#定义类时指定元类
class CLanguage(object,metaclassFirstMetaClass):passclangs CLanguage()
print(clangs.name)
clangs.say()
可以看到在创建类时通过在标注父类的同时指定元类格式为metaclass元类名则当 Python 解释器在创建这该类时FirstMetaClass 元类中的 __new__ 方法就会被调用从而实现动态修改类属性或者类方法的目的。 运行上面的程序输出结果为
C语言中文网
调用 say() 实例方法
显然FirstMetaClass 元类的 __new__() 方法动态地为 Clanguage 类添加了 name 属性和 say() 方法因此即便该类在定义时是空类它也依然有 name 属性和 say() 方法。 对于 MetaClass 元类它多用于创建 API因此我们几乎不会使用到它。 二十五、Python MetaClass元类实现的底层原理 Python底层是如何实现 MetaClass元类的
要理解 MetaClass 的底层原理首先要深入理解 Python 类型模型。本节将从以下 2 点对 Python 类型模型做详细的介绍。
1、所有的 Python 的用户定义类都是 type 这个类的实例
事实上类本身不过是一个名为 type 类的实例可以通过如下代码进行验证
class MyClass:passinstance MyClass()
print(type(instance))
print(type(MyClass))
输出结果为
class __main__.MyClass
class type
可以看到instance 是 MyClass 的实例而 MyClass 是 type 的实例。
2、用户自定义类只不过是 type 类的 __call__ 运算符重载。
当定义完成一个类时真正发生的情况是 Python 会调用 type 类的 __call__ 运算符。 简单来说当定义一个类时例如下面语句
class MyClass:data 1
Python 底层执行的是下面这段代码
class type(classname, superclasses, attributedict)
其中等号右边的 type(classname, superclasses, attributedict) 就是 type 的 __call__ 运算符重载它会进一步调用下面这 2 个函数
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)
以上整个过程可以通过如下代码进行论证
class MyClass:data 1instance MyClass()
print(MyClass,instance)
print(instance.data)
MyClass type(MyClass, (), {data: 1})
instance MyClass()print(MyClass,instance)
print(instance.data)
运行结果为
class __main__.MyClass __main__.MyClass object at 0x000001CB469F7400
1
class __main__.MyClass __main__.MyClass object at 0x000001CB46A50828
1
由此可见正常的 MyClass 定义和手工调用 type 运算符的结果是完全一样的。 总之正是 Python 的类创建机制给了 metaclass 大展身手的机会即一旦把一个类型 MyClass 设置成元类 MyMeta那么它就不再由原生的 type 创建而是会调用 MyMeta 的 __call__ 运算符重载
class type(classname, superclasses, attributedict)
# 变为了
class MyMeta(classname, superclasses, attributedict)
3、使用 metaclass 的风险
正如上面所看到的那样metaclass 这样“逆天”的存在会扭曲变形正常的 Python 类型模型所以如果使用不慎对于整个代码库造成的风险是不可估量的。 换句话说metaclass 仅仅是给小部分 Python 开发者在开发框架层面的 Python 库时使用的。而在应用层metaclass 往往不是很好的选择。 建议初学者不要轻易尝试使用 mateclass。 二十六、什么是多态Python多态及用法详解
在面向对象程序设计中除了封装和继承特性外多态也是一个非常重要的特性本节就带领大家详细了解什么是多态。 我们都知道Python 是弱类型语言其最明显的特征是在使用变量时无需为其指定具体的数据类型。这会导致一种情况即同一变量可能会被先后赋值不同的类对象例如
class CLanguage:def say(self):print(赋值的是 CLanguage 类的实例对象)
class CPython:def say(self):print(赋值的是 CPython 类的实例对象)
a CLanguage()
a.say()a CPython()
a.say()
运行结果为
赋值的是 CLanguage 类的实例对象
赋值的是 CPython 类的实例对象
可以看到a 可以被先后赋值为 CLanguage 类和 CPython 类的对象但这并不是多态。类的多态特性还要满足以下 2 个前提条件
继承多态一定是发生在子类和父类之间重写子类重写了父类的方法。
下面程序是对上面代码的改写
class CLanguage:def say(self):print(调用的是 Clanguage 类的say方法)
class CPython(CLanguage):def say(self):print(调用的是 CPython 类的say方法)
class CLinux(CLanguage):def say(self):print(调用的是 CLinux 类的say方法)
a CLanguage()
a.say()a CPython()
a.say()a CLinux()
a.say()
程序执行结果为
调用的是 Clanguage 类的say方法
调用的是 CPython 类的say方法
调用的是 CLinux 类的say方法
可以看到CPython 和 CLinux 都继承自 CLanguage 类且各自都重写了父类的 say() 方法。从运行结果可以看出同一变量 a 在执行同一个 say() 方法时由于 a 实际表示不同的类实例对象因此 a.say() 调用的并不是同一个类中的 say() 方法这就是多态。 但是仅仅学到这里读者还无法领略 Python 类使用多态特性的精髓。其实Python 在多态的基础上衍生出了一种更灵活的编程机制。 继续对上面的程序进行改写
class WhoSay:def say(self,who):who.say()
class CLanguage:def say(self):print(调用的是 Clanguage 类的say方法)class CPython(CLanguage):def say(self):print(调用的是 CPython 类的say方法)class CLinux(CLanguage):def say(self):print(调用的是 CLinux 类的say方法)
a WhoSay()
#调用 CLanguage 类的 say() 方法
a.say(CLanguage())
#调用 CPython 类的 say() 方法
a.say(CPython())
#调用 CLinux 类的 say() 方法
a.say(CLinux())
程序执行结果为
调用的是 Clanguage 类的say方法
调用的是 CPython 类的say方法
调用的是 CLinux 类的say方法
此程序中通过给 WhoSay 类中的 say() 函数添加一个 who 参数其内部利用传入的 who 调用 say() 方法。这意味着当调用 WhoSay 类中的 say() 方法时我们传给 who 参数的是哪个类的实例对象它就会调用那个类中的 say() 方法。 在其它教程中Python 这种由多态衍生出的更灵活的编程机制又称为“鸭子模型”或“鸭子类型”。 二十七、Python枚举类定义和使用详解版
一些具有特殊含义的类其实例化对象的个数往往是固定的比如用一个类表示月份则该类的实例对象最多有 12 个再比如用一个类表示季节则该类的实例化对象最多有 4 个。针对这种特殊的类Python 3.4 中新增加了 Enum 枚举类。也就是说对于这些实例化对象个数固定的类可以用枚举类来定义。 例如下面程序演示了如何定义一个枚举类
from enum import Enum
class Color(Enum):# 为序列值指定value值red 1green 2blue 3
如果想将一个类定义为枚举类只需要令其继承自 enum 模块中的 Enum 类即可。例如在上面程序中Color 类继承自 Enum 类则证明这是一个枚举类。 在 Color 枚举类中red、green、blue 都是该类的成员可以理解为是类变量。注意枚举类的每个成员都由 2 部分组成分别为 name 和 value其中 name 属性值为该枚举值的变量名如 redvalue 代表该枚举值的序号序号通常从 1 开始。 和普通类的用法不同枚举类不能用来实例化对象但这并不妨碍我们访问枚举类中的成员。访问枚举类成员的方式有多种例如以 Color 枚举类为例在其基础上添加如下代码
#调用枚举成员的 3 种方式
print(Color.red)
print(Color[red])
print(Color(1))
#调取枚举成员中的 value 和 name
print(Color.red.value)
print(Color.red.name)
#遍历枚举类中所有成员的 2 种方式
for color in Color:print(color)
程序输出结果为
Color.red
Color.red
Color.red
1
red
Color.red
Color.green
Color.blue
枚举类成员之间不能比较大小但可以用 或者 is 进行比较是否相等例如
print(Color.red Color.green)
print(Color.red.name is Color.green.name)
输出结果为
Flase
Flase
需要注意的是枚举类中各个成员的值不能在类的外部做任何修改也就是说下面语法的做法是错误的
Color.red 4
除此之外该枚举类还提供了一个 __members__ 属性该属性是一个包含枚举类中所有成员的字典通过遍历该属性也可以访问枚举类中的各个成员。例如
for name,member in Color.__members__.items():print(name,-,member)
输出结果为
red - Color.red
green - Color.green
blue - Color.blue
值得一提的是Python 枚举类中各个成员必须保证 name 互不相同但 value 可以相同举个例子
from enum import Enumclass Color(Enum):# 为序列值指定value值red 1green 1blue 3
print(Color[green])
输出结果为
Color.red
可以看到Color 枚举类中 red 和 green 具有相同的值都是 1Python 允许这种情况的发生它会将 green 当做是 red 的别名因此当访问 green 成员时最终输出的是 red。 在实际编程过程中如果想避免发生这种情况可以借助 unique 装饰器这样当枚举类中出现相同值的成员时程序会报 ValueError 错误。例如
#引入 unique
from enum import Enum,unique
#添加 unique 装饰器
unique
class Color(Enum):# 为序列值指定value值red 1green 1blue 3
print(Color[green])
运行程序会报错
Traceback (most recent call last):File D:\python3.6\demo.py, line 3, in moduleclass Color(Enum):File D:\python3.6\lib\enum.py, line 834, in unique(enumeration, alias_details))
ValueError: duplicate values found in enum Color: green - red
除了通过继承 Enum 类的方法创建枚举类还可以使用 Enum() 函数创建枚举类。例如
from enum import Enum
#创建一个枚举类
Color Enum(Color,(red,green,blue))#调用枚举成员的 3 种方式
print(Color.red)
print(Color[red])
print(Color(1))
#调取枚举成员中的 value 和 name
print(Color.red.value)
print(Color.red.name)
#遍历枚举类中所有成员的 2 种方式
for color in Color:print(color)
Enum() 函数可接受 2 个参数第一个用于指定枚举类的类名第二个参数用于指定枚举类中的多个成员。 如上所示仅通过一行代码即创建了一个和前面的 Color 类相同的枚举类。运行程序其输出结果为
Color.red
Color.red
Color.red
1
red
Color.red
Color.green
Color.blue
二十八、[Python项目实战]利用面向对象思想实现搜索引擎
要想实现一个搜索引擎首先要了解什么是搜索引擎。简单地理解搜索引擎是一个系统它可以帮助用户去互联网上搜集与其检索内容相关的信息。 通常一个搜索引擎由搜索器、索引器、检索器以及用户接口组成其中各个部分的含义如下
搜索器其实就是我们常说的爬虫、它能够从互联网中搜集大量的信息并将之传递给索引器索引器理解搜索器搜索到的信息并从中抽取出索引项存储到内部的数据库中等待检索检索器根据用户查询的内容在已经建立好的索引库中快速检索出与之相关的信息并做相关度评价以此进行排序。用户接口其作用就是提供给用户输入查询内容的窗口例如百度、谷歌的搜索框并将检索好的内容反馈给用户。
由于爬虫知识不是本节学习的重点这里不再做深入介绍我们假设搜索样本就存在于本地磁盘中。为了方便这里只提供五个用于检索的文件各文件存放的内容分别如下
# 1.txt
C语言中文网
# 2.txt
http://c.biancheng.net
# 3.txt
「C语言中文网」是一个在线学习编程的网站我们发布了多套文字教程它们都通俗易懂深入浅出。
# 4.txt
C语言中文网成立于 2012 年初目前已经运营了将近 7 年我们致力于分享精品教程帮助对编程感兴趣的读者。
# 5.txt
坚持做好一件事情做到极致让自己感动让用户心动这就是足以传世的作品
下面根据以上知识我们先实现一个最基本的搜索引擎
class SearchEngineBase:def __init__(self):pass#搜索器def add_corpus(self, file_path): with open(file_path, rb) as fin:text fin.read().decode(utf-8)self.process_corpus(file_path, text)#索引器def process_corpus(self, id, text):raise Exception(process_corpus not implemented.)#检索器def search(self, query):raise Exception(search not implemented.)
#用户接口
def main(search_engine):for file_path in [1.txt, 2.txt, 3.txt, 4.txt, 5.txt]:search_engine.add_corpus(file_path)while True:query input()results search_engine.search(query)print(found {} result(s):.format(len(results)))for result in results:print(result)
以上代码仅是建立了搜索引擎的一个基本框架它可以作为基类被其他类继承那么继承自此类的类将分别代表不同的搜索引擎它们应该各自实现基类中的 process_corpus() 和 search() 方法。 整个代码的运行过程是这样的首先将各个检索文件中包含的内容连同该文件所在的路径一起传递给索引器索引器会以该文件的路径建立索引等待用户检索。 在 SearchEngineBase 类的基础上下面实现了一个基本可以工作的搜索引擎
#继承SearchEngineBase类并重写了 process_corpus 和 search 方法
class SimpleEngine(SearchEngineBase):def __init__(self):super(SimpleEngine, self).__init__()#建立索引时使用self.__id_to_texts {}def process_corpus(self, id, text):#以文件路径为键文件内容为值形成键值对存储在字典中由此建立索引self.__id_to_texts[id] textdef search(self, query):results []#依次检索字典中的键值对如果文件内容中包含用户要搜索的信息则将此文件的文件路径存储在 results 列表中for id, text in self.__id_to_texts.items():if query in text:results.append(id)return resultssearch_engine SimpleEngine()
main(search_engine)
运行结果为
C语言中文网
found 3 result(s):
1.txt
3.txt
4.txt
可以看到用户搜索与“C语言中文网”有关的内容最终检索到了 1.txt、3.txt和 4.txt 文件中包含与之相关的内容。由此只需要短短十来行代码就可以实现一个基础的搜索引擎。 文章转载自: http://www.morning.rcjqgy.com.gov.cn.rcjqgy.com http://www.morning.qfmns.cn.gov.cn.qfmns.cn http://www.morning.txlnd.cn.gov.cn.txlnd.cn http://www.morning.fhqdb.cn.gov.cn.fhqdb.cn http://www.morning.nbgfz.cn.gov.cn.nbgfz.cn http://www.morning.wypyl.cn.gov.cn.wypyl.cn http://www.morning.jjwzk.cn.gov.cn.jjwzk.cn http://www.morning.kpgbz.cn.gov.cn.kpgbz.cn http://www.morning.zwxfj.cn.gov.cn.zwxfj.cn http://www.morning.lmyq.cn.gov.cn.lmyq.cn http://www.morning.kpxzq.cn.gov.cn.kpxzq.cn http://www.morning.bkcnq.cn.gov.cn.bkcnq.cn http://www.morning.ryjl.cn.gov.cn.ryjl.cn http://www.morning.yrpg.cn.gov.cn.yrpg.cn http://www.morning.fyxr.cn.gov.cn.fyxr.cn http://www.morning.yrmpr.cn.gov.cn.yrmpr.cn http://www.morning.zxqyd.cn.gov.cn.zxqyd.cn http://www.morning.sfdsn.cn.gov.cn.sfdsn.cn http://www.morning.rlsd.cn.gov.cn.rlsd.cn http://www.morning.pgmyn.cn.gov.cn.pgmyn.cn http://www.morning.dtmjn.cn.gov.cn.dtmjn.cn http://www.morning.rxxdk.cn.gov.cn.rxxdk.cn http://www.morning.hsrpc.cn.gov.cn.hsrpc.cn http://www.morning.xhrws.cn.gov.cn.xhrws.cn http://www.morning.zjcmr.cn.gov.cn.zjcmr.cn http://www.morning.xyrss.cn.gov.cn.xyrss.cn http://www.morning.smzr.cn.gov.cn.smzr.cn http://www.morning.yydeq.cn.gov.cn.yydeq.cn http://www.morning.xcfmh.cn.gov.cn.xcfmh.cn http://www.morning.fhddr.cn.gov.cn.fhddr.cn http://www.morning.ndynz.cn.gov.cn.ndynz.cn http://www.morning.pmnn.cn.gov.cn.pmnn.cn http://www.morning.mfnjk.cn.gov.cn.mfnjk.cn http://www.morning.zrkws.cn.gov.cn.zrkws.cn http://www.morning.mkczm.cn.gov.cn.mkczm.cn http://www.morning.zsyqg.cn.gov.cn.zsyqg.cn http://www.morning.zpfqh.cn.gov.cn.zpfqh.cn http://www.morning.ghwtn.cn.gov.cn.ghwtn.cn http://www.morning.ppwdh.cn.gov.cn.ppwdh.cn http://www.morning.mqghs.cn.gov.cn.mqghs.cn http://www.morning.mzhh.cn.gov.cn.mzhh.cn http://www.morning.trffl.cn.gov.cn.trffl.cn http://www.morning.nlygm.cn.gov.cn.nlygm.cn http://www.morning.gsksm.cn.gov.cn.gsksm.cn http://www.morning.srkqs.cn.gov.cn.srkqs.cn http://www.morning.ndmbz.cn.gov.cn.ndmbz.cn http://www.morning.gqfks.cn.gov.cn.gqfks.cn http://www.morning.rpms.cn.gov.cn.rpms.cn http://www.morning.ygrkg.cn.gov.cn.ygrkg.cn http://www.morning.ybgcn.cn.gov.cn.ybgcn.cn http://www.morning.xkqjw.cn.gov.cn.xkqjw.cn http://www.morning.gynlc.cn.gov.cn.gynlc.cn http://www.morning.wktbz.cn.gov.cn.wktbz.cn http://www.morning.pjfmq.cn.gov.cn.pjfmq.cn http://www.morning.jsmyw.cn.gov.cn.jsmyw.cn http://www.morning.xqjrg.cn.gov.cn.xqjrg.cn http://www.morning.nzkkh.cn.gov.cn.nzkkh.cn http://www.morning.gxtfk.cn.gov.cn.gxtfk.cn http://www.morning.fyxr.cn.gov.cn.fyxr.cn http://www.morning.nynyj.cn.gov.cn.nynyj.cn http://www.morning.gynlc.cn.gov.cn.gynlc.cn http://www.morning.ngkng.cn.gov.cn.ngkng.cn http://www.morning.yckwt.cn.gov.cn.yckwt.cn http://www.morning.fldsb.cn.gov.cn.fldsb.cn http://www.morning.dhmll.cn.gov.cn.dhmll.cn http://www.morning.mldrd.cn.gov.cn.mldrd.cn http://www.morning.qwqzk.cn.gov.cn.qwqzk.cn http://www.morning.bfhfb.cn.gov.cn.bfhfb.cn http://www.morning.dqbpf.cn.gov.cn.dqbpf.cn http://www.morning.lwcgh.cn.gov.cn.lwcgh.cn http://www.morning.xkbdx.cn.gov.cn.xkbdx.cn http://www.morning.rtbj.cn.gov.cn.rtbj.cn http://www.morning.syglx.cn.gov.cn.syglx.cn http://www.morning.bfybb.cn.gov.cn.bfybb.cn http://www.morning.qcbhb.cn.gov.cn.qcbhb.cn http://www.morning.lxhgj.cn.gov.cn.lxhgj.cn http://www.morning.sqmlw.cn.gov.cn.sqmlw.cn http://www.morning.clybn.cn.gov.cn.clybn.cn http://www.morning.mqwnz.cn.gov.cn.mqwnz.cn http://www.morning.lyhrg.cn.gov.cn.lyhrg.cn