首页 > 代码库 > Python——类的高级主题

Python——类的高级主题

介绍关于类的一些高级主题,这些是可选的,在Python应用程序中,不会常常遇到。

==========================================================================

slots实例
将字符串属性名称顺序赋值给特殊的__slots__类属性。就能够限制类的实例将有的合法属性集。
这个特殊属性通常是在class语句顶层内将字符串名称顺序赋值给变量__slots__而设置:仅仅有__slots__列表内的这些变量名可赋值为实例属性。而实例属性名必须在引用前赋值,即使是列在__slots__中也是这样。看下述样例:

>>> class limiter(object):
	__slots__ = ['age','name','job']

	
>>> x = limiter()
>>> x.age
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    x.age
AttributeError: age
>>> x.age = 40
>>> x.age
40
>>> x.ape = 1000
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    x.ape = 1000
AttributeError: 'limiter' object has no attribute 'ape'
假设创建了非常多实例而且仅仅有几个属性是必需的话。那么为每一个实例对象分配一个命名空间字典可能在内存方面的代价过于昂贵。要节省空间和运行速度,slot属性能够顺序存储以供高速查找,而不是为每一个实例分配一个字典。


-------------------------------------------------------------------------------------------------------------------------------------

Slot和通用代码
实际上。有些带有slots的实例或许根本就没有__dict__属性字典。所以在有些程序中要使用比__dict__更为存储中立的工具,比如getattr、setattr和dir内置函数。

>>> class C:
	__slots__ = ['a','b']

	
>>> X = C()
>>> X.a = 1
>>> X.a
1
>>> X.__dict__
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    X.__dict__
AttributeError: 'C' object has no attribute '__dict__'
>>> getattr(X,'a')
1
>>> setattr(X,'b',2)
>>> X.b
2
>>> 'a' in dir(X)
True
>>> 'b' in dir(X)
True
没有属性命名空间字典,不可能给不是slots列表中名称的实例来分配新的名称,然而,通过在__slots__中包括__dict__仍然能够容纳额外的属性,从而考虑到一个属性空间字典的须要。在这个样例中。两种机制都用到了,可是,getattr这种通用工具同意我们将它们当做单独一组属性对待:
>>> class D:
	__slots__ = ['a','b','__dict__']
	c = 3
	def __init__(self):
		self.d = 4

		
>>> X = D()
>>> X.d
4
>>> X.__dict__
{'d': 4}
>>> X.__slots__
['a', 'b', '__dict__']
>>> X.c
3
>>> X.a
Traceback (most recent call last):
  File "<pyshell#24>", line 1, in <module>
    X.a
AttributeError: a
>>> X.a = 1
>>> getattr(X,'a'),getattr(X,'c'),getattr(X,'d')
(1, 3, 4)
==========================================================================
类特性
有一种称为特性(property)的机制,提供还有一种方式让类定义自己主动调用的方法,来读取或赋值实例属性。
【特性和slots都是基于属性描写叙述器的新概念】

简而言之,特性是一种对象,赋值给类属性名称。特性的产生是以三种方法(获得、设置以及删除运算的处理器)以及通过文档字符串调用内置函数property。假设不论什么參数以None传递或者省略。该运算就不能支持。


特性一般都是在class语句顶层赋值【比如,name = property(...)】。这样赋值时。对类属性本身的读取(比如。obj.name),就会自己主动传给property的一个读取方法。例如以下例:

>>> class new():
	def getage(self):
		return 40
	age = property(getage,None,None,None)  # get,set,del,docs

	
>>> x = new()
>>> x.age
40
>>> x.name
Traceback (most recent call last):
  File "<pyshell#34>", line 1, in <module>
    x.name
AttributeError: 'new' object has no attribute 'name'
特性比传统技术执行起来更快。

比如,当我们新增属性赋值运算支持时,特性就变得更有吸引力:输入的代码更少,对我们不希望动态计算的属性进行赋值运算时,不会发生额外的方法调用。

>>> class new():
	def getage(self):
		return 40
	def setage(self,value):
		print('set age:',value)
		self._age = value
	age = property(getage,setage,None,None)

	
>>> x = new()
>>> x.age
40
>>> x.age = 42
set age: 42
>>> x.age
40
>>> x._age
42
>>> x.job = 'trainer'
>>> x.job
'trainer'
==========================================================================

__getattribute__和描写叙述符

__getattribute__能够让类拦截全部属性的引用。而不局限于没有定义的引用(假设__getattr__)。


除了特性和运算符重载方法,Python支持属性描写叙述符的概念——带有__get__和__set__方法的类,分配给类属性而且由实例继承,这拦截了对特定属性的读取和写入訪问。描写叙述符在某种意义上是特性的一种更加通用的形式。

关于特性、__getattribute__和描写叙述符这些高级话题将在兴许逐步介绍。
==========================================================================

静态方法

类方法通常在其第一个參数中传递一个实例对象。以充当方法调用的一个隐式主体。有时候。程序须要处理与类而不是与实例相关的数据,也就是我们须要一个类中的方法不仅不传递并且也不期待一个self实例參数。

Python通过【静态方法】的概念来支持这种目标——嵌套在一个类中的没有self參数的简单函数。

比如。如果我们想使用类属性去计算从一个类产生了多少实例。我们能够把一个计数器存储为类属性。每次创建一个新的实例的时候。构造函数都会对计数器加1。而且,有一个显示计数器值的方法。【记住,类属性是由全部实例共享的】:

>>> class Spam:
	numInstances = 0
	def __init__(self):
		Spam.numInstances += 1
	def printNumInstances():
		print('Number of instances created:',Spam.numInstances)
printNumInstances方法旨在处理类数据而不是实例数据——它是关于全部实例的,而不是某个特定的实例。因此,我们想要不必传递一个实例就能够调用它。

实际上,我们不想生成一个实例来获取实例的数目。由于这本身就会改变我们想要获取的实例的数目。

换句话说:我们想要一个无self的静态方法。



我们能够看一下測试结果:

>>> a = Spam()
>>> b = Spam()
>>> c = Spam()
>>> Spam.printNumInstances()
Number of instances created: 3
>>> a.printNumInstances()
Traceback (most recent call last):
  File "<pyshell#66>", line 1, in <module>
    a.printNumInstances()
TypeError: printNumInstances() takes 0 positional arguments but 1 was given
能够看到,我们通过类调用无self方法执行成功。而通过实例调用无self方法出错了,由于參数不正确。通过一个实例调用方法。这个实例会自己主动传递给方法的第一个參数中,可是这个无self方法并不存在參数来接收它。



假设你可以坚持仅仅通过类调用无self方法。那么事实上你已经有了一个静态方法了。然而假设你还想通过实例调用,那么就须要採取其它设计,或者把这个方案标记为特殊的。
-------------------------------------------------------------------------------------------------------------------------------------

使用静态方法
要将这个无self方法标记为静态方法。须要调用内置函数staticmethod。例如以下:

>>> class Spam:
	numInstances = 0
	def __init__(self):
		Spam.numInstances += 1
	def printNumInstances():
		print('Number of instances created:',Spam.numInstances)
	printNumInstances = staticmethod(printNumInstances)  # staticmethod

	
>>> a = Spam()
>>> b = Spam()
>>> c = Spam()
>>> a.printNumInstances()
Number of instances created: 3
静态方法对于处理一个类本地的数据来说是更好的解决方式。
==========================================================================

装饰器和元类:初识

上边提到的staticmethod可能比較奇怪。所以新增了一个功能,要让这个运算变得简单。【函数装饰器】提供了一种方式,替函数明白了特定的运算模式。也就是将函数包裹了还有一层,在还有一函数的逻辑内实现。



函数装饰器变成了通用的工具:除了静态方法使用方法外。也可用于新增多种逻辑的函数。比如,能够用来记录函数调用的信息和在出错时检查传入的參数类型等

Python提供了一下内置的函数装饰器,来做一些运算,比如。标识静态方法,可是我们也能够编写自己的随意装饰器。尽管不限于使用类,但用户定义的函数装饰器通常也写成类,把原始函数和其它数据当成状态信息。

-------------------------------------------------------------------------------------------------------------------------------------

函数装饰器基础
从语法上讲,函数装饰器是它后边的函数的执行时的声明。

函数装饰器是写成一行,就在定义函数或方法的def语句之前,并且由@符号、后面跟着所谓的元函数组成:也就是管理还有一函数的函数。

比如,如今的静态方法我们能够用下述的装饰器语法编写:

class C:
	@staticmethod
	def printNumInstances():
		...
这个语法和以下的写法有同样的效果:
class C:
	def meth():
		...
	meth = staticmethod(meth)
结果就是,调用方法函数的名称。实际上是触发了它staticmethod装饰器的结果。


-------------------------------------------------------------------------------------------------------------------------------------

装饰器样例
之前介绍过,__call__运算符重载方法为类实例实现函数调用接口。

以下的程序通过这样的方法定义类。在实例中存储装饰的函数,并捕捉对最初变量名的调用。

class tracer:
    def __init__(self,func):
        self.calls = 0
        self.func = func
    def __call__(self,*args):
        self.calls += 1
        print('call %s to %s'%(self.calls,self.func.__name__))
        self.func(*args)

@tracer
def spam(a,b,c):
    print(a,b,c)

spam(1,2,3)
spam('a','b','c')
spam(4,5,6)
由于spam函数时通过tracer装饰器运行的,所以当最初的变量名spam调用时。实际上触发的是类中的__call__方法,这种方法会计算和记录该次调用,然后托付给原始的包裹的函数。

因此,此装饰器可用于包裹携带随意数目參数的不论什么函数。



结果就是新增一层逻辑至原始的spam函数。执行结果例如以下:第一行来自tracer类,第二行来自spam函数。

call 1 to spam
1 2 3
call 2 to spam
a b c
call 3 to spam
4 5 6
这里仅仅是初步了解。兴许我们将会介绍各种各种的方法来编写函数装饰器。

-------------------------------------------------------------------------------------------------------------------------------------
类装饰器和元类

类装饰器类似于函数装饰器,可是。它们在一条class语句的末尾执行,而且把一个类名又一次绑定到一个可调用对象,相同的,它们能够用来管理类,或者当随后创建实例的时候插入一个包装逻辑层来管理实例。代码结构例如以下:

def decorator(aClass):...

@decorator
class C:...
被映射为下列相当代码:
def decorator(aClass):...

class C:...
C = decorator(C)
元类是一种类似的基于类的高级工具。其用途往往与类装饰器有所重合。

它们提供了一种可选的模式。会把一个类对象的创建导向到顶级type类的一个子类。



关于装饰器和元类的内容,将在之后更加具体地介绍。

Python——类的高级主题