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 "", line 1, in 
    x.age
AttributeError: age
>>> x.age = 40
>>> x.age
40
>>> x.ape = 1000
Traceback (most recent call last):
  File "", line 1, in 
    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 "", line 1, in 
    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 "", line 1, in 
    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 "", line 1, in 
    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 "", line 1, in 
    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类的一个子类。

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

相关内容

    暂无相关文章

评论关闭