96SEO 2026-02-19 21:06 40
的重要特征#xff0c;相比其它面向对象语言#xff0c;Python

很容易就可以创建出一个类和对象。
同时就定位为一门面向对象的编程语言“Python
中“一切皆对象”的说法但可能并不了解它的具体含义只是在学习的时候听说
面向对象编程是在面向过程编程的基础上发展来的它比面向过程编程具有更强的灵活性和扩展性。
面向对象编程是程序员发展的分水岭很多初学者会因无法理解面向对象而放弃学习编程。
OOP是一种封装代码的方法。
其实在前面章节的学习中我们已经接触了封装比如说将乱七八糟的数据扔进列表中这就是一种简单的封装是数据层面的封装把常用的代码块打包成一个函数这也是一种封装是语句层面的封装。
代码封装其实就是隐藏实现功能的具体代码仅留给用户使用的接口就好像使用计算机用户只需要使用键盘、鼠标就可以实现一些功能而根本不需要知道其内部是如何工作的。
本节所讲的面向对象编程也是一种封装的思想不过显然比以上两种封装更先进它可以更好地模拟真实世界里的事物将其视为对象并把描述特征的数据和代码块函数封装到一起。
打个比方若在某游戏中设计一个乌龟的角色应该如何来实现呢使用面向对象的思想会更简单可以分为如下两个方面进行描述
kg、有外壳等等。
从所具有的的行为来描述例如它会爬、会吃东西、会睡觉、会将头和四肢缩到壳里等等。
如果将乌龟用代码来表示则其表面特征可以用变量来表示其行为特征可以通过建立各种函数来表示。
参考代码如下所示
crawl(self):print(乌龟会爬)#会吃东西def
sleep(self):print(乌龟在睡觉)#会缩到壳里def
注意以上代码仅是为了演示面向对象的编程思想具体细节后续会做详细介绍。
因此从某种程序上相比较只用变量或只用函数使用面向对象的思想可以更好地模拟现实生活中的事物。
中所有的变量其实也都是对象包括整形int、浮点型float、字符串str、列表(list)、元组(tuple)、字典dict和集合set。
以字典dict为例它包含多个函数供我们使用例如使用
在系统学习面向对象编程之前初学者要了解有关面向对象的一些术语。
当和其他人讨论代码的时候或者尝试查找我们遇到的问题的解决方案时知道正确的术语会很有帮助。
类可以理解是一个模板通过它可以创建出无数个具体实例。
比如前面编写的
表示的只是乌龟这个物种通过它可以创建出无数个实例来代表各种不同特征的乌龟这一过程又称为类的实例化。
对象类并不能直接使用通过类创建出的实例又称对象才能使用。
这有点像汽车图纸和汽车的关系图纸本身类并不能为人们使用通过图纸创建出的一辆辆车对象才能使用。
属性类中的所有变量称为属性。
例如tortoise
这个类中bodyColor、footNum、weight、hasShell
都是这个类拥有的属性。
方法类中的所有函数通常称为方法。
不过和函数所有不同的是类方法至少要包含一个
类中crawl()、eat()、sleep()、protect()
都是这个类所拥有的方法类方法无法单独使用只能和类的对象一起使用。
前面已经提到类仅仅充当图纸的作用本身并不能直接拿来用而只有根据图纸造出的实际物品对象才能直接使用。
因此Python
创建定义类也就是制作图纸的过程创建类的实例对象根据图纸造出实际的物品通过实例对象实现特定的功能。
本节先教大家如何创建定义一个类如何使用定义好的类将放到后续章节进行讲解。
注意无论是类属性还是类方法对于类来说它们都不是必需的可以有也可以没有。
另外Python
类中属性和方法所在的位置是任意的即它们之间并没有固定的前后次序。
和变量名一样类名本质上就是一个标识符因此我们在给类起名字时必须让其符合
语法上讲是完全没有问题的但作为一名合格的程序员我们必须还要考虑程序的可读性。
因此在给类起名字时最好使用能代表该类功能的单词例如用“Student”作为学生类的类名甚至如果必要可以使用多个单词组合而成例如初学者定义的第一个类的类名可以是“TheFirstDemo”。
其实类属性指的就是包含在类中的变量而类方法指的是包含类中的函数。
换句话说类属性和类方法其实分别是包含类中的变量和函数的别称。
需要注意的一点是同属一个类的所有类属性和类方法要保持统一的缩进格式通常统一缩进
TheFirstDemo:这是一个学习Python定义的第一个类#
和函数一样我们也可以为类定义说明文档其要放到类头之后类体之前的位置如上面程序中第二行的字符串就是
的类属性。
注意根据定义属性位置的不同在各个类方法之外定义的变量称为类属性或类变量如
属性而在类方法中定义的属性称为实例属性或实例变量它们的区别和用法可阅读六、Python类变量和实例变量类属性和实例属性。
种类方法的区别和具体用法可阅读六、Python类变量和实例变量类属性和实例属性。
事实上我们完全可以创建一个没有任何类属性和类方法的类换句话说Python
关键字作为类体即可。
但在实际应用中很少会创建空类因为空类没有任何实际意义。
构造方法用于创建对象时使用每当创建一个类的实例对象时Python
中很多这种以双下划线开头、双下划线结尾的方法都具有特殊的意义后续会一一为大家讲解。
的参数且必须作为第一个参数。
也就是说类的构造方法最少也要有一个
TheFirstDemo:这是一个学习Python定义的第一个类#构造方法def
参数外还可以自定义一些参数参数之间使用逗号“,”进行分割。
例如下面的代码在创建
CLanguage:这是一个学习Python定义的一个类def
__init__(self,name,add):print(name,的网址为:,add)
CLanguage(C语言中文网,http://c.biancheng.net)
注意由于创建对象时会调用类的构造方法如果构造函数有多个参数时需要手动传递参数传递方式如代码中所示后续部分会做详细讲解。
通过前面章节的学习我们已经学会如何定义一个类但要想使用它必须创建该类的对象。
CLanguage(C语言中文网,http://c.biancheng.net)
类变量和实例变量简单地理解定义在各个类方法之外包含在类中的变量为类变量或者类属性定义在类方法中的变量为实例变量或者实例属性二者的具体区别和用法可阅读第六部分、Python类变量和实例变量。
定义的类只有进行实例化也就是使用该类创建对象之后才能得到利用。
总的来说实例化后的类对象可以执行以下操作
访问或修改类对象具有的实例变量甚至可以添加新的实例变量或者删除已有的实例变量调用类对象的方法包括调用现有的方法以及给类对象动态添加方法。
CLanguage(C语言中文网,http://c.biancheng.net)
print(clanguage.name,clanguage.add)
clanguage.addhttp://c.biancheng.net/python
print(clanguage.name,clanguage.add)
使用lambda表达式为clanguage对象的bar方法赋值动态绑定方法
不会自动将方法调用者绑定到它们的第一个参数因此程序必须手动为第一个参数传入参数值如上面程序中
info(self,content):print(C语言中文网地址为%s
clanguage.info(http://c.biancheng.net)
在定义类的过程中无论是显式创建类的构造方法还是向类中添加实例方法都要求将
只是规定无论是构造方法还是实例方法最少要包含一个参数并没有规定该参数的具体名称。
之所以将其命名为
self只是程序员之间约定俗成的一种习惯遵守这个约定可以使我们编写的代码具有更好的可读性大家一看到
参数的具体作用是什么呢打个比方如果把类比作造房子的图纸那么类实例化后的对象是真正可以住的房子。
根据一张图纸类我们可以设计出成千上万的房子类对象每个房子长相都是类似的都有相同的类变量和类方法但它们都有各自的主人那么如何对它们进行区分呢
参数它就相当于每个房子的门钥匙可以保证每个房子的主人仅能进入自己的房子每个类对象只能调用自己的类变量和类方法。
也就是说同一个类可以产生多个对象当某个对象调用类方法时该对象会把自身的引用作为第一个参数自动传给该方法换句话说Python
会自动绑定类方法的第一个参数指向调用该方法的对象。
如此Python解释器就能知道到底要操作哪个对象的方法了。
因此程序在调用实例方法和构造方法时不需要手动为第一个参数传值。
例如更改前面的
__init__(self):print(正在执行构造方法)#
study(self):print(self,正在学Python)
__init__(self,name):self.namenamezhangsan
值得一提的是除了类对象可以直接调用类方法还有一种函数调用的方式例如
总之无论是类中的构造函数还是普通的类方法实际调用它们的谁则第一个参数
无论是类属性还是类方法都无法像普通变量或者函数那样在类的外部直接使用它们。
我们可以将类看做一个独立的空间则类属性其实就是在类体中定义的变量类方法是在类体中定义的函数。
前面章节提到过在类体中根据变量定义的位置不同以及定义的方式不同类属性又可细分为以下
类体中、所有函数之外此范围定义的变量称为类属性或类变量类体中所有函数内部以“self.变量名”的方式定义的变量称为实例属性或实例变量类体中所有函数内部以“变量名变量值”的方式定义的变量称为局部变量。
不仅如此类方法也可细分为实例方法、静态方法和类方法第七部分会做详细介绍。
那么类变量、实例变量以及局部变量之间有哪些不同呢接下来就围绕此问题做详细地讲解。
类变量的特点是所有类的实例化对象都同时共享类变量也就是说类变量在所有实例化对象中是作为公用资源存在的。
类方法的调用方式有
当然也可以使用类对象来调用所属类中的类变量此方式不推荐使用原因后续会讲。
例如在
注意因为类变量为所有实例化对象共有通过类名修改类变量的值会影响所有的实例化对象。
例如在
print(clang2.add)print(修改后各类对象中类变量的值)
注意通过类对象是无法修改类变量的。
通过类对象对类变量赋值其本质将不再是修改类变量的值而是在给该对象定义新的实例变量在讲实例变量时会进行详细介绍。
值得一提的是除了可以通过类名访问类变量之外还可以动态地为类和对象添加类变量。
例如在
实例变量指的是在任意类方法内部以“self.变量名”的方式定义的变量其特点是只作用于调用方法的对象。
另外实例变量只能通过对象名访问无法通过类名访问。
前面讲过通过类对象可以访问类变量但无法修改类变量的值。
这是因为通过类对象修改类变量的值不是在给“类变量赋值”而是定义新的实例变量。
例如在
类中实例变量和类变量可以同名但这种情况下使用类对象将无法调用类变量它会首选实例变量这也是不推荐“类变量使用对象名调用”的原因。
另外和类变量不同通过某个对象修改实例变量的值不会影响类的其它实例化对象更不会影响同名的类变量。
例如
除了实例变量类方法中还可以定义局部变量。
和前者不同局部变量直接以“变量名值”的方式进行定义例如
通常情况下定义局部变量是为了所在类方法功能的实现。
需要注意的一点是局部变量只能用于所在函数中函数执行完成后局部变量也会被销毁。
和类属性一样类方法也可以进行更细致的划分具体可分为类方法、实例方法和静态方法。
通常情况下在类中定义的方法默认都是实例方法。
前面章节中我们已经定义了不只一个实例方法。
不仅如此类的构造方法理论上也属于实例方法只不过它比较特殊。
有关使用类名直接调用实例方法的更多介绍可阅读第八部分Python类调用实例方法一节。
类方法和实例方法相似它最少也要包含一个参数只不过类方法中通常将其命名为
参数注意绑定的不是类对象。
也就是说我们在调用类方法时无需显式为
和实例方法最大的不同在于类方法需要使用classmethod修饰符进行修饰例如
http://c.biancheng.net#下面定义了一个类方法classmethoddef
类方法推荐使用类名直接调用当然也可以使用实例对象来调用不推荐。
例如在上面
静态方法其实就是我们学过的函数和函数唯一的区别是静态方法定义在类这个空间类命名空间中而函数则定义在程序所在的空间全局命名空间中。
解释器不会对它包含的参数做任何类或对象的绑定。
也正因为如此类的静态方法中无法调用任何类属性和类方法。
CLanguage.info(C语言中文网,http://c.biancheng.net)
clang.info(Python教程,http://c.biancheng.net/python)
在实际编程中几乎不会用到类方法和静态方法因为我们完全可以使用函数代替它们实现想要的功能但在一些特殊的场景中例如工厂模式中使用类方法和静态方法也是很不错的选择。
类分别是类方法、实例方法和静态方法其中实例方法用的是最多的。
我们知道实例方法的调用方式其实有
通常情况下我们习惯使用类对象调用类中的实例方法。
但如果想用类调用实例方法不能像如下这样
参数传参。
这意味着和使用类对象调用实例方法不同通过类名直接调用实例方法时Python
参数需要的是方法的实际调用者是类对象而这里只提供了类名当然无法自动传值。
参数使得程序得以正确执行。
实际上这里调用实例方法的形式完全是等价于
参数传值但并没有规定必须传一个该类的对象其实完全可以任意传入一个参数例如
用类的实例对象访问类成员的方式称为绑定方法而用类名调用类成员的方式称为非绑定方法。
类体中的代码位于独立的命名空间称为类命名空间中。
换句话说所有用
我们一开始学习类时就已经提到类其实是由多个类属性和类方法构成而类属性其实就是定义在类这个独立空间中的变量而类方法其实就是定义在类空间中的函数和定义在全局命名空间中的变量和函数相比并没有明显的不同。
say():print(我在学习Python--CLanguage独立空间)#
print(CLanguage.name,CLanguage.add)
可以看到相比位于全局命名空间的变量和函数位于类命名空间中的变量和函数在使用时只需要标注
还允许直接在类命名空间中编写可执行程序例如输出语句、分支语句等例如
显然上面这些位于类命名空间的可执行程序和位于全局命令空间相比并没有什么不同。
但需要注意的一点是当使用类对象调用类方法时在传参方面是和外界的函数有区别的因为
会自动为第一个参数绑定方法的调用者而位于全局空间中的函数则必须显式为第一个参数传递参数。
中通过使用描述符可以让程序员在引用一个对象属性时自定义要完成的工作。
本质上看描述符就是一个类只不过它定义了另一个类中属性的访问方式。
换句话说一个类可以将属性管理全权委托给描述符类。
__getattribute__()它会按照下列顺序查找该属性
验证该属性是否为类实例对象的数据描述符如果不是就查看该属性是否能在类实例对象的
objtype):print(Retrieving,self.name)return
val):print(updating,self.name)self.val
从这个例子可以看到如果一个类的某个属性有数据描述符那么每次查找这个属性时都会调用描述符的
前面章节中我们一直在用“类对象.属性”的方式访问类中定义的属性其实这种做法是欠妥的因为它破坏了类的封装原则。
正常情况下类包含的属性应该是隐藏的只允许通过类提供的方法来间接实现对类属性的访问和操作。
因此在不破坏类封装原则的基础上为了能够有效操作类中的属性类中应包含读或写类属性的多个
setter方法这样就可以通过“类对象.方法(参数)”的方式操作属性例如
可能有读者觉得这种操作类属性的方式比较麻烦更习惯使用“类对象.属性”这种方式。
函数可以实现在不破坏类封装原则的前提下让开发者依旧使用“类对象.属性”的方式操作类中的属性。
delname(self):self.__namexxx#为name
getname()这将会先入无限死循环。
为了避免这种情况的出现程序中的
有关类属性和类方法的属性设置分为共有属性、保护属性、私有属性后续会做详细介绍。
既要保护类的封装特性又要让开发者可以使用“对象.属性”的方式操作操作类属性除了使用
装饰器可以直接通过方法名来访问方法不需要在方法名后添加一对“”小括号。
__init__(self,area):self.__area
rect.areaprint(删除后的area值为,rect.area)
简单的理解封装Encapsulation即在设计类时刻意地将一些属性和方法隐藏在类的内部这样在使用此类时将无法直接以“类对象.属性名”或者“类对象.方法名(参数)”的形式调用这些属性或方法而只能用未隐藏的类方法间接操作这些隐藏的属性和方法。
就好比使用电脑我们只需要学会如何使用键盘和鼠标就可以了不用关心内部是怎么实现的因为那是生产和设计人员该操心的。
注意封装绝不是将类中所有的方法都隐藏起来一定要留一些像键盘、鼠标这样可供外界使用的类方法。
首先封装机制保证了类内部数据结构的完整性因为使用类的用户无法直接看到类中的数据结构只能使用类允许公开的数据很好地避免了外部对内部数据的影响提高了程序的可维护性。
除此之外对一个类实现良好的封装用户只能借助暴露出来的类方法来访问数据我们只需要在这些暴露的方法中加入适当的控制逻辑即可轻松实现用户对类中属性或方法的不合理操作。
public公有属性的类变量和类函数在类的外部、类内部以及子类后续讲继承特性时会做详细介绍中都可以正常访问private私有属性的类变量和类函数只能在本类内部使用类的外部以及子类都无法使用。
类中的变量和方法都是公有public的它们的名称前都没有下划线_如果类中的变量和函数其名称以双下划线“__”开头则该变量函数为私有变量私有函数其属性等同于
_name、_display(self)这种类属性和类方法通常被视为私有属性和私有方法虽然它们也能通过类对象正常访问但这是一种约定俗称的用法初学者一定要遵守。
类中还有以双下划线开头和结尾的类方法例如类的构造函数__init__(self)这些都是
ValueError(名称长度必须大于3)self.__name
add.startswith(http://):self.__add
__display(self):print(self.__name,self.__add)clang
属性都隐藏了起来但同时也提供了可操作它们的“窗口”也就是各自的
方法控制用户输入的地址必须以“http://”开头否则程序将会执行
的具体用法后续章节会做详细的讲解这里可简单理解成如果用户输入不规范程序将会报错。
方法进行适当的设计可以避免用户对类中属性的不合理操作从而提高了类的可维护性和安全性。
方法由于该类方法为私有private方法且该类没有提供操作该私有方法的“窗口”因此我们无法在类的外部使用它。
换句话说如下调用
封装特性的实现纯属“投机取巧”之所以类对象无法直接调用以双下划线开头命名的类属性和类方法是因为其底层实现时Python
ValueError(名称长度必须大于3)self.__name
add.startswith(http://):self.__add
__display(self):print(self.__name,self.__add)
方法由于其是私有方法且该类没有提供任何调用该方法的“接口”因此在目前看来此方法根本无法在类外部调用。
也就是说如下调用
封装机制的底层实现原理就可以调用它。
事实上对于以双下划线开头命名的类属性或类方法Python
在底层将其方法名偷偷改成了“_CLanguage__display()”。
例如在
__add其底层的名称也改成了“_类名__属性名”的这种格式。
例如
print(clang._CLanguage__name,clang._CLanguage__add)
print(clang._CLanguage__name,clang._CLanguage__add)
底层修改类属性或者类方法的名称以此将它们隐藏起来只需将它们的名称前添加双下划线“__”即可。
继承机制经常用于创建和现有类功能类似的新类又或是新类只需要在现有类基础上添加一些成员属性和方法但又不想直接将现有类代码复制给新类。
也就是说通过使用继承这种机制可以轻松实现类的重复使用。
类要求此类不但可以在屏幕上画出指定的形状还可以计算出所画形状的面积。
要创建这样的类笨方法是将
draw(self,content):print(画,content)
draw(self,content):print(画,content)def
area(self):#....print(此图形的面积为...)
draw(self,content):print(画,content)
area(self):#....print(此图形的面积为...)
中实现继承的类称为子类被继承的类称为父类也可称为基类、超类。
因此在上面这个样例中Form
是父类。
子类继承父类时只需在定义子类时将父类可以是多个放在子类之后的圆括号里即可。
语法格式如下
中所有类的父类即要么是直接父类要么是间接父类。
另外Python
注意有读者可能还听说过“派生”这个词汇它和继承是一个意思只是观察角度不同而已。
换句话话继承是相对子类来说的即子类继承自父类而派生是相对于父类来说的即父类派生出子类。
say(self):print(我是一个人名字是,self.name)
没错子类拥有父类所有的属性和方法即便该属性或方法是私有private的。
至于为什么可阅读《Python封装实现原理》一节。
事实上大部分面向对象的编程语言都只支持单继承即子类有且只能有一个父类。
而
和单继承相比多继承容易让代码逻辑复杂、思路混乱一直备受争议中小型项目中较少使用后来的
使用多继承经常需要面临的问题是多个父类中包含同名的类方法。
对于这种情况Python
的处置措施是根据子类继承多个父类时这些父类的前后次序决定即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。
say(self):print(People类,self.name)class
say(self):print(Animal类,self.name)
类是支持多继承的一个类的方法和属性可能定义在当前类也可能定义在基类。
针对这种情况当调用类方法或类属性时就需要对当前类以及它的基类进行搜索以确定方法或属性的位置而搜索的顺序就称为方法解析顺序。
方法解析顺序Method
的方法它仍然采用从左至右的深度优先遍历但是如果遍历中出现重复的类只保留最后一个。
方式已经能够解决“菱形”继承的问题但是可能会违反单调性原则。
所谓单调性原则是指在类存在多继承时子类不能改变基类的
C-A-X-object-Y-object-B-Y-object-X-object再进行简化相同取后者得到
C:\Users\mengma\Desktop\demo.py,
重复上述步骤直至列表为空或者不能再找出可以输出的元素。
如果是前一种情况则算法结束如果是后一种情况Python
中子类继承了父类那么子类就拥有了父类所有的类属性和类方法。
通常情况下子类会在此基础上扩展一些新的类属性和类方法。
但凡事都有例外我们可能会遇到这样一种情况即子类从父类继承得来的类方法中大部分是适合子类使用的但有个别的类方法并不能直接照搬父类的如果不对这部分类方法进行修改子类对象无法使用。
针对这种情况我们就需要在子类中重复父类的方法。
举个例子鸟通常是有翅膀的也会飞因此我们可以像如下这样定义个和鸟相关的类
isWing(self):print(鸟有翅膀)#鸟会飞def
但是对于鸵鸟来说它虽然也属于鸟类也有翅膀但是它只会奔跑并不会飞。
针对这种情况可以这样定义鸵鸟类
重写有时又称覆盖是一个意思指的是对类中已有方法的内部实现进行修改。
isWing(self):print(鸟有翅膀)#鸟会飞def
事实上如果我们在子类中重写了从父类继承来的类方法那么当在类的外部通过子类对象调用该方法时Python
这就产生一个新的问题即如果想调用父类中被重写的这个方法该怎么办呢
中的类可以看做是一个独立空间而类方法其实就是出于该空间中的一个函数。
而如果想要全局空间中调用类空间中的函数只需要在调用该函数时备注类名即可。
举个例子
isWing(self):print(鸟有翅膀)#鸟会飞def
类它是所有内置类型的共同祖先也是所有没有显式指定父类的类包括用户自定义的的共同祖先。
因此在实际编程过程中如果想实现与某个内置类型具有类似行为的类时最好的方法就是将这个内置类型子类化。
内置类型子类化其实就是自定义一个新类使其继承有类似行为的内置类通过重定义这个新类实现指定的功能。
newDictError(ValueError):如果向newDict
newDictError(这个值已经存在并对应不同的键)super().__setitem__(key,value)
C:\Users\mengma\Desktop\demo.py,
moduledemoDict[o***r_key]valueFile
C:\Users\mengma\Desktop\demo.py,
不允许字典中多个键对应相同的值。
如果用户试图添加具有相同值的新元素则会引发
demoList.append(http://c.biancheng.net)
中子类会继承父类所有的类属性和类方法。
严格来说类的构造方法其实就是实例方法因此毫无疑问父类的构造方法子类同样会继承。
是一门支持多继承的面向对象编程语言如果子类继承的多个父类中包含同名的类实例方法则子类对象在调用该方法时会优先选择排在最前面的父类中的实例方法。
显然构造方法也是如此。
say(self):print(我是人名字为,self.name)class
display(self):print(我是动物,我吃,self.food)
displayprint(我是动物,我吃,self.food)
类自己的构造方法等同于重写第一个直接父类的构造方法。
但需要注意如果在子类中定义构造方法则必须在该方法中调用父类的构造方法。
类可以看做一个独立空间在类的外部调用其中的实例方法可以向调用普通函数那样只不过需要额外备注类名此方式又称为未绑定方法使用
函数。
但如果涉及多继承该函数只能调用第一个直接父类的构造方法。
也就是说涉及到多继承时在子类构造函数中调用第一个父类构造方法的方式有以上
say(self):print(我是人名字为,self.name)
display(self):print(我是动物,我吃,self.food)
类的构造方法super().__init__(name)#super(Person,self).__init__(name)
#执行效果和上一行相同#People.__init__(self,name)#使用未绑定方法调用
object如果定义一个类但没有显式指定其祖先那么就被解释为旧式类例如
newStyleClass(newStyleClass):pass
版本中不再保留旧式类的概念。
因此没有继承任何其他类的类都隐式地继承自
作为所有基类的祖先因为如果不这么做的话这些类将被解释为旧式类最终会导致难以诊断的问题。
中被隐式地调用需要程序员显式调用它们。
这种情况下当程序中包含多重继承的类层次结构时使用
)A.__init__(self)B.__init__(self)
但是有时这种层次结构的一部分位于第三方代码中我们无法确定外部包的这些代码中是否使用
super()因此当需要对某个第三方类进行子类化时最好查看其内部代码以及
的另一个问题是初始化过程中的参数传递。
如果没有相同的签名一个类怎么能调用其基类的
__init__(self):print(commonBase)super().__init__()class
__init__(self):print(base1)super().__init__()class
__init__(self):print(base2)super().__init__()class
C:\Users\mengma\Desktop\demo.py,
C:\Users\mengma\Desktop\demo.py,
包装的参数和关键字参数这样即使不使用它们所有的构造函数也会传递所有参数如下所示
__init__(self,*args,**kwargs):print(commonBase)super().__init__()class
__init__(self,*args,**kwargs):print(base1)super().__init__(*args,**kwargs)class
__init__(self,*args,**kwargs):print(base2)super().__init__(*args,**kwargs)class
不过这是一种很糟糕的解决方法由于任何参数都可以传入所有构造函数都可以接受任何类型的参数这会导致代码变得脆弱。
另一种解决方法是在
中没有指定任何祖先地类都被认定为旧式类。
调用父类时应提前查看类的层次结构也就是使用类的
通过学习《Python类变量和实例变量》一节了解了如何动态的为单个实例对象添加属性甚至如果必要的话还可以为所有的类实例对象统一添加属性通过给类添加属性。
是否也允许动态地为类或实例对象添加方法呢答案是肯定的。
我们知道类方法又可细分为实例方法、静态方法和类方法Python
种方法但对于实例对象则只允许动态地添加实例方法不能添加类方法和静态方法。
为单个实例对象添加方法不会影响该类的其它实例对象而如果为类动态地添加方法则所有的实例对象都可以使用。
info3():print(正在调用静态方法)#类可以动态添加以上
clang.info3()#类实例对象只能动态添加实例方法不会影响其它实例对象
显然动态给类或者实例对象添加属性或方法是非常灵活的。
但与此同时如果胡乱地使用也会给程序带来一定的隐患即程序中已经定义好的类如果不做任何限制是可以做动态的修改的。
只能限制为实例对象动态添加属性和方法而无法限制动态地为类添加属性和方法。
属性值其实就是一个元组只有其中指定的元素才可以作为动态添加的属性或者方法的名称。
举个例子
info(self,name):print(正在调用实例方法,self.name)
info(self,name):print(正在调用实例方法,self.name)
info(self):print(正在调用实例方法)CLanguage.say
当然还可以为类动态添加类方法和静态方法这里不再给出具体实例读者可自行编写代码尝试。
因此如果子类也要限制外界为其实例对象动态地添加属性和方法必须在子类中设置
函数还有一个更高级的用法即创建一个自定义类型也就是创建一个类。
函数查看某个变量或类对象的类型由于很简单这里不再做过多解释直接给出一个样例
type(CLanguage,(object,),dict(say
C语言中文网则表示为类添加了一个类属性反之如果值为外部定义的函数如
MetaClass元类本质也是一个类但和普通类的用法不同它可以对类内部的定义包括类属性和类方法进行动态的修改。
可以这么说使用元类的主要目的就是为了实现在创建类时能够动态地改变类中定义的属性或者方法。
元类动态地修改内部的属性或者方法则类的创建过程将变得复杂先创建
方法该方法一定要返回该类的一个实例对象因为在使用元类创建类时该
super().__new__(cls,name,bases,attrs)
CLanguage(object,metaclassFirstMetaClass):passclangs
可以看到在创建类时通过在标注父类的同时指定元类格式为metaclass元类名则当
MyClass()print(MyClass,instance)
类型模型所以如果使用不慎对于整个代码库造成的风险是不可估量的。
在面向对象程序设计中除了封装和继承特性外多态也是一个非常重要的特性本节就带领大家详细了解什么是多态。
是弱类型语言其最明显的特征是在使用变量时无需为其指定具体的数据类型。
这会导致一种情况即同一变量可能会被先后赋值不同的类对象例如
这种由多态衍生出的更灵活的编程机制又称为“鸭子模型”或“鸭子类型”。
一些具有特殊含义的类其实例化对象的个数往往是固定的比如用一个类表示月份则该类的实例对象最多有
枚举类。
也就是说对于这些实例化对象个数固定的类可以用枚举类来定义。
和普通类的用法不同枚举类不能用来实例化对象但这并不妨碍我们访问枚举类中的成员。
访问枚举类成员的方式有多种例如以
需要注意的是枚举类中各个成员的值不能在类的外部做任何修改也就是说下面语法的做法是错误的
属性该属性是一个包含枚举类中所有成员的字典通过遍历该属性也可以访问枚举类中的各个成员。
例如
Color.__members__.items():print(name,-,member)
Enum(Color,(red,green,blue))#调用枚举成员的
个参数第一个用于指定枚举类的类名第二个参数用于指定枚举类中的多个成员。
要想实现一个搜索引擎首先要了解什么是搜索引擎。
简单地理解搜索引擎是一个系统它可以帮助用户去互联网上搜集与其检索内容相关的信息。
通常一个搜索引擎由搜索器、索引器、检索器以及用户接口组成其中各个部分的含义如下
搜索器其实就是我们常说的爬虫、它能够从互联网中搜集大量的信息并将之传递给索引器索引器理解搜索器搜索到的信息并从中抽取出索引项存储到内部的数据库中等待检索检索器根据用户查询的内容在已经建立好的索引库中快速检索出与之相关的信息并做相关度评价以此进行排序。
用户接口其作用就是提供给用户输入查询内容的窗口例如百度、谷歌的搜索框并将检索好的内容反馈给用户。
由于爬虫知识不是本节学习的重点这里不再做深入介绍我们假设搜索样本就存在于本地磁盘中。
为了方便这里只提供五个用于检索的文件各文件存放的内容分别如下
「C语言中文网」是一个在线学习编程的网站我们发布了多套文字教程它们都通俗易懂深入浅出。
坚持做好一件事情做到极致让自己感动让用户心动这就是足以传世的作品
fin.read().decode(utf-8)self.process_corpus(file_path,
5.txt]:search_engine.add_corpus(file_path)while
search_engine.search(query)print(found
result(s):.format(len(results)))for
以上代码仅是建立了搜索引擎的一个基本框架它可以作为基类被其他类继承那么继承自此类的类将分别代表不同的搜索引擎它们应该各自实现基类中的
整个代码的运行过程是这样的首先将各个检索文件中包含的内容连同该文件所在的路径一起传递给索引器索引器会以该文件的路径建立索引等待用户检索。
SimpleEngine(SearchEngineBase):def
__init__(self):super(SimpleEngine,
self).__init__()#建立索引时使用self.__id_to_texts
text):#以文件路径为键文件内容为值形成键值对存储在字典中由此建立索引self.__id_to_texts[id]
[]#依次检索字典中的键值对如果文件内容中包含用户要搜索的信息则将此文件的文件路径存储在
文件中包含与之相关的内容。
由此只需要短短十来行代码就可以实现一个基础的搜索引擎。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback