前言
《编写高质量python代码的59个有效方法》这本书分类逐条地介绍了编写python代码的有效思路和方法,对理解python和提高编程效率有一定的帮助。本笔记简要整理其中的重要方法。
承接上文//www.greatytc.com/p/15a6050220e6
//www.greatytc.com/p/1f6a2b3b502e
本篇介绍关于元类及属性的编程方法
4.元类(MetaClass)及属性
Python中的元类概念是一种比较模糊的概念,简单来讲就是可以把python中的class语句转译为元类,令其在每次定义具体的类时都提供独特的行为。
Python还具有一个奇妙的特性:可以动态地定义对属性的访问操作,这种动态属性也有可能带来令人意外的副作用。
用纯属性取代get和set方法
# C++等其他语言常用写法
# 在类中明确实现getter和setter
class OldResistor(object):
def __init__(self,ohms):
self._ohms=ohms
def get_ohms(self):
return self._ohms
def set_ohms(self,ohms):
self._ohms=ohms
r0=OldResistor(5)
print(r0.get_ohms())
r0.set_ohms(10)
print(r0.get_ohms())
r0.set_ohms(r0.get_ohms()+5)
这种自定义获取和设置的操作,在其他语言中很常见,但在python中不需要手工实现,直接从public属性开始:
class Resistor(object):
def __init__(self,ohms):
self.ohms=ohms
self.vol=0
self.current=0
r1=Resistor(5)
print(r1.ohms) # 5
r1.ohms=10
print(r1.ohms) # 10
此外,还可以通过@property和setter来实现一些特殊操作,如下所示,在给成员变量_vol赋值的同时,还进行了其他变量的修改。
class VolResistance(Resistor):
def __init__(self,ohms):
super().__init__(ohms)
self._vol=0
@property
def vol(self):
return self._vol
@vol.setter
def vol(self,vol):
self._vol=vol
self.current=self._vol/self.ohms
r2=VolResistance(5)
print(r2.current) # 0
r2.vol=10
print(r2.current) #2.0
@property和setter合作实现对类成员属性的修改,@property相当于getter操作,获取成员信息,而同时会默认创建相应的xx.setter装饰器,可以动态地对相应的成员属性进行修改。
用描述符来改写需要复用的@property方法
内置的@property修饰器存在不便于复用的问题,受它修饰的方法无法为同一类中的其他属性所复用。
Python提供了描述符(descriptor)来做,对访问操作进行一定转译:描述符类提供get和set方法
class Grade(object):
def __get__(*args,**kwargs):
pass
def __set__(*args,**kwargs):
pass
class Exam(object):
math_grade=Grade()
writing_grade=Grade()
science_grade=Grade()
在具体使用中,我们可以将每个实例对应的值记录到Grade描述符类中,使用字典保存每个实例的状态
class Grade(object):
def __init__(self):
self._values={}
def __get__(self,instance,instance_type):
print(self._values)
if instance is None:
return self
return self._values.get(instance,0)
def __set__(self,instance,value):
self._values[instance]=value
class Exam(object):
math_grade=Grade()
writing_grade=Grade()
science_grade=Grade()
exam=Exam()
exam.science_grade=40
print(exam.science_grade)
#{<__main__.Exam object at 0x7f1b6281ab90>: 40}
#40
#
这种写法存在一个严重的问题:泄露内存;在程序的生命期内,对于传给set的每个Exam实例,_values字典都会保存指向该实例的一份引用,导致该实例的引用计数无法将为0,使得内存无法被回收。 可以使用内置的weakref模块,使用WeakKeyDictionary的特殊字典,替代普通的字典。
from weakref import WeakKeyDictionary
class Grade(object):
def __init__(self):
self._values=WeakKeyDictionary()
def __get__(self,instance,instance_type):
print(self._values)
if instance is None:
return self
return self._values.get(instance,0)
def __set__(self,instance,value):
self._values[instance]=value
用_getattr_ /_getattribute_ /_setattr_实现需求
Python提供了一些挂钩(Hook),辅助通用代码的编写,将多个系统粘合起来。
例如:把数据库的行表示为Python对象,往往操作时优势我们不清楚行的结构,需要些通用的代码结构。
class DB(object):
def __init__(self):
self.exists=5
def __getattr__(self,name):
value='Value for %s'%name
setattr(self,name,value)
return value
data=DB()
print(data.__dict__) ## {'exists': 5}
print(data.foo)
print(data.__dict__) ## {'exists': 5, 'foo': 'Value for foo'}
在访问data缺少的foo属性时,会调用定义的getattr方法,从而修改实例的dict字典;这种行为适合实现无结构数据的按需访问,初次执行getattr把相关的属性加载进来。
然而有些情况下,我们需要动态地访问对象属性,使用_getattr_会导致属性加载后一直存在属性字典中;因此可以使用另一个方法:_getattribute_,每次访问对象属性时都会触发该方法,即使属性已经存在与属性字典中。
此外,可以用内置的hashattr函数判断对象是否已经拥有了相关的属性,并用内置的getattr函数来获取属性值。
在利用获取方法得到具体属性,可以通过_setattr_进行属性的赋值操作。
此外值得注意的一点是,要避免通过_getattribute_和_setattr_方法中访问实例属性,容易造成无限递归。
这个时候要采用super()._get__attribute_方法,从实例的属性字典里面直接获取_data属性值,以避免无限递归。