python tutorial是什么意思_方法类Python Tutorial(九):类 方法类

题记:写这篇博客要主是加深自己对方法类的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢。

与其它编程语言比相,Python的类机制加添了最小的新法语和语义。它是C++和Modula-3中的类机制的混合。Python的类供提了面向对象编程的有所的准标特性,类继承机制许允有多个基类,一个类子可以重写基类中的任何方法,一个方法可以调用基类面里的同名方法。对象可以含包恣意数量和种类的数据。就像模块那样,类介入Python的动态本性,在运行时被创立,创立后可以被进一步修改。

在C++术语中,常通类的成员(包含数据成员)是有公的,有所的成员函数都是虚的。在Modula-3中,从对象的方法面里引用对象的成员没有简写式形。方法函数被声明为有拥一个显式的第一个数参来表现这个对象,在调用时隐式供提。就像在Smalltalk中,类它们本身就是对象。这供提引入和重名命的语义。不像C++和Modula-3,内建类型可以被户用用作扩展的基类。像C++,很多内建的作操符都有特别的法语,可以为类的例实从新义定。

9.1 称名和对象简介

同一个对象有性个的,和多个称名(在多个围范里)。在其它语言中称为名别。常通第一次看Python时可以用不管它。在处置不可变的基本类型时可以被安全的疏忽。当牵扯到可变对象时,名别对Python代码的语义可能有一个奇惊的作用。这常通被用于序程的利益,因为名别的行为在某些方面像指针。例如,递传一个对象比拟节俭开销,因为只有一个指针被递传。如果一个函数修改了作为数参被递传的对象,调用方将会看到这个变改,这样就去除了两种不同数参递传机制的要需,像在Pascal中那样。

9.2 Python作用域和名命空间

在绍介类之前,我首先不得不诉告你一些有关Python作用域的则规。类义定对名命空间玩了一些洁整技能,你要需道知作用域和名命空间如何任务,并且完整的懂得正在生发什么。趁便地,有关本主题的知识对于级高Python序程员是有效的。

让我们以一些义定开始。

一个名命空间是一个从称名到对象的映射。很多名命空间前当被实现为Python的字典,但是那常通以任何方法说来都不是见易而显的,未来将会变改。名命空间的示例是:内建称名的合集;模块里的全局称名;函数调用里的局部称名。在某种意思上一个对象的属性的合集也构成一个名命空间。关于名命空间要需道知的要重事件是不同的名命空间里的称名之间绝对没有关系。例如,两个不同的模块可以都义定一个函数maximize,不会生产淆混,模块的户用必须用应模块称名作为前缀。

趁便提一下,任何跟在点面前的称名我都用属性呼称它,例如,在表达式z.real中,real是对象z的一个属性。格严的说,引用模块里的称名是属性引用,在表达式modname.funcname,modname是一个模块对象,funcname是它的一个属性。在种这情况下,恰好有一个直接的映射在模块的属性和义定在模块里的全局称名,它们享共样同的名命空间。

属性可所以只读的或可写的。在后一种情况,可以对一个属性赋值。模块属性是可写的,你可以写modname.the_answer = 42。可写属性也可以用应del语句停止除删。例如,del modname.the_answer将移除属性the_answer从名叫modname的对象。

名命空间在不同的刻时被创立,有不同的生命周期。含包内建称名的名命空间在Python解释器动启的时候被创立,并且不再除删。一个模块的全局名命空间在模块义定被读入时创立,常通,模块的名命空间也延续到解释器的出退。通过解释器的顶层调用执行的语句,要么从脚本件文读入或交互式的,被以为是一个叫做__main__模块的一分部,所以它们有它们自己的全局名命空间。(内建的称名际实上也在一个模块里,叫做builtins)

一个函数的局部名命空间在函数被调用时创立,在函数返回或激发一个在函数里没有被处置的常异时被除删。(际实上,记忘是一个较好的方法来描述际实生发了什么)当然,递归调用时,每一次调用都有它们自己的局部名命空间。

一个作用域是Python序程的一个正文区域,在那里一个名命空间被直接问访。这里的直接问访意味着一个对称名的未定限引用将试尝在名命空间里查找。

虽然作用域是态静定决的,但是是却动态用应的。在执行期间,少至有三个嵌套的作用域,它们的名命空间是直接被问访:

最里层的作用域,首先被搜索,含包局部称名。

任何的关闭函数作用域,从近最的关闭作用域开始搜索,含包非局部,但是也非全局的称名。

倒数第二个作用域含包前当模块的全局称名。

最外层的作用域,最后搜索,含包内建称名。

如果一个称名被声明为全局的,然后有所的引用和赋值直接到含包模块全局称名的间中作用域。要从新定绑在最内层作用域外发明的量变,nonlocal语句可以被用应,如果没有声明nonlocal,那些量变是只读的(一个试尝向这样一个量变写将单简的在最里层作用域创立一个新的局部量变,同名的外层围范量变没有变改)。

常通,局部作用域引用前当函数的局部称名。在函数面外,局部作用域引用和全局作用域同相的名命空间:模块的名命空间。类义定位于局部作用域里的另一个名命空间。

要重的是要认识到作用域是本文的定决的。义定在模块里的函数的全局作用域是那个模块的名命空间,无论是从什么地方或通过什么名别来调用它。换句话说,际实对称名的搜索是动态实现的,在运行时,然而,语言义定是朝着态静称名决解化进,在译编时,所以不要赖依动态称名决解(事实上,局部量变经已被态静的定决)

Python的一个特别的合巧是,如果没有global语句的影响,对一个称名的赋值老是进入到最里层的作用域。赋值其实不贝拷数据,它们仅仅把称名定绑到对象。对于除删这也是真的:语句del x从局部作用域引用的名命空间里除删x的定绑。事实上,引入新称名的有所作操用应局部作用域:特别的,import语句和函数义定在局部作用域里定绑模块或函数称名。

global语句用来指示特别的量变驻留在全局作用域,应当在那里被弹回。nonlocal语句指示特别的量变驻留在一个关闭的作用域,并且应当在那里被弹回。

9.2.1 作用域和名命空间示例

这个示例演示如何引用不同的作用域和名命空间,global和nonlocal如何影响量变的定绑:

43ede27b88a9c3ff255a377f047e9cbf.png

示例代码的输出是:

d12fa8bd2605bc0ff62eb33b72ba8980.png

意注,局部赋值是如何没有变改spam的scope_test的定绑。nonlocal赋值变改了spam的scope_test的定绑,global赋值变改的是模块别级的定绑。

你可以看到在global赋值之前没有前以的对spam的定绑。

9.3 第一次看类

类引入了一小点新法语,三种新的对象类型,和一些新的语义。

9.3.1 类义定法语

最单简的类义定式形像这样:

23d54836867a9f768ed07dc731f1ffb3.png

类义定,像函数义定一样,在它们有任何作用前必须先被执行。(你可以把类义定放到一个if语句的支分里,或一个函数面里)

在实际中,一个类义定面里的语句常通是函数义定,其它语句也是许允的,并且有些时候比拟有效。类面里的函数义定常通有一个特别的数参列表式形。通过对方法的调用约定被配支。

当进入一个类义定时,一个新的名命空间被创立,并且用作局部作用域。因此,有所对局部量变的赋值都进入这个新的名命空间。特别的,函数义定把新函数的称名定绑到这里。

当畸形分开一个类义定时,一个类对象被创立。这是一个基本的包装围在通过类义定创立的名命空间的容内的外围。下一节我们将习学更多有关类对象的容内。原始的局部作用域(进入类义定前,正在起作用的那个)被恢复,这个类的对象在这里定绑到类义定头部给出的那个称名上。

9.3.2 类对象

类对象支撑两种类型的作操,属性引用和例实化。

在Python里,对于有所的属性引用都用应准标的法语:obj.name。正当的属性称名是在类对象被创立时类的名命空间里的有所的称名。所以,如果类的义定看起来像这样:

54584511db7806b8ed99b6417185fa03.png

那么MyClass.i和MyClass.f是正当的属性引用,分离返回一个数整和一个函数对象。类属性也可以被赋值,所以你可以通过赋值来变改MyClass.i的值。__doc__也是一个正当的属性,返回类的档文字符串。

类例实化用应函数写法。定假类对象是一个没有数参的函数,并且返回一个类的新例实。例如:

af57b2264c78224b5c20fa221f11246f.png

创立类的一个新例实并把这个对象赋给局部量变x。

例实化作操创立一个空的对象。很多类欢喜创立使例实定制到一个特定的初始状态的对象。因此一个类可以义定一个特别的方法叫做__init__(),像这样:

6ace9fc6ea224c7d3e66a70469a2fa6f.png

当类义定了一个__init__()方法时,对于一个新创立的类例实,类例实化会主动的调用__init__()方法。所以在这个示例里,一个新的、初始化的例实可以这样得获:

3509719b3a8c2db5c5b106f0fcec94f6.png

当然,__init__()方法可以有数参,这样有更大的灵活性。在那种情况下,给类例实化作操符的数参被递传到__init__()方法。例如:

dc0fd9aff1044ffa4a9141ef65aa7fca.png

9.3.3 例实对象

那么在现我们可以对例实对象做些什么呢?唯一被例实对象懂得的作操是属性引用。有两种正当的属性称名,数据属性和方法。

数据属性对应于Smalltalk中的例实量变,C++中的数据成员。数据属性不要需被声明,像局部量变一样,当第一次被赋值时它们忽然就存在了。例如,如果x是MyClass的例实,面下的代码片段将印打出值16,没有留下一个踪影:

2a22613c79b4707231f5eb767a19d15e.png

另一种例实属性引用是方法。方法是属于对象的一个函数。(在Python里,术语方法对于类例实其实不是唯一的,其它对象类型也有方法。)

一个例实对象正当的方法名取决于它的类。通过义定,一个类的有所是函数对象的属性义定了它的例实的响应方法。所以在我们的示例里,x.f是一个正当的方法引用,因为MyClass.f是一个函数,但是x.i不是,因为MyClass.i不是。但是x.f和MyClass.f其实不是一回事,它是一个方法对象,不是一个函数对象。

9.3.4 方法对象

常通一个方法会被当即调用在它被界定后以:

5ed4889227406e1ed2a2a75d1bc5fdf6.png

在MyClass示例里,这将会返回字符串'hello world'。然而,当即调用一个方法是没有须要的。x.f是一个方法对象,可以被存储到其它地方,并且在后以的间时调用。例如:

ac423486d9aee1fff0094ae0013c162f.png

将直一印打hello world,直到最后。

当一个方法被调用时究竟生发了什么?你应当意注到x.f()被调用而没有数参,即使f()的函数义定指定了一个数参。那么数参生发了什么呢?可以肯定的是Python会激发一个常异当一个函数求要一个数参但在调用时没有,即使这个数参际实上其实不用应。

事实上,你或许经已猜到了谜底,关于方法的特别的事件是对象作为函数的第一个数参被传入。在我们的示例中,调用x.f()和MyClass.f(x)是相称的。一般说来,调用一个带有n个数参的方法等同于调用响应的函数,并把方法所属的对象插入到数参列表的第一个数参面前。

如果你仍然不懂得方法如何任务,看一下实现或许会使事件变得晰清。当一个不是数据属性的例实属性被引用时,它的类被找寻。如果这个称名指示一个正当的类属性是一个函数对象,一个方法对象通过把这个例实对象和那个一同发明的函数对象打包进一个抽象对象的方法被创立,这就是这个方法对象。当这个方法对象用应一个数参列表被调用时,一个新的数参列表将从这个例实对象和这个数参列表被造构,这个函数对象将用应这个新的数参列表被调用。

9.4 随机备注

数据属性重写了同相称名的方法属性;为了免避不测的称名冲突,这将会引起比拟难发明的问题在大序程里,一个明聪的做法是用应一些约定来把冲突的机遇降到最小。可能的约定包含方法称名大写,数据属性称名加一个小的唯一字符串前缀(或许就是一个下划线),或方法用应动词,数据属性用应名词。

数据属性可以被方法和一个对象的一般户用引用。换句话说,类不是可用的对于实现纯抽象数据类型。事实上,在Python面里没什么货色够能强制数据隐藏,它都是基于约定的。(从另一方面说,Python是用C实现的,可以完整的隐藏实现细节和控制对一个对象的问访,如果有须要的话;这一点可以通过C写的Python扩展来用应。)

户客端应当仔细的用应数据属性,户客端或许搅乱稳定的护维通过方法冲压它们的数据属性。意注,户客端可以加添它们自己的数据属性到一个例实对象上而不影响方法的正当性,和称名冲突被免避,一个名命约定在这里可以省去很多头疼的事件。

在一个方法面里没有单简的写法来引用数据属性(或其它方法)。我发明际实上这增加了方法的可读性,在翻阅方法时,不存在局部量变和例实量变冲突的机遇。

常通,方法的第一个数参叫做self。这就是一个约定,self这个名字对于Python绝对没有特别的意思。意注,如果你的代码不追随约定的话对于其它Python序程员说来可读性会低降。也可以设想,一个类浏览序程也要需赖依于这个约定来写。

任何函数对象是一个类属性,义定了这个类的例实的一个方法。函数义定被本文的关闭在类义定面里不是必须的。把一个函数对象赋给类的局部量变也是可以的。例如:

583589dd4470683a48823f735a0c2461.png

在现f,g和h都是类C的属性并且指向函数对象,所以它们都是C的例实的方法,h和g是相称的,这样的实际常通会是序程的读者惑困。

方法可以调用其它方法通过用应self数参的方法属性:

4b4d8b99071c60411a527c6b4a1c2755.png

方法可以引用全局称名以像一般函数那样的方法。和一个方法联关的全局作用域是含包它义定的模块。(一个类从来不被用作一个全局作用域)一个人比拟习见的遇到了在一个方法面里用应全局数据的好由理,有很多全局作用域的正当用应。首先,导入到全局作用域面里的函数和模块可以被方法和义定在它面里的函数和类用应。常通,含包方法的类它自己义定在这个全局作用域面里,在下一节我们将发明一些好的由理,为什么一个方法想要引用它自己的类。

个一每值都是一个对象,因此有一个类(也叫做它的类型)它被存储为objct.__class__。

9.5 继承

当然,一个语言特性将不值得有拥称名类,如果它不支撑继承。一个类子义定的法语看起来像这样:

a8b3b5de94d253a0be074096da871aa2.png

称名BaseClassName必须义定在含包类子义定的作用域里。在基类称名的那个地方,其它的恣意表达式也是许允的。这会比拟有效,例如,当基类义定在其它模块面里:

1213207de5d2b31f793e679b66a68b3c.png

类子义定的执行的停止和基类是一样的。当类对象被构建时,基类被住记。这用来剖析属性引用,如果一个求要的属性在类面里没有发明,搜索将停止到基类面里。这个则规将递归的往下用应如果这个基类也继承了其它的类。

关于类子的例实化并没有什么特别之处,DerivedClassName()创立一个新的类例实。方法引用按面下方法被剖析,响应的类属性被搜索,如果须要的话沿着基类的链往下停止,如果这能返回一个函数对象,方法引用就是正当的。

类子可以重写基类的方法。因为方法没有特权当调用同一个对象的其它方法时,一个基类的方法调用同一个基类的另一个方法将结束调用一个重写它的类子的一个方法。(对于C++序程员,有所Python面里的方法际实上都是虚的)

一个类子面里的重写方法事实上是想扩展而不是单简的替换基类面里的同名方法。有一个单简的方法可以直接调用基类面里的方法,就调用BaseClassName.methodname(self, arguments)。这偶尔对户客也非常有效。(意注,在这个全局作用域面里,如果基类作为BaseClassName是可问访的,下面的方法才可以任务)

Python有两个内建函数和继承一同用应:

用应isinstance()来测检一个例实的类型,isinstance(obj, int)将返回True当且仅当obj.__class__是int或某个类继承自int。

用应issubclass()来测检类继承,issubclass(bool, int)是True,因为bool是int的一个类子。然而,issubclass(float, int)是False,因为float不是int的一个类子。

9.5.1 多重继承

Python也支撑多重继承的式形。一个带有多个基类的类义定看起来像这样:

8358e124e56cd5aaa8f646e8e6cf5aed.png

基于多种的目,在最单简的情况下,你可以以为搜索一个从父类继承过去的属性是深度优先,从左到右,不会在一个类面里搜索两次当它处于继承次层的叠重处时。因此,如果一个属性在DerivedClassName面里没有发明,然后搜索Base1,然后递归的搜索Base1的有所基类,如果在那里没有发明,然后搜索Base2,等等。

事实上,比下面略微庞杂些,方法的剖析序顺动态的变改来支撑作协的调用super()。种这方法也出在现其它一些多继承语言中,比单继承面里的super调用更壮大。

动态序排是须要的,因为多重继承的有所情况都展示出一个或多个菱形关系(那里少至有一个父类可以在最底层的类面里通过多个路径问访到。)例如,有所的类都继承自object,所以多继承的任何情况都供提多于一条的路径达到object。为了坚持基类被问访不多于一次,动态算法以一个方法线性化搜索序顺,坚持在个一每类面里指定的从左到右的序顺,那就调用个一每父类仅一次,那就是枯燥的(意味着一个类可以被类子化而不影响它父类的优先权序顺)。综合起来,这些属性使用采多重继承来计设靠可的和可扩展的类成变可能。

9.6 私有量变

除了在一个对象面里否则不能被问访的私有例实量变在Python里是不存在的。然而,有一个被数多Python代码遵守的约定,一个以下划线为前缀的称名应当被以为是API的非共公分部(它是不是是一个函数,一个方法或一个数据成员)。它应当被以为是一个具体实现,并且从服变改而用不通知。

因为有一个正当的类私有成员用例(即免避类子义定的称名成造的名命冲突),对于这个机制有一个无限的支撑,叫做称名改正。任何__spam这个式形的标识符(少至两个前导下划线,最多一个部尾下划线)被本文的替换为_classname__spam,这里的classname是前当的类的称名,并去掉前导的下划线。这个改正被实现和标识符的法语位置关无,只要它生发在一个类的义定外部。

称名改是正有效的,它让类子重写方法而不破打类外部方法调用。例如:

25c0334501478a6fa86aeef7de2f42de.png

意注,改正则规被计设大数多是为了免避事变,它也可以用来问访或修改一个被以为是私有的量变。在特别的情况下这个甚至有效,就像调试。

意注,递传给exec()或eval()的代码不以为正在调用的类的称名是前当的类,这和global语句的作用是相似的,它的作用对于节字译编在一同的代码是样同的制约的。样同的制约用应于getattr(),setattr()和delattr(),和当直接引用__dict__时。

9.7 零星的

时有,有一个像Pascal的记载或C的结构长短常有效的,把多数的名命数据项打包在一同。一个空的类义定就够能很好的实现:

008c02deeece730c26233aff2e05c388.png

一段Python代码希望一个特别的抽象数据类型,经常被传入一个类来拟模那个数据类型的方法所代替。例如,如果你有一个函数格式化一些来自件文对象的数据,你可以义定一个类,有read()和readline()方法从一个字符串缓冲区得获数据,并且作为数参递传个它。

例实方法对象也有属性,m.__self__是有拥方法m()的例实对象,m.__func__是和方法对应的函数对象。

9.8 常异也是类

户用义定的常异也是通过类来标识的。用应这个机制可以创立常异的可扩展次层。

有两个新的正当的语义式形对于raise语句:

e5838ee05a90e52483d79484ae9dd4ee.png

第一种式形,Class必须是type或它的类子的例实。它是面下的一个简写:

e47ac9ebac65342359d8f8c2b2dd613e.png

一个except从句中的类是和一个常异可匹配的,如果它和常异是同一个类或是常异的一个基类(其它方法不可,一个except从句列出一个类子,和一个基类是不匹配的)。例如,面下的代码将按照那样的序顺印打B,C,D:

9cb84a64668647735d347dccd9bdacce.png

如果except从句反转,把except B放到第一,将会印打出BBB,第一个匹配except从句被触发。

当一个未处置的常异的错误信息被印打出来时,常异的类称名被印打,然后一个冒号和一个空格,最后是例实的字符串表现式形,用应内建的str()函数停止转化。

9.9 迭代器

到在现你可能经已意注到很多容器对象可以用应for语句在它下面停止迭代:

4dcc0b498efd3610ea45a19c1f66cb87.png

问访的式样晰清,简练,便方。迭代器的用应遍布和同一Python。在这个场景面前,for语句在容器对象上调用iter()。函数返回一个迭代器对象,它义定了方法__next__(),它每次问访一个容器中的元素。当没有更多元素时,它就激发一个StopIteration常异诉告for环循来终止。你可以用应内建的next()函数调用__next__()方法。面下的示例演示它如何任务:

32430a5c336945bf39dc22710e9ebb16.png

经已看到了迭代器协议背后的结构,可以很容易的给你的类加上迭代器行为。义定一个__iter__()方法,并返回一个含包__next__()方法对象。如果类义定__next__(),然后__iter__()可以仅仅返回它自己:

bb5d26f9106d93277477ab3fc4d82f57.png

7a219a2d6b78a382036dbac7093db899.png

9.10 生成器

生成器是一个单简和壮大的工具来创立迭代器。它们写起来就像畸形的函数,但是任何时候它们想返回数据的时候用应yield语句。每一次在它下面调用next()时,生成器在它分开的地方从新开始(它能住记有所的数据值和最后一次执行的语句)。一个示例演示生成器可以很单简的被创立:

281e3a6b706de0597c376f3dfb586d74.png

939a08e25a9af67aab1b8e9ae7977d10.png

任何够能用生成器实现的事件也够能用基于迭代器的类来实现。使生成器如此兼容的是__iter__()和__next__()方法被主动创立。

另一个关键的特征是局部量变和执行状态在每次调用之间被主动的保存。这使得函数更容易书写和比用应像self.index和self.data这样的例实量变的方法更加晰清。

除了主动方法创立和保存序程状态,当生成器终止时,它们主动的激发StopIteration常异。总之,这些特性使创立一个迭代器很容易,其实不比写一个畸形的函数多付出努力。

9.11 生成器表达式

一些单简的生成器可以被简练的写为表达式,用应一个和列表综合相似的法语,但是要用小括号代替大括号。这些表达式被计设为在那里生成器被一个关闭的函数立马用应的情况。生成器表达式比完整的生成器义定更加紧凑但功能较少和比相称的列表综合趋向于更友好的内存用应。

示例:

56e55abcbc51c7add4adce267030946a.png

文章结束给大家分享下程序员的一些笑话语录: 那是习惯决定的,一直保持一个习惯是不好的!IE6的用户不习惯多标签,但是最终肯定还是得转到多标签的浏览器。历史(软件UI)的进步(改善)不是以个人意志(习惯)为转移的!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值