Last Updated: 2023-05-12 01:05:05 Friday
-- TOC --
在Python中,class本身也是一个对象,且可以绑定变量,被称为类变量。对应类变量的,就是实例变量,绑定在实例对象上。这两类变量有着微秒的区别和联系。
Python中一切都是对象,定义一个class,这个class本身也是个对象,用class在运行时创建的instance,也是对象,但它们是不同类型的对象。
我们习惯了给instance对象设置各种变量,而Python提供了一个机制,可以给class对象设置变量,这些变量被称为是类变量,class variable
。
class variable这个机制,从逻辑上看,它可以用于存储从这个class派生出来的所有instance共用的数据。这很像在C++的class中定义static成员变量。访问类变量建议通过类名,虽然通过instance也可以访问,但可能很容易出现通过instance尝试修改类变量的错误。比如:
>>> class xyz():
... a = 0
...
>>> xyz.a
0
>>> xyz.a = 9
>>> xyz.a
9
>>> x = xyz()
>>> x.a
9
>>> x.a = 88
>>> x.a
88
>>> xyz.a
9
当执行x.a = 88
这行代码的时候,实际上是给x对象创建了一个名为a变量,并没有修改类变量xyz.a
的值。但是,下面这种用法却是正确的:
>>> class xyz():
... b = {}
...
>>> xyz.b.setdefault('a',1)
1
>>> xyz.b
{'a': 1}
>>> y = xyz()
>>> y.b.setdefault('b',2)
2
>>> y.b
{'a': 1, 'b': 2}
>>> xyz.b
{'a': 1, 'b': 2}
上面的代码为何就正确了?
可以这样来理解,y.b.setdefault
这行代码在访问b
,调用b的一个接口,并不是用=
进行创建并赋值,这时Python解释器要去寻找b在哪里,而不是创建一个新的b。因此,通过instance也可以实现修改类变量的效果。
上面示例的这个微妙的差异,使用时必须要非常小心,虽然Python标准Lib里面也有这样的用法。比如下面的写法,就又变成错误的了:
>>> class xyz():
... b = {}
...
>>> z = xyz()
>>> z.b = [1,2,3] # create z.b
>>> z.b
[1, 2, 3]
>>> xyz.b
{}
Python中的class,在运行时也是存在于内存中的一个对象,可以为class动态地创建或删除class variable:
>>> class xyz(): pass
...
>>> xyz.a = 1
>>> xyz.b = {}
>>> xyz.b.setdefault('a',9)
9
>>> xyz.a
1
>>> xyz.b
{'a': 9}
>>> del xyz.a
>>> del xyz.b
比较有趣味的细节是,定义在class中(非def内)的代码,实际上在导入时,会被直接执行:
>>> class codeinclass():
... for i in range(10): print(i)
...
0
1
2
3
4
5
6
7
8
9
>>> cic = codeinclass()
>>> cic
<__main__.codeinclass object at 0x7fc73001e358>
类变量就是这样在运行时被执行了一次。
当instance和class都有相同名称的成员时,优先访问instance的。但我认为,这是代码设计需要避免的容易出错的细节。
__dict__
vars
是builtin接口,返回输入对象的__dict__
属性,这里面存放了所有与对象绑定的实例变量。
>>> class xyz():
... pass
...
>>> x = xyz()
>>> x.a = 1
>>> x.b = 2
>>> vars(x)
{'a': 1, 'b': 2}
>>> x.__dict__
{'a': 1, 'b': 2}
instance variables都放在instance的__dict__
中。
vars接口的一个作用,就是用访问dict的方式,访问instance variable,比如特别好用的setdefault
:
>>> vars(x).setdefault('c',3)
3
>>> vars(x).setdefault('c',5)
3
>>> vars(x)
{'a': 1, 'b': 2, 'c': 3}
事实上,class variables也都是存放在class对象的__dict__
中的:
>>> class xyz(): pass
...
>>> xyz.a = 11
>>> xyz.b = 22
>>> vars(xyz)
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'xyz' objects>, '__weakref__': <attribute '__weakref__' of 'xyz' objects>, '__doc__': None, 'a': 11, 'b': 22})
如果类变量是descriptor(descriptor只能是class variable),并且定义了__set__
接口,通过实例对此变量进行setting的时候,不会创建同名实例变量,而是访问descriptor的set接口。
class bob():
def __get__(self, obj, objtype=None):
print("bob.__get__")
return obj._b
def __set__(self, obj, val):
print("bob.__set__")
obj._b = val+1 # crate x._b
class xyz():
b = bob()
def __init__(self, bob):
self.b = bob # call xyz.b.__set__
x = xyz(777)
print(x.b)
x.b = 888 # call xyz.b.__set__
print(x.b)
一般用_暗示
这是个私有的成员,但Python是允许直接访问的,毕竟大家都是成年人了...
Python的面向对象语法比C++要简化了很多,比如成员定义的时候,由于不需要申明,也没有private和public关键词。有人说,Python对象没有私有成员。这个说法,其实,对也不对!
用__
实现私有成员
双下划线开头的成员,不管是变量还是函数,都可以说是私有的,只能内部访问。
class test():
def __init__(self):
self.__foo = 'foo'
self.__bar = 'bar'
def show(self):
self.__show()
def __show(self):
print(self.__foo)
print(self.__bar)
t = test()
t.show() # success
t.__show() # fail
调用t.show()会成功,但是调用t.__show()会失败,此时Python解释器提示:AttributeError: 'test' object has no attribute '__show'
。同样,直接访问t.__foo和t.__bar,一样失败,一样提示没有此属性。使用hasattr接口判断,返回False!
但,__
加持的私有成员还是可以直接访问的...
t = test()
t._test__show()
print(t._test__foo)
print(t._test__bar)
用一个下划线加类名再加成员名称,obj._<className><__attributeName>
,就可以访问了。这基本只是在调试的时候,为了调试方便而打开的后门,代码可千万别写成这样了!
如果class定了类变量__slots__
,带来的效果是:
举个例子:
>>> class xyz():
... __slots__ = ('a','b')
...
>>> x = xyz()
>>> hasattr(x, '__dict__')
False
>>> x.a = 1
>>> x.b = 2
>>> x.c = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'xyz' object has no attribute 'c'
>>> vars(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: vars() argument must have __dict__ attribute
xyz的实例x,只能拥有属性a和b,其它名称的属性都不行,而且x没有魔法__dict__属性。
看起来新代码没有不使用__slots__的理由呀...
本文链接:https://cs.pynote.net/sf/python/202209301/
-- EOF --
-- MORE --