- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章一篇文章搞懂Python的类与对象名称空间由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
代码块的分类 。
python中分几种代码块类型,它们都有自己的作用域,或者说名称空间:
文件或模块整体是一个代码块,名称空间为全局范围 。
函数代码块,名称空间为函数自身范围,是本地作用域,在全局范围的内层 。
类代码块,名称空间为类自身 。
正是这一层层隔离又连接的名称空间将变量、类、对象、函数等等都组织起来,使得它们可以拥有某些属性,可以进行属性查找.
本文详细解释类和对象涉及的名称空间,属于纯理论类的内容,有助于理解python面向对象的细节。期间会涉及全局和本地变量作用域的查找规则,如有不明白之处,可先看文章:python作用域详述 。
一个概括全文的示例 。
以下是一个能在一定程度上概括全文的示例代码段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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属于哪个作用域、哪个名称空间,本文内容基本上就理解了.
类的名称空间 。
下面有一个类,类中有类属性x、y,有类方法m和n.
1
2
3
4
5
6
7
8
9
10
11
12
|
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所表示的对象.
因为有名称空间,可以直接使用完全限定名称去访问这个名称空间中的内容。例如:
1
2
3
4
|
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参数了,然后执行它.
1
2
3
4
5
6
7
8
|
class
testcls():
z
=
3
def
a():
x
=
1
print
(x)
# print(z) # 这是错的
testcls.a()
|
但是需要注意,类方法代码块中看不见类变量。虽然类和类方法的作用域关系类似于全局作用域和函数本地作用域,但并不总是等价。例如,方法a()中无法直接访问类变量z。这就像类内部看不到全局变量一样.
上面全都是使用类名.属性这种完全限定名称去访问类中的属性的。如果生成类的对象,则可以通过对象去访问相关对象属性,因为对象有自己的名称空间,且部分属性来源于类.
对象名称空间 。
类就像一个模板,可以根据这个模板大量生成具有自己特性的对象。在python中,只需像调用函数一样直接调用类就可以创建对象.
例如,下面创建了两个cls类的对象o1和o2,创建类的时候可以传递参数给类,这个参数可以传递给类的构造函数__init__().
1
2
|
o1
=
cls
()
o2
=
cls
(
"some args"
)
|
对象有自己的名称空间。因为对象是根据类来创建的,类是它们的模板,所以对象名称空间中包含所有类属性,但是对象名称空间中这些属性的值不一定和类名称空间属性的值相同.
现在根据supcls类构造两个对象s1和s2:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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()
|
那么它们的名称空间,以及类的名称空间的关系如下图所示:
现在仅仅只是对象s1、s2连接到了类supcls,对象s1和s2有自己的名称空间。但因为类supcls中没有构造方法__init__()初始化对象属性,所以它们的名称空间中除了python内部设置的一些"其它"属性,没有任何属于自己的属性.
但因为s1、s2连接到了supcls类,所以可以进行对象属性查找,如果对象中没有,将会向上找到supcls。例如:
1
2
3
|
print
(s1.x)
# 输出3,搜索到类名称空间
print
(s1.y)
# 输出4,搜索到类名称空间
# print(s1.z) # 这是错的
|
上面不再是通过完全限定的名称去访问类中的属性,而是通过对象属性查找的方式搜索到了类属性。但上面访问z属性将报错,因为还没有调用m方法.
当调用m方法后,将会通过self.xxx的方式设置完全属于对象自身的属性,包括x、y、z.
1
2
|
s1.m()
s2.m()
|
现在,它们的名称空间以及类的名称空间的关系如下图所示:
现在对象名称空间中有x、y和z共3个属性(不考虑其它python内部设置的属性),再通过对象名去访问对象属性,仍然会查找属性,但对于这3个属性的搜索不会进一步搜索到类的名称空间。但如果访问对象中没有的属性,比如m和n,它们不存在于对象的名称空间中,所以会搜索到类名称空间.
1
2
3
4
5
|
print
(s1.x)
# 对象属性333,搜索到对象名称空间
print
(s1.y)
# 对象属性444,搜索到对象名称空间
print
(s1.z)
# 对象属性555,搜索到对象名称空间
s1.m()
# 搜索到类名称空间
s1.n()
# 搜索到类名称空间
|
对象与对象之间的名称空间是完全隔离的,对象与类之间的名称空间存在连接关系。所以,s1和s2中的x和y和z是互不影响的,谁也看不见谁.
但现在想要访问类变量x、y,而不是对象变量,该怎么办?直接通过类名的完全限定方式即可:
1
2
|
print
(s1.x)
# 输出333,对象属性,搜索到对象名称空间
print
(supcls.x)
# 输出3,类属性,搜索到类名称空间
|
因为对象有了自己的名称空间,就可以直接向这个名称空间添加属性或设置属性。例如,下面为s1对象添加一个新的属性,但并不是在类内部设置,而是在类的外部设置:
1
2
|
s1.x
=
3333
# 在外部设置已有属性x
s1.var1
=
"aaa"
# 在外部添加新属性var1
|
新属性var1将只存在于s1,不存在于s2和类supcls中.
类属性和对象属性 。
属于类的属性称为类属性,即那些存在于类名称空间的属性。类属性分为类变量和类方法。有些类方法无法通过对象来调用,这类方法称为称为静态方法.
类似的,属于对象名称空间的属性称为对象属性。对象属性脱离类属性,和其它对象属性相互隔离.
例如:
1
2
3
4
5
6
7
|
class
cls
:
x
=
3
def
f():
y
=
4
print
(y)
def
m(
self
):
self
.z
=
3
|
上面的x、f、m都是类属性,x是类变量,f和m是类方法,z是对象属性.
子类继承时的名称空间 。
子类和父类之间有继承关系,它们的名称空间也通过一种特殊的方式进行了连接:子类可以继承父类的属性.
例如下面的例子,子类class childcls(supcls)表示childcls继承了父类supcls.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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().
因为有自己的名称空间,所以可以访问类属性。当访问的属性不存在于子类中时,将自动向上搜索到父类.
1
2
3
|
print
(childcls.x)
# 父类属性,搜索到父类名称空间
print
(childcls.y)
# 子类自身属性,搜索到子类名称空间
print
(childcls.z)
# 错误,子类和父类都没有该属性
|
当创建子类对象的时候,子类对象的变量搜索规则:
例如,创建子类对象c1,并调用子类的方法n():
1
2
|
c1
=
childcls()
c1.n()
|
现在,子类对象c1、子类childcls和父类supcls的关系如下图所示:
通过前面的说明,想必已经不用过多解释.
多重继承时的名称空间 。
python支持多重继承,只需将需要继承的父类放进子类定义的括号中即可.
1
2
3
4
5
6
7
8
|
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中,父类属性的搜索规则是先左后右,先深度后广度,搜索到了就停止.
如下图:
一般不建议使用多重继承,甚至不少语言根本就不支持多重继承,因为很容易带来属性混乱的问题.
类自身就是一个全局属性 。
在python中,类并没有什么特殊的,它存在于模块文件中,是全局名称空间中的一个属性.
例如,在模块文件中定义了一个类cls,那么这个cls就是一个全局变量,只不过这个变量中保存的地址是类代码块所在数据对象.
1
2
3
|
# 模块文件顶层
class
cls
():
n
=
3
|
而模块本身是一个对象,有自己的模块对象名称空间(即全局名称空间),所以类是这个模块对象名称空间中的一个属性,仅此而已.
另外需要注意的是,类代码块和函数代码块不一样,涉及到类代码块中的变量搜索时,只会根据对象与类的连接、子类与父类的继承连接进行搜索。不会像全局变量和函数一样,函数内可以向上搜索全局变量、嵌套函数可以搜索外层函数.
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 全局范围
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是面向对象时名称空间之间的连接关系,在搜索属性的时候可以顺着"这根树"不断向上爬,直到搜索到属性.
__dict__就是名称空间 。
前面一直说名称空间,这个抽象的东西用来描述作用域,比如全局作用域、本地作用域等等.
在其他语言中可能很难直接查看名称空间,但是在python中非常容易,因为只要是数据对象,只要有属性,就有自己的__dict__属性,它是一个字典,表示的就是名称空间。__dict__内的所有东西,都可以直接通过点"."的方式去访问、设置、删除,还可以直接向__dict__中增加属性.
例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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,所作的修改都会直接对名称空间起作用.
1
2
3
4
|
>>> c.newkey
=
"newkey"
>>> c.__dict__[
"hello"
]
=
"world"
>>> c.__dict__
{
'z'
:
5
,
'newkey'
:
'newkey'
,
'hello'
:
'world'
}
|
注意,__dict__表示的是名称空间,所以不会显示类的属性以及父类的属性。正如上面刚创建childcls的实例时,dict中是空的,只有在c.f()之后才设置独属于对象的属性.
如果要显示类以及继承自父类的属性,可以使用dir().
例如:
1
2
3
4
5
6
7
|
>>> c1
=
childcls()
>>> c1.__dict__
{}
>>>
dir
(c1)
[
'__class__'
,
'__delattr__'
,
'__dict__'
,
......
'f'
,
'x'
,
'y'
]
|
关于__dict__和dir()的详细说明和区别,参见.
__class__和__base__ 。
前面多次提到对象和类之间有连接关系,子类与父类也有连接关系。但是到底是怎么连接的?
对象与类之间,通过__class__进行连接:对象的__class__的值为所属类的名称 子类与父类之间,通过__bases__进行连接:子类的__bases__的值为父类的名称 。
例如:
1
2
3
4
5
6
7
8
9
|
class
supcls():
x
=
3
class
childcls(supcls):
y
=
4
def
f(
self
):
self
.z
=
5
c
=
childcls()
|
c是childcls类的一个实例对象:
1
2
|
>>> c.__class__
<
class
'__main__.childcls'
>
|
childcls继承自父类supcls,父类supcls继承自祖先类object:
1
2
3
4
5
|
>>> childcls.__bases__
(<
class
'__main__.supcls'
>,)
>>> supcls.__bases__
(<
class
'object'
>,)
|
查看类的继承层次 。
下面通过__class__和__bases__属性来查看对象所在类的继承树结构.
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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 。
最后此篇关于一篇文章搞懂Python的类与对象名称空间的文章就讲到这里了,如果你想了解更多关于一篇文章搞懂Python的类与对象名称空间的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
HTTP缓存相关的问题好像是前端面试中比较常见的问题了,上来就会问什么cache-control字段有哪些,有啥区别啥的。嗯……说实话,我觉得至少在本篇来说,HTTP缓存还算不上复杂,只是字段稍
代理,其实全称应该叫做代理服务器,它是客户端与服务器之间得中间层,本质上来说代理就是一个服务器,在HTTP的链路中插入的一个中间环节,就是代理服务器啦。所谓的代理服务就是指:服务本身不生产内容,
我们在前两篇的内容中分别学习了缓存和代理,大致了解了缓存有哪些头字段,代理是如何服务于服务器和客户端的,那么把两者结合起来,代理缓存,也就是说代理服务器也可以缓存,当客户端请求数据的时候,未必一
在前面的章节,我们把HTTP/1.1的大部分核心内容都过了一遍,并且给出了基于Node环境的一部分示例代码,想必大家对HTTP/1.1已经不再陌生,那么HTTP/1.1的学习基本上就结束了。这两
我们前一篇学习了HTTP/2,相比于HTTP/1,HTTP/2在性能上有了大幅的改进,但是HTTP/2因为底层还是基于TCP协议的,虽然HTTP/2在应用层引入了流的概念,利用多路复用解决了队头
前面我们花了很大的篇幅来讲HTTP在性能上的改进,从1.0到1.1,再到2.0、3.0,HTTP通过替换底层协议,解决了一直阻塞性能提升的队头阻塞问题,在性能上达到了极致。 那么,接下
上一篇噢,我们搞明白了什么是安全的通信,这个很重要,特别重要,敲黑板!! 然后,我们还学了HTTPS到底是什么,以及HTTPS真正的核心SSL/TLS是什么。最后我们还聊了聊TLS的实
经过前两章的学习,我们知道了通信安全的定义以及TLS对其的实现~有了这些知识作为基础,我们现在可以正式的开始研究HTTPS和TLS协议了。嗯……现在才真正开始。 我记得之前大概聊过,当
这一篇文章,我们核心要聊的事情就是HTTP的对头阻塞问题,因为HTTP的核心改进其实就是在解决HTTP的队头阻塞。所以,我们会讲的理论多一些,而实践其实很少,要学习的头字段也只有一个,我会在最开始
我们在之前的文章中介绍HTTP特性的时候聊过,HTTP是无状态的,每次聊起HTTP特性的时候,我都会回忆一下从前辉煌的日子,也就是互联网变革的初期,那时候其实HTTP不需要有状态,就是个浏览页面
前面几篇文章,我从纵向的空间到横向的时间,再到一个具体的小栗子,可以说是全方位,无死角的覆盖了HTTP的大部分基本框架,但是我聊的都太宽泛了,很多内容都是一笔带过,再加上一句后面再说就草草结束了。
大家好,我是煎鱼。 在 Go 语言中总是有一些看上去奇奇怪怪的东西,咋一眼一看感觉很熟悉,但又不理解其在 Go 代码中的实际意义,面试官却爱问... 今天要给大家介绍的是 SliceHead
我是一名优秀的程序员,十分优秀!