当前位置: 首页 > 后端技术 > Python

Python基础:Python中类

时间:2023-03-26 00:38:44 Python

简介类是面向对象编程中一个非常重要的概念。python中也有类,支持面向对象编程的所有标准特性:继承、多态等,本文将详细讲解Python中类的信息。作用域和命名空间在详细解释类之前,让我们先了解一下作用域和命名空间的概念。命名空间是从名称到对象的映射,大多数命名空间都是通过Python字典实现的。命名空间的主要目的是避免程序中的名称冲突。只要名称在同一名称空间中保持唯一,不同命令空间中的名称就不会相互影响。Python中存在三种命名空间:内置名称(built-innames),Python语言内置的名称,如函数名称abs、char和异常名称BaseException、Exception等。全局名称(globalnames),名称definedinthemodule,记录模块的变量,包括函数、类、其他导入的模块、模块级变量和常量。局部名称(localnames),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(在类中也是这样定义的)命名空间的查找顺序是本地名-》全局名-》内置名。在不同时间创建的命名空间具有不同的生命周期。包含内置名称的命名空间是在Python解释器启动时创建的,并且永远不会被删除。模块的全局命名空间是在读入模块定义时创建的。通常,模块命名空间也会一直存在,直到解释器退出。由解释器的顶级调用执行的语句,例如从脚本文件读取的程序或交互读取的程序,被视为__main__模块调用的一部分,因此它们也有自己的全局命名空间。(内置名称实际上也存在于模块中;这个模块称为builtins。)作用域是Python程序中的文本区域,可通过命名空间直接访问。Python中有四种作用域:局部:最内层,包含局部变量,例如函数/方法内部。Enclosing:包含非局部(non-local)和非全局(non-global)变量。比如两个嵌套的函数,一个函数(或类)A包含一个函数B,那么对于B中的名字,A中的作用域是nonlocal的。global:当前脚本的最外层,比如当前模块的全局变量。Built-in:包含内置变量/关键字等,最后要搜索的scope的搜索顺序是Local->Enclosing->Global->Built-inPython,用nonlocal关键字声明为Enclosingscope,并且使用global关键字声明为全局范围。让我们看一个全局和非本地如何影响变量绑定的例子:="globalspam"spam="testspam"do_local()print("Afterlocalassignment:",spam)do_nonlocal()print("Afternonlocalassignment:",spam)do_global()print("Afterglobalassignment:",spam)scope_test()print("Inglobalscope:",spam)上面程序输出:局部赋值后:testspam非局部赋值后:nonlocalspam全局赋值后:nonlocalspamInglobalscope:globalspam函数中的变量默认为它是本地范围。如果要在函数的函数中修改外部函数的变量,需要将这个变量声明为nonlocal。最后,模块顶层或程序文件顶层的变量是全局作用域。如果需要引用修改,需要声明为全局作用域。classPython类是通过类来定义的,我们来看最简单的类定义:classClassName:。..类定义中的代码会创建一个新的命名空间,里面的变量被认为是局部作用域的。对局部变量的所有赋值都在这个新命名空间内进行。类对象class定义了一个类之后,就生成了一个类对象。我们可以使用此类对象来访问类中定义的属性和方法。例如,我们定义了以下类:classMyClass:"""Asimpleexampleclass"""i=12345deff(self):return'helloworld'定义了一个属性i和一个方法f。然后我们可以通过MyClass.i和MyClass.f访问它们。请注意,Python中没有像java中的private和public那样的变量访问范围控制。您可以将Python类中的变量和方法视为公共的。我们可以通过给MyClass.i赋值直接改变i变量的值。In[2]:MyClass.__doc__Out[2]:'Asimpleexampleclass'In[3]:MyClass.i=100In[4]:MyClassOut[4]:__main__.MyClassIn[5]:MyClass.iOut[5]:在100Class中,我们还定义了类文档,可以直接通过__doc__访问。一个类的实例实例化了一个类对象,类可以看成是一个没有参数的函数。In[6]:x=MyClass()In[7]:x.iOut[7]:100上面我们创建了一个MyClass的实例,并赋值给了x。通过访问x中的i值,我们可以发现这个i值与MyClass类变量中的i值是一致的。实例化操作(“调用”类对象)创建一个空对象。如果想在实例化的时候做一些自定义的操作,可以在类中定义一个__init__()方法,类的实例化会自动为新创建的类实例调用__init__()。def__init__(self):self.data=[]__init__()方法也可以接受参数,参数是我们实例化类时传入的:>>>classComplex:...def__init__(self,realpart,imagpart):...self.r=realpart...self.i=imagpart...>>>x=Complex(3.0,-4.5)>>>x.r,x.i(3.0,-4.5)实例对象属性依旧上面的类,我们定义了一个i属性和一个f方法:classMyClass:"""Asimpleexampleclass"""i=12345deff(self):return'helloworld'我们可以通过实例对象来访问这个property:In[6]:x=MyClass()In[7]:x.iOut[7]:100即使我们可以在实例对象中创建一个不属于类对象的属性:In[8]:x.y=200In[9]:x.yOut[9]:200即使使用后,也不保留任何记录:x.counter=1whilex.counter<10:x.counter=x.counter*2print(x.counter)delX。计数器方法对象我们有两种方式来访问函数中定义的方法,一种是通过类对象,另一种是通过实例对象。让我们看看两者之间的区别:In[10]:x.fOut[10]:>In[11]:x.f()Out[11]:'你好世界'In[12]:MyClass.fOut[12]:In[13]:MyClass.f()------------------------------------------------------------------------TypeErrorTraceback(最近调用最后)in()---->1MyClass.f()TypeError:f()missing1requiredpositionalargument:'self'从上面的输出我们可以看到MyClass.f是一个函数,x.f是一个对象。还记得f方法的定义吗?f方法有一个self参数。如果作为函数调用,必须传入所有需要的参数。这就是为什么直接调用MyClass.f()会报错,而x.f()可以直接运行的原因。虽然方法的第一个参数通常被命名为self。这只不过是一个约定:名称self在Python中绝对没有特殊含义。方法对象的特殊之处在于实例对象将作为函数的第一个参数传入。在我们的示例中,调用x.f()等效于MyClass.f(x)。简而言之,调用一个有n个参数的方法,相当于多调用一个参数对应的函数。该参数的值为该方法所属的实例对象,位置在其他参数之前。为什么方法对象不需要传入self参数呢?从x.f的输出可以看出这个方法已经绑定了一个实例对象,所以会自动传入self参数,方法可以通过self参数的method属性调用其他方法:classBag:def__init__(self):self.data=[]defadd(self,x):self.data.append(x)defaddtwice(self,x):self.add(x)self.add(x)类变量和实例变量在类变量和实例变量的使用中,需要注意哪些问题呢?通常,实例变量用于每个实例唯一的数据,而类变量用于类的所有实例共享的属性和方法。classDog:kind='canine'#所有实例共享的类变量def__init__(self,name):self.name=name#每个实例唯一的实例变量>>>d=Dog('Fido')>>>e=Dog('Buddy')>>>d.kind#所有狗'canine'共享>>>e.kind#所有狗'canine'共享>>>d.name#d'Fido'独有>>>e.name#e'Buddy'独有所以,如果是实例变量,需要在初始化方法中进行赋值和初始化。如果是类变量,可以直接在类的结构体中定义。正确使用实例变量的例子:classDog:def__init__(self,name):self.name=nameself.tricks=[]#为每只狗创建一个新的空列表defadd_trick(self,trick):self.tricks.append(trick)>>>d=Dog('Fido')>>>e=Dog('Buddy')>>>d.add_trick('翻身')>>>e.add_trick('装死')>>>d.tricks['rollover']>>>e.tricks['playdead']如果实例和类中都出现相同的属性名,则属性查找会优先实例:>>>类仓库:purpose='storage'region='west'>>>w1=Warehouse()>>>print(w1.purpose,w1.region)storagewest>>>w2=Warehouse()>>>w2。region='east'>>>print(w2.purpose,w2.region)storageeastinheritance看Python中继承的语法:classDerivedClassName(BaseClassName):。..ifthebaseclasswhendefinedinanothermodule:classDerivedClassName(modname.BaseClassName):如果请求的属性在类中没有找到,会去基类中查找。如果基类本身是从某个其他类派生的,则递归地应用此规则。派生类可以覆盖其基类的方法。因为方法在调用同一对象的其他方法时没有特殊特权,所以调用同一基类中定义的另一个方法的基类方法可能最终会调用重写它的派生类的方法。Python中有两个内置函数可以方便的判断是继承还是实例:使用isinstance()检查实例的类型:例如:isinstance(obj,int)只会被使用当obj.__class__是int或从True派生时,如果是int的类。使用issubclass()检查类继承:例如:issubclass(bool,int)为True,因为bool是int的子类。但是,issubclass(float,int)为False,因为float不是int的子类。Python也支持多重继承:classDerivedClassName(Base1,Base2,Base3):。..如果一个属性在DerivedClassName中没有找到,就到Base1中查找,然后(递归地)到Base1的基类中查找,如果没有找到,再到Base2中查找,以此类推.私有变量尽管Python中没有针对私有变量的强制语法,但大多数Python代码遵循约定,即带有下划线的名称(例如_spam)应被视为API的非公共部分(无论是函数、方法还是数据成员)。这只是我们编写Python程序时的一个实现细节,并不是语法的强制规范。由于存在私有变量,在继承的情况下可能会出现私有变量覆盖。Python是怎么解决的?在Python中,可以通过变量名重写来避免私有变量被覆盖。__spam形式的任何标识符的文本(至少有两个前导下划线和最多一个尾随下划线)将被替换为_classname__spam,其中classname是删除前导下划线的当前类名。这种重写的发生与标识符的句法位置无关,只要它出现在类定义中即可。例如:classMapping:def__init__(self,iterable):self.items_list=[]self.__update(iterable)defupdate(self,iterable):foriteminiterable:self.items_list.append(item)__update=update#privatecopyoforiginalupdate()methodclassMappingSubclass(Mapping):defupdate(self,keys,values):#为update()提供新签名#但不会破坏__init__()foriteminzip(keys,values):self.items_list.append(item)上面的例子即使MappingSubclass引入了__update标识也不会报错,因为在Mapping类中会被_Mapping__update替换,被_MappingSubclass__update替换。请注意,传递给exec()或eval()的代码不会将调用类的类名视为当前类;这类似于global语句的效果,因此这种效果仅限于同样是字节码编译的代码。迭代器对于大多数容器对象,您可以使用for语句来遍历容器中的元素。对于[1,2,3]中的元素:print(element)对于(1,2,3)中的元素:print(element)forkeyin{'one':1,'two':2}:print(key)forcharin"123":print(char)forlineinopen("myfile.txt"):print(line,end='')基本原理是for语句将调用iter()方法容器对象。此函数返回一个迭代器对象,该对象定义了一个__next__()方法,该方法将逐个遍历容器的元素。当元素耗尽时,__next__()将引发StopIteration异常以发出for循环终止的信号。您可以使用next()内置函数来调用__next__()方法;下面的例子展示了如何:>>>s='abc'>>>it=iter(s)>>>it>>next(it)'a'>>>next(it)'b'>>>next(it)'c'>>>next(it)Traceback(mostrecentcalllast):File"",line1,innext(it)StopIteration知道原理后对于迭代器,我们可以在自定义类中添加一个迭代器对象,我们需要定义一个__iter__()方法来返回一个具有__next__()方法的对象。如果类定义了__next__(),__iter__()可以简单地返回self:classReverse:"""Iteratorforloopingoverasequencebackwards."""def__init__(self,data):self.data=dataself.index=len(data)def__iter__(self):returnselfdef__next__(self):ifself.index==0:raiseStopIterationself.index=self.index-1returnself.data[self.index]生成器生成器是用于创建迭代器的简单而强大的工具。它们的编写方式与标准函数类似,但在返回数据时使用yield语句。每次在生成器上调用next()时,它都会从中断处继续执行(它会记住上次执行语句时的所有数据值)。请参阅生成器的示例:defreverse(data):forindexinrange(len(data)-1,-1,-1):yielddata[index]>>>>>>forcharinreverse('golf'):...print(char)...flog可以用生成器做与上一节中描述的基于类的迭代器可以做的相同的事情。但是生成器更紧凑,因为它们会自动创建__iter__()和__next__()方法。生成器也可以作为表达式代码执行,其编写方式类似于列表理解,但使用圆括号而不是方括号。>>>sum(i*iforiinrange(10))#平方和285>>>xvec=[10,20,30]>>>yvec=[7,5,3]>>>sum(x*yforx,yinzip(xvec,yvec))#dotproduct260>>>unique_words=set(wordforlineinpageforwordinline.split())>>>valedictorian=max((student.gpa,student.name)forstudentingraduates)>>>data='golf'>>>list(data[i]foriinrange(len(data)-1,-1,-1))['f','l','o','g']本文已收录于http://www.flydean.com/10-python-class/最流行的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!