Python基础:17类和实例之一(类属性和实例属性)

时间:2024-01-05 11:09:14

1:类通常在一个模块的顶层进行定义。对于Python来说,声明与定义类是同时进行的。

2:类属性仅与其类相绑定,类数据属性仅当需要有更加“静态”数据类型时才变得有用,这种属性是静态变量。它们表示这些数据是与它们所属的类绑定的,不依赖于任何类实例。类似于Java或C++中在一个变量声明前加上static 关键字。

3:方法,比如下面,类MyClass 中的myNoActionMethod 方法,仅仅是一个作为类定义一部分定义的函数。(这使得方法成为类属性)。这表示myNoActionMethod 仅应用在MyClass 类型的对象(实例)上。

class  MyClass(object):
def myNoActionMethod(self):
pass >>> mc = MyClass()
>>>mc.myNoActionMethod()

任何像函数一样对myNoActionMethod 自身的调用都将失败:

>>>myNoActionMethod()
Traceback (innermost last):
File "<stdin>",line 1, in ?
myNoActionMethod() NameError:myNoActionMethod

引发了NameError 异常,因为在全局名字空间中,没有这样的函数存在。甚至由类调用此方法也失败了。

>>>MyClass.myNoActionMethod()
Traceback (innermost last):
File"<stdin>", line 1, in ?
MyClass.myNoActionMethod()
TypeError: unbound method must be called with class instance 1st argument

这是因为,Python 严格要求,没有实例,方法是不能被调用的。这种限制即Python所描述的绑定概念(binding),在此,方法必须绑定(到一个实例)才能直接被调用。

然而,不管是否绑定,方法都是它所在的类的固有属性,即使它们几乎总是通过实例来调用的。

4:要知道一个类有哪些属性,有两种方法。最简单的是使用dir()内建函数。另外是通过访问类的字典属性__dict__,这是所有类都具备的特殊属性之一。例子:

class  MyClass(object):
'MyClass class definition'
myVersion = '1.1'
def showMyVersion(self):
print MyClass.myVersion >>> dir(MyClass)
['__class__', '__delattr__','__dict__', '__doc__','__getattribute__', '__hash__', '__init__','__module__','__new__', '__reduce__', '__reduce_ex__','__repr__','__setattr__', '__str__', '__weakref__', 'myVersion','showMyVersion'] >>> print MyClass.__dict__
{'showMyVersion':<function showMyVersion at 0x59370>,'__dict__': <attribute '__dict__'of 'MyClass' objects>,'myVersion': '1.1','__weakref__': <attribute'__weakref__' of 'MyClass' objects>, '__doc__':'MyClassclass definition'}   

注意,dir返回的结果,除了包含类自身的属性之外,还包括所有父类的属性。而__dict__则只包含类本身的属性。类的__dict__是只读的,不可更新的。 dir的结果包含了__dict__。

dir()返回的仅是属性的一个名字列表,而__dict__返回的是一个字典,它的键(keys)是属性名,键值(values)是相应的属性对象的数据值。

内建的vars()函数接受类对象作为参数,返回类的__dict__属性的内容。

5:对任何类C,都具有特殊属性:

C.__name__

类C的名字(字符串)

C.__doc__

类C的文档字符串

C.__bases__

类C的所有父类构成的元组

C.__dict__

类C的属性

C.__module__

类C定义所在的模块(1.5 版本新增)

C.__class__

实例C对应的类(仅新式类中)

根据上面定义的类MyClass,有如下结果:

>>>MyClass.__name__
'MyClass' >>> MyClass.__doc__
'MyClass class definition' >>> MyClass.__bases__
(<type 'object'>,) >>> print MyClass.__dict__
{'__doc__': None, 'myVersion': 1, 'showMyVersion': <functionshowMyVersion at 950ed0>, '__module__': '__main__'} >>> MyClass.__module__
'__main__' >>> MyClass.__class__
<type 'type'>

__name__是给定类的字符名字。一些内建的类型也有这个属性。可以使用类型对象的__name__属性来取得相应的字符串名。如下例示:

>>> stype  =  type('Whatis your quest?')
>>> stype
<type 'string'>
>>> stype.__name__
'str'
>>> >>> type(3.14159265)
<type 'float'>
>>>type(3.14159265).__name__
'float'

__doc__是类的文档字符串,与函数及模块的文档字符串相似,必须紧随头行(header line)后的字符串。文档字符串不能被派生类继承,也就是说派生类必须含有它们自己的文档字符串。

__bases__包含了一个由所有父类组成的元组。

__dict__属性包含一个字典,由类的属性组成。访问一个类属性的时候,Python 解释器将会搜索字典以得到需要的属性。如果在__dict__中没有找到,将会在基类的字典中进行搜索。对类的修改会仅影响到此类的字典;基类的__dict__属性不会被改动的。

Python支持模块间的类继承。1.5 版本中引入了__module__,这样类名就完全由模块名所限定。看一下下面的例子:

>>> class  C(object):
... pass
... >>> C
<class __main__.C at0x53f90> >>>C.__module__
'__main__'

类C 的全名是“__main__.C”,比如,source_module.class_name。如果类C 位于一个导入的模块中,如mymod,像下面的:

>>> from  mymod  import  C
>>> C
<class mymod.C at 0x53ea0> >>>C.__module__
'mymod'

最后,由于类型和类的统一性,当访问任何类的__class__属性时,将发现它就是一个类型对象的实例。对于经典类,这个属性并未定义。

>>> class  test(object): pass
... >>>test.__class__
<type 'type'>
>>>
>>> class test: pass
...
>>>test.__class__
Traceback (most recentcall last):
File "<stdin>", line 1, in<module>
AttributeError:class test has no attribute '__class__'

6:实例

类和类型在2.2 版本中就统一了,新式类如下:

>>> mc  =  MyClass()
>>> type(mc)
<class '__main__.MyClass'>
>>> type(0)
<type 'int'>

MyClass 和 int,你将会发现二者都是类型(type):

>>>type(MyClass)
<type 'type'>
>>> type(int)
<type 'type'>

在Python2.2 版本之前,对于经典类,类是类类型,实例是实例类型。在这两个对象类型之间没有任何关系,除了实例的__class__属性引用了该类。经典类如下:

>>>type(mc)
<type 'instance'> >>> type(0)
<type 'int'> >>>type(MyClass)
<type 'class'> >>> type(int)
<type 'builtin_function_or_method'>

7:当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python 检查是否实现了__init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任何特别的操作,实例化过程完毕。

如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递进去,像标准方法调用一样。__init__()是在解释器为你创建一个实例后调用的第一个方法,在你开始使用它之前,这一步可以让你做些准备工作。

如果定义了__init__,它不应当返回任何对象,否则,就可能出现冲突,试着返回非None 的任何其它对象都会导致TypeError 异常:

>>> class  MyClass:
... def __init__(self):
... print 'initialized'
... return 1
... >>> mc = MyClass()
initialized
Traceback (innermost last):File "<stdin>", line 1, in ?
mc = MyClass()
TypeError:__init__() should return None

__new__()“构造器”方法

与__init__()相比,__new__()方法更像一个真正的构造器。它是一个类方法,并且传入的参数是在类实例化操作时生成的。__new__()会调用父类的__new__()来创建对象(向上代理)。

__new__()必须返回一个合法的实例,__new__()和__init__()在类创建时,都传入了(相同)参数。它们的参数必须一致,否则会出错。

__del__()"解构器"方法

有一个相应的特殊解构器(destructor)方法名为__del__()。这个函数要直到该实例对象所有的引用都被清除掉后才会执行。它通常没有被实现,因为实例很少被显式释放。举例:

class  C(P):
def __init__(self):
print 'initialized'
def __del__(self):
P.__del__(self) >>> c1 = C()
>>> c2 = c1
>>> c3 = c1
>>> id(c1), id(c2), id(c3)
(11938912, 11938912, 11938912)
>>> del c1
>>> del c2
>>> del c3
deleted

在上面的例子中,解构器是在类C 实例所有的引用都被清除掉后,才被调用的。一旦引用计数为0,则对象就被清除了。

总结:

不要忘记首先调用父类的__del__()。

调用 del x 不表示调用了x.__del__() -----它仅仅是减少x 的引用计数。

__del__()未捕获的异常会被忽略掉(因为一些在__del__()用到的变量或许已经被删除了)。

不要在__del__()中干与实例没任何关系的事情。

除非你知道你正在干什么,否则不要去实现__del__()。

8:实例属性

实例仅拥有数据属性,方法是类属性,实例的数据属性只是与某个类的实例相关联的数据值,这些值独立于其它实例或类。当一个实例被释放后,它的属性同时也被清除了。

设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。可以能够在“运行时”创建实例属性,是Python 类的优秀特性之一。但是这样创建属性时,必须谨慎。

内建函数dir()同样还可以打印所有实例属性:

>>> class  C(object):
... pass >>> c = C()
>>> c.foo = 'roger'
>>> c.bar = 'shrubber'
>>> dir(c)
['__class__', '__delattr__','__dict__', '__doc__', '__getattribute__', '__hash__', '__init__','__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__','__str__', '__weakref__', 'bar', 'foo']

实例也有一个__dict__特殊属性(可以调用vars()并传入一个实例来获取),它是实例属性构成的一个字典:

>>>c.__dict__
{'foo': 'roger', 'bar': 'shrubber'}

dir(实例)也包含类的属性。实例的__dict__只有实例属性,它是可写的。没有类属性或特殊属性。

实例仅有两个特殊属性,对于任意对象I:

I.__class__

实例化I 的类

I.__dict__

I 的属性

现在使用类C 及其实例C 来看看这些特殊实例属性:

>>> class  C(object):
... pass
... >>> c = C()
>>> dir(c)
['__class__', '__delattr__','__dict__', '__doc__', '__format__', '__getattribute__', '__hash__','__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__','__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] >>> c.__dict__
{} >>> c.__class__
<class '__main__.C'>

可以看到,c 现在还没有数据属性,但我们可以添加一些再来检查__dict__属性,看是否添加成功了:

>>> c.foo  =  1
>>> c.bar = 'SPAM'
>>> '%d can of %splease' % (c.foo, c.bar)
'1 can of SPAM please'
>>>c.__dict__
{'foo': 1, 'bar':'SPAM'}

对类和实例来说,尽管__dict__属性是可修改的,但还是建议你不要修改这些字典,使用熟悉的句点属性标识来访问及操作属性会更易于接受。

9:内建类型属性

内建类型也是类,对内建类型也可以使用dir(),与任何其它对象一样,可以得到一个包含它属性名字的列表:

>>> x  =  3+0.14j
>>> x.__class__
<type 'complex'> >>> dir(x)
['__abs__', '__add__','__class__', '__coerce__','__delattr__', '__div__', '__divmod__', '__doc__','__eq__','__float__', '__floordiv__', '__ge__','__getattribute__','__getnewargs__', '__gt__', '__hash__', '__init__','__int__','__le__', '__long__', '__lt__', '__mod__','__mul__', '__ne__', '__neg__','__new__', '__nonzero__','__pos__', '__pow__', '__radd__', '__rdiv__','__rdivmod__','__reduce__', '__reduce_ex__', '__repr__','__rfloordiv__','__rmod__', '__rmul__', '__rpow__', '__rsub__','__rtruediv__','__setattr__', '__str__', '__sub__','__truediv__', 'conjugate', 'imag', 'real'] >>> x.imag
2.0 >>> x.real
1.0 >>> x.conjugate()
(1-2j)

试着访问__dict__会失败,因为在内建类型中,不存在这个属性:

>>> x.__dict__
Traceback (innermost last):File "<stdin>", line 1, in ?
AttributeError: __dict__

10:实例属性 vs 类属性

类属性和实例无关。这些值像静态成员那样被引用,即使在多次实例化中调用类,它们的值都保持不变。不管如何,静态成员不会因为实例而改变它们的值,除非实例中显式改变它们的值。

类和实例都是名字空间。类是类属性的名字空间,实例则是实例属性的。

可采用类来访问类属性,如果实例没有同名的属性的话,你也可以用实例来访问。

类属性可通过类或实例来访问。下面的示例中,类C 在创建时,带一个version 属性,这样通过类对象来访问它是很自然的了,比如,C.version。当实例c 被创建后,对实例c 而言,访问c.version 时,Python 首先会在实例中搜索名字version,然后是类,再就是继承树中的基类。本例中,version 在类中被找到了:

>>> class  C(object):
... version = 1.2
... >>> c = C()
>>>C.version
1.2 >>>c.version
1.2 >>>C.version += 0.1
>>>C.version
1.3 >>>c.version
1.3

只有当使用类引用version 时,才能更新它的值,像上面的C.version 递增语句。如果尝试在实例中设定或更新类属性会创建一个实例属性c.version,后者会阻止对类属性C.versioin 的访问,因为第一个访问的就是c.version,这样可以对实例有效地“遮蔽”类属性C.version,直到c.version 被清除掉。

从实例中访问类属性须谨慎

与通常Python 变量一样,任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并且对其赋值。如果类属性中存在同名的属性,有趣的副作用即产生。(经典类和新式类都存在)

>>> class  Foo(object):
... x = 1.5
... >>> foo = Foo()
>>> foo.x
1.5 >>> foo.x =1.7
>>> foo.x
1.7 >>> Foo.x
1.5 >>> del foo.x
>>> foo.x
1.5

所以,给一个与类属性同名的实例属性赋值,我们会有效地“隐藏”类属性,但一旦我们删除了这个实例属性,类属性又重见天日。

现在再来试着更新类属性,但这次,尝试一下增量动作:

>>> foo.x  +=  2
>>> foo.x
1.7
>>> Foo.x
1.5

同样创建了一个新的实例属性,类属性原封不动。(深入理解Python 相关知识:属性已存于类字典[__dict__]中。通过赋值,其被加入到实例的__dict__中了。)赋值语句右边的表达式计算出原类的变量,增加0.2,并且把这个值赋给新创建的实例属性。

但在类属性可变的情况下,一切都不同了,注意:使用实例属性来试着修改类属性是很危险的:

>>> class  Foo(object):
... x = {2003: 'poe2'}
...
>>> foo = Foo()
>>> foo.x
{2003: 'poe2'} >>>foo.x[2004] = 'valid path'
>>> foo.x
{2003: 'poe2', 2004: 'valid path'} >>> Foo.x
{2003: 'poe2', 2004: 'valid path'} >>> del foo.x
Traceback (most recent calllast): File "<stdin>", line 1, in ?
del foo.x
AttributeError: x
>>>

当一个实例在类属性被修改后才创建,那么更新的值就将生效。类属性的修改会影响到所有的实例:

>>> class  C(object):
... spam = 100
... >>> c1 = C()
>>> c1.spam
100 >>> C.spam += 100
>>> C.spam
200 >>> c1.spam
200 >>> c2 = C()
>>> c2.spam
200 >>>del c1
>>> C.spam += 200
>>> c2.spam
400

访问类属性或者实例属性的时候,都需要加上名称空间,比如:

class  test(object):
name = ‘test’
def __init__(self):
self.name = ‘instance’ def foo(self):
name = ‘foo’
print ‘name is ’, name
print ‘1class name is ’, test.name
print ‘2class name is ’, self.__class__.name
print ‘3class name is ’, type(self).name
print ‘self name is ’, self.name testobj = test()
testobj.foo()

结果是:

name is  foo
1class name is test
2class name is test
3class name is test
self name is instance