<a name="blog1544254642"></a>html
python中分几种代码块类型,它们都有本身的做用域,或者说名称空间:python
正是这一层层隔离又链接的名称空间将变量、类、对象、函数等等都组织起来,使得它们能够拥有某些属性,能够进行属性查找。算法
本文详细解释类和对象涉及的名称空间,属于纯理论类的内容,有助于理解python面向对象的细节。期间会涉及全局和本地变量做用域的查找规则,若有不明白之处,可先看文章:Python做用域详述函数
<a name="blog1544254643"></a>spa
如下是一个能在必定程度上归纳全文的示例代码段:code
x = 11 # 全局变量x def f(): # 全局变量f print(x) # 引用全局变量x def g(): # 全局变量g x = 22 # 定义本地变量x print(x) # 引用本地变量x class supcls(): # 全局变量supcls x = 33 # 类变量x def m(self): # 类变量m,类内函数变量self x = 44 # 类内函数变量x self.x = 55 # 对象变量x class cls(supcls): # 全局变量cls x = supcls.x # 引用父类属性x,并定义cls类属性x def n(self): # 类变量n self.x = 66 # 对象变量x
若是能理解上面的每一个x属于哪一个做用域、哪一个名称空间,本文内容基本上就理解了。htm
<a name="blog1544254644"></a>对象
下面有一个类,类中有类属性x、y,有方法m和n。blog
class supcls(): x = 3 y = 4 def m(self): x = 33 self.x = 333 self.y = 444 self.z = 555 def n(self): return self.x, self.y, self.z
当python解释到supcls代码块后,知道这是一个类,类有本身的名称空间。因此,当知道了这个类里面有x、y、m、n后,这几个属性都会放进类supcls的名称空间中。继承
以下图:
在上图中,类的名称空间中有属性x、y、m和n,它们都称为类属性。须要说明的是,在python中,函数变量m、n和普通变量没什么区别,仅仅只是它保存了指向函数体的地址,函数体即上图中用func m和func n所表示的对象。
由于有名称空间,能够直接使用彻底限定名称去访问这个名称空间中的内容。例如:
print(supcls.x) print(supcls.y) print(supcls.m) print(supcls.n)
输出结果:
3 4 <function supcls.m at 0x02B83738> <function supcls.n at 0x02B836F0>
由于函数m和n也是类的属性,它们也能够直接经过类名来访问执行。例如,新加入一个函数,但不用self参数了,而后执行它。
class testcls(): z = 3 def a(): x = 1 print(x) # print(z) # 这是错的 testcls.a()
不只如此,方法也是属性,它实际上就是函数(在python 3.0中确实如此,在之前版本中方法和函数是有点区别的)。因此,能够将方法保存下来,以后再去调用它:
aa = testcls.a aa() print(aa)
可是须要注意,方法代码块中看不见类变量。虽然类和方法的做用域关系相似于全局做用域和函数本地做用域,但并不老是等价。例如,方法a()中没法直接访问类变量z。这就像类内部看不到全局变量同样。
上面全都是使用类名.属性
这种彻底限定名称去访问类中的属性的。若是生成类的对象,则能够经过对象去访问相关对象属性,由于对象有本身的名称空间,且部分属性来源于类。
<a name="blog1544254645"></a>
类就像一个模板,能够根据这个模板大量生成具备本身特性的对象。在Python中,只需像调用函数同样直接调用类就能够建立对象。
例如,下面建立了两个cls类的对象o1和o2,建立类的时候能够传递参数给类,这个参数能够传递给类的构造函数__init__()
。
o1 = cls() o2 = cls("some args")
对象有本身的名称空间。由于对象是根据类来建立的,类是它们的模板,因此对象名称空间中包含全部类属性,可是对象名称空间中这些属性的值不必定和类名称空间属性的值相同。
如今根据supcls类构造两个对象s1和s2:
class supcls(): x = 3 y = 4 def m(self): x = 33 self.x = 333 self.y = 444 self.z = 555 def n(self): return self.x, self.y, self.z s1 = supcls() s2 = supcls()
那么它们的名称空间,以及类的名称空间的关系以下图所示:
如今仅仅只是对象s一、s2链接到了类supcls,对象s1和s2有本身的名称空间。但由于类supcls中没有构造方法__init__()
初始化对象属性,因此它们的名称空间中除了python内部设置的一些"其它"属性,没有任何属于本身的属性。
但由于s一、s2链接到了supcls类,因此能够进行对象属性查找,若是对象中没有,将会向上找到supcls。例如:
print(s1.x) # 输出3,搜索到类名称空间 print(s1.y) # 输出4,搜索到类名称空间 # print(s1.z) # 这是错的
上面再也不是经过彻底限定的名称去访问类中的属性,而是经过对象属性查找的方式搜索到了类属性。但上面访问z属性将报错,由于尚未调用m方法。
当调用m方法后,将会经过self.xxx
的方式设置彻底属于对象自身的属性,包括x、y、z。
s1.m() s2.m()
如今,它们的名称空间以及类的名称空间的关系以下图所示:
如今对象名称空间中有x、y和z共3个属性(不考虑其它python内部设置的属性),再经过对象名去访问对象属性,仍然会查找属性,但对于这3个属性的搜索不会进一步搜索到类的名称空间。但若是访问对象中没有的属性,好比m和n,它们不存在于对象的名称空间中,因此会搜索到类名称空间。
print(s1.x) # 对象属性333,搜索到对象名称空间 print(s1.y) # 对象属性444,搜索到对象名称空间 print(s1.z) # 对象属性555,搜索到对象名称空间 s1.m() # 搜索到类名称空间 s1.n() # 搜索到类名称空间
对象与对象之间的名称空间是彻底隔离的,对象与类之间的名称空间存在链接关系。因此,s1和s2中的x和y和z是互不影响的,谁也看不见谁。
但如今想要访问类变量x、y,而不是对象变量,该怎么办?直接经过类名的彻底限定方式便可:
print(s1.x) # 输出333,对象属性,搜索到对象名称空间 print(supcls.x) # 输出3,类属性,搜索到类名称空间
和类同样,经过对象能够访问到方法,方法能够赋值给一个变量保存下来,之后再执行:
mm = s1.m mm()
实际上,s1.m是一个function类的实例对象。在s1.xxx
搜索索性的时候,若是发现xxx不是对象或类的数据属性,说明这是方法属性,python会将这个方法与对象(obj.method
)或类(cls.method
)进行关联。不管如何,方法都是类的属性。
由于对象有了本身的名称空间,就能够直接向这个名称空间添加属性或设置属性。例如,下面为s1对象添加一个新的属性,但并非在类内部设置,而是在类的外部设置:
s1.x = 3333 # 在外部设置已有属性x s1.var1 = "aaa" # 在外部添加新属性var1
新属性var1将只存在于s1,不存在于s2和类supcls中。
<a name="blog1544254646"></a>
属于类的属性称为类属性,即那些存在于类名称空间的属性。类属性分为类变量和方法。方法分为3种:
相似的,属于对象名称空间的属性称为对象属性。对象属性脱离类属性,和其它对象属性相互隔离。
例如:
class cls: x=3 def f(): y=4 print(y) def m(self): self.z=3
上面的x、f、m都是类属性,x是类变量,f和m是方法,z是对象属性。
<a name="blog1544254647"></a>
子类和父类之间有继承关系,它们的名称空间也经过一种特殊的方式进行了链接:子类能够继承父类的属性。
例以下面的例子,子类class childcls(supcls)
表示childcls继承了父类supcls。
class supcls(): x = 3 y = 4 def m(self): x = 33 self.x = 333 self.y = 444 self.z = 555 def n(self): return self.x, self.y, self.z class childcls(supcls): y = supcls.y + 1 # 经过类名访问父类属性 def n(self): self.z = 5555
当python解释完这两段代码块时,初始时的名称空间结构图以下:
当执行完class childcls(supcls)
代码块以后,子类childcls就有了本身的名称空间。初始时,这个名称空间中除了链接到父类supcls外,还有本身的类变量y和方法n(),子类中的方法n()重写了父类supcls的方法n()。
由于有本身的名称空间,因此能够访问类属性。当访问的属性不存在于子类中时,将自动向上搜索到父类。
print(childcls.x) # 父类属性,搜索到父类名称空间 print(childcls.y) # 子类自身属性,搜索到子类名称空间 print(childcls.z) # 错误,子类和父类都没有该属性
当建立子类对象的时候,子类对象的变量搜索规则:
例如,建立子类对象c1,并调用子类的方法n():
c1 = childcls() c1.n()
如今,子类对象c一、子类childcls和父类supcls的关系以下图所示:
经过前面的说明,想必已经不用过多解释。
<a name="blog1544254648"></a>
python支持多重继承,只需将须要继承的父类放进子类定义的括号中便可。
class cls1(): ... class cls2(): ... class cls3(cls1,cls2): ...
上面cls3继承了cls1和cls2,它的名称空间将链接到两个父类名称空间,也就是说只要cls1或cls2拥有的属性,cls3构造的对象就拥有(注意,cls3类是不拥有的,只有cls3类的对象才拥有)。
但多重继承时,若是cls1和cls2都具备同一个属性,好比cls1.x和cls2.x,那么cls3的对象c3.x取哪个?会取cls1中的属性x,由于规则是按照(括号中)从左向右的方式搜索父类。
再考虑一个问题,若是cls1中没有属性x,但它继承自cls0,而cls0有x属性,那么,c3.x取哪一个属性。
这在python 3.x中是一个比较复杂的问题,涉及到动态调整访问顺序。但基本上能够根据MRO(Method Resolution Order)算法总结为:先左后右,先深度再广度,但必须遵循共同的超类最后搜索。
此处只给一个它们的访问顺序图示,这也是__mro__
属性的默认顺序:
<a name="blog1544254649"></a>
在python中,类并无什么特殊的,它存在于模块文件中,是全局名称空间中的一个属性。
例如,在模块文件中定义了一个类cls,那么这个cls就是一个全局变量,只不过这个变量中保存的地址是类代码块所在数据对象。
# 模块文件顶层 class cls(): n = 3
而模块自己是一个对象,有本身的模块对象名称空间(即全局名称空间),因此类是这个模块对象名称空间中的一个属性,仅此而已。
另外须要注意的是,类代码块和函数代码块不同,涉及到类代码块中的变量搜索时,只会根据对象与类的链接、子类与父类的继承链接进行搜索。不会像全局变量和函数同样,函数内能够向上搜索全局变量、嵌套函数能够搜索外层函数。
例如:
# 全局范围 x = 3 def f(): print(x) # 搜索到全局变量x class sup(): # print(x) # 这是错的,不会搜索全局变量 y = 3 print(y) # 这是对的,存在类属性y def m(self): # print(y) # 这是错的,不会搜索到类变量 self.z = 4 class childcls(sup): # print(y) # 这是错的,不会搜索到父类
其实很容易理解为何面向对象要有本身的搜索规则。对象和类之间是is a
的关系,子类和父类也是is a
的关系,这两个is a
是面向对象时名称空间之间的链接关系,在搜索属性的时候能够顺着"这根树"不断向上爬,直到搜索到属性。
<a name="blog1544254650"></a>
前面一直说名称空间,这个抽象的东西用来描述做用域,好比全局做用域、本地做用域等等。
在其余语言中可能很难直接查看名称空间,可是在python中很是容易,由于只要是数据对象,只要有属性,就有本身的__dict__
属性,它是一个字典,表示的就是名称空间。__dict__
内的全部东西,均可以直接经过点"."的方式去访问、设置、删除,还能够直接向__dict__
中增长属性。
例如:
class supcls(): x=3 class childcls(supcls): y=4 def f(self): self.z=5 >>> c=childcls() >>> c.__dict__.keys() dict_keys([]) >>> c.f() >>> c.__dict__ {'z': 5}
能够直接去增、删、改这个dict,所做的修改都会直接对名称空间起做用。
>>> c.newkey = "NEWKEY" >>> c.__dict__["hello"] = "world" >>> c.__dict__ {'z': 5, 'newkey': 'NEWKEY', 'hello': 'world'}
注意,__dict__
表示的是名称空间,因此不会显示类的属性以及父类的属性。正如上面刚建立childcls的实例时,dict中是空的,只有在c.f()以后才设置独属于对象的属性。
若是要显示类以及继承自父类的属性,可使用dir()
。
例如:
>>> c1 = childcls() >>> c1.__dict__ {} >>> dir(c1) ['__class__', '__delattr__', '__dict__', ...... 'f', 'x', 'y']
关于__dict__
和dir()的详细说明和区别,参见dir()和__dict__的区别。
<a name="blog1544254651"></a>
前面屡次提到对象和类之间有链接关系,子类与父类也有链接关系。可是究竟是怎么链接的?
__class__
进行链接:对象的__class__
的值为所属类的名称__bases__
进行链接:子类的__bases__
的值为父类的名称例如:
class supcls(): x=3 class childcls(supcls): y=4 def f(self): self.z=5 c = childcls()
c是childcls类的一个实例对象:
>>> c.__class__ <class '__main__.childcls'>
childcls继承自父类supcls,父类supcls继承自祖先类object:
>>> childcls.__bases__ (<class '__main__.supcls'>,) >>> supcls.__bases__ (<class 'object'>,)
<a name="blog1544254652"></a>
下面经过__class__
和__bases__
属性来查看对象所在类的继承树结构。
代码以下:
def classtree(cls, indent): print("." * indent + cls.__name__) for supcls in cls.__bases__: classtree(supcls, indent + 3) def objecttree(obj): print("Tree for %s" % obj) classtree(obj.__class__, 3) class A: pass class B(A): pass class C(A): pass class D(B, C): pass class E: pass class F(D, E): pass objecttree(B()) print("==============") objecttree(F())
运行结果:
Tree for <__main__.B object at 0x037D1630> ...B ......A .........object ============== Tree for <__main__.F object at 0x037D1630> ...F ......D .........B ............A ...............object .........C ............A ...............object ......E .........object
原文出处:https://www.cnblogs.com/f-ck-need-u/p/10094021.html