类和对象
类和函数一样都是Python中的对象。当一个类定义完成之后,Python将创建一个“类对象”并将其赋值给一个同名变量。类是type类型的对象(是不是有点拗口?)。
类对象是可调用的(callable,实现了 __call__方法),并且调用它能够创建类的对象。你可以将类当做其他对象那么处理。例如,你能够给它们的属性赋值,你能够将它们赋值给一个变量,你可以在任何可调用对象能够用的地方使用它们,比如在一个map中。事实上当你在使用map(str,[1,2,3])的时候,是将一个整数类型的list转换为字符串类型的list,因为str是一个类。可以看看下面的代码:
>>> class C(object): ... def __init__(self,s): ... print s ... >>> myclass = C >>> type(C) <type 'type'> >>> type(myclass) <type 'type'> >>> myclass(2) 2 <__main__.C object at 0x10e2bea50> >>> map(myclass,3]) 1 2 3 [<__main__.C object at 0x10e2be9d0>,<__main__.C object at 0x10e2bead0>,<__main__.C object at 0x10e2beb10>] >>> map(C,3]) 1 2 3 [<__main__.C object at 0x10e2be950>,<__main__.C object at 0x10e2beb50>,<__main__.C object at 0x10e2beb90>] >>> C.test_attribute = True >>> myclass.test_attribute True
正因如此,Python中的“class”关键字不像其他语言(例如C++)那样必须出现在代码main scope中。在Python中,它能够在一个函数中嵌套出现,举个例子,我们能够这样在函数运行的过程中动态的创建类。看代码:
>>> def make_class(class_name): ... class C(object): ... def print_class_name(self): ... print class_name ... C.__name__ = class_name ... return C ... >>> C1,C2 = map(make_class,["C1","C2"]) >>> c1,c2 = C1(),C2() >>> c1.print_class_name() C1 >>> c2.print_class_name() C2 >>> type(c1) <class '__main__.C1'> >>> type(c2) <class '__main__.C2'> >>> c1.print_class_name.__closure__ (<cell at 0x10ab6dbe8: str object at 0x10ab71530>,)
请注意,在这里通过make_class创建的两个类是不同的对象,因此通过它们创建的对象就不属于同一个类型。正如我们在装饰器中做的那样,我们在类被创建之后手动设置了类名。同样也请注意所创建类的print_class_name方法在一个closure cell中捕捉到了类的closure和class_name。如果你对closure的概念还不是很清楚,那么最好去看看前篇,复习一下closures和decorators相关的内容。
Metaclasses
如果类是能够制造对象的对象,那制造类的对象又该叫做什么呢(相信我,这并不是一个先有鸡还是先有蛋的问题)?答案是元类(Metaclasses)。大部分常见的基础元类都是type。当输入一个参数时,type将简单的返回输入对象的类型,这就不涉及元类。然而当输入三个参数时,type将扮演元类的角色,基于输入参数创建一个类并返回。输入参数相当简单:类名,父类及其参数的字典。后面两者可以为空,来看一个例子:
>>> MyClass = type("MyClass",(object,),{"my_attribute": 0}) >>> type(MyClass) <type 'type'> >>> o = MyClass() >>> o.my_attribute 0
特别注意第二个参数是一个tuple(语法看起来很奇怪,以逗号结尾)。如果你需要在类中安排一个方法,那么创建一个函数并且将其以属性的方式传递作为第三个参数,像这样:
>>> def myclass_init(self,my_attr): ... self.my_attribute = my_attr ... >>> MyClass = type("MyClass",{"my_attribute": 0,"__init__": myclass_init}) >>> o = MyClass("Test") >>> o.my_attribute 'Test' >>> o.__init__ <bound method MyClass.myclass_init of <__main__.MyClass object at 0x10ab72150>>
我们可以通过一个可调用对象(函数或是类)来自定义元类,这个对象需要三个输入参数并返回一个对象。这样一个元类在一个类上实现只要定义了它的__metaclass__属性。第一个例子,让我们做一些有趣的事情看看我们能够用元类做些什么:
>>> def mymetaclass(name,parents,attributes): ... return "Hello" ... >>> class C(object): ... __metaclass__ = mymetaclass ... >>> print C Hello >>> type(C) <type 'str'>
请注意以上的代码,C只是简单地将一个变量引用指向了字符串“Hello”。当然了,没人会在实际中写这样的代码,这只是为了演示元类的用法而举的一个简单例子。接下来我们来做一些更有用的操作。在本系列的第二部分我们曾看到如何使用装饰器类来记录目标类每个方法的输出,现在我们来做同样的事情,不过这一次我们使用元类。我们借用之前的装饰器定义:
def log_everything_metaclass(class_name,attributes): print "Creating class",class_name myattributes = {} for name,attr in attributes.items(): myattributes[name] = attr if hasattr(attr,'__call__'): myattributes[name] = logged("%b %d %Y - %H:%M:%S",class_name + ".")(attr) return type(class_name,myattributes) class C(object): __metaclass__ = log_everything_metaclass def __init__(self,x): self.x = x def print_x(self): print self.x # Usage: print "Starting object creation" c = C("Test") c.print_x() # Output: Creating class C Starting object creation - Running 'C.__init__' on Aug 05 2013 - 13:50:58 - Finished 'C.__init__',execution time = 0.000s - Running 'C.print_x' on Aug 05 2013 - 13:50:58 Test - Finished 'C.print_x',execution time = 0.000s
如你所见,类装饰器与元类有着很多共同点。事实上,任何能够用类装饰器完成的功能都能够用元类来实现。类装饰器有着很简单的语法结构易于阅读,所以提倡使用。但就元类而言,它能够做的更多,因为它在类被创建之前就运行了,而类装饰器则是在类创建之后才运行的。记住这点,让我们来同时运行一下两者,请注意运行的先后顺序:
def my_metaclass(class_name,attributes): print "In metaclass,creating the class." return type(class_name,attributes) def my_class_decorator(class_): print "In decorator,chance to modify the class." return class_ @my_class_decorator class C(object): __metaclass__ = my_metaclass def __init__(self): print "Creating object." c = C() # Output: In metaclass,creating the class. In decorator,chance to modify the class. Creating object.
元类的一个实际用例
让我们来考虑一个更有用的实例。假设我们正在构思一个类集合来处理MP3音乐文件中使用到的ID3v2标签Wikipedia。简而言之,标签由帧(frames)组成,而每帧通过一个四字符的识别码(identifier)进行标记。举个例子,TOPE标识了原作者帧,TOAL标识了原专辑名称等。如果我们希望为每个帧类型写一个单独的类,并且允许ID3v2标签库用户自定义他们自己的帧类。那么我们可以使用元类来实现一个类工厂模式,具体实现方式可以这样:
frametype_class_dict = {} class ID3v2FrameClassFactory(object): def __new__(cls,class_name,attributes): print "Creating class",class_name # Here we could add some helper methods or attributes to c c = type(class_name,attributes) if attributes['frame_identifier']: frametype_class_dict[attributes['frame_identifier']] = c return c @staticmethod def get_class_from_frame_identifier(frame_identifier): return frametype_class_dict.get(frame_identifier) class ID3v2Frame(object): frame_identifier = None __metaclass__ = ID3v2FrameClassFactory pass class ID3v2TitleFrame(ID3v2Frame): __metaclass__ = ID3v2FrameClassFactory frame_identifier = "TIT2" class ID3v2CommentFrame(ID3v2Frame): __metaclass__ = ID3v2FrameClassFactory frame_identifier = "COMM" title_class = ID3v2FrameClassFactory.get_class_from_frame_identifier('TIT2') comment_class = ID3v2FrameClassFactory.get_class_from_frame_identifier('COMM') print title_class print comment_class # Output: Creating class ID3v2Frame Creating class ID3v2TitleFrame Creating class ID3v2CommentFrame <class '__main__.ID3v2TitleFrame'> <class '__main__.ID3v2CommentFrame'>
当然了,以上的代码同样可以用类装饰器来完成,以下是对应代码:
frametype_class_dict = {} class ID3v2FrameClass(object): def __init__(self,frame_id): self.frame_id = frame_id def __call__(self,cls): print "Decorating class",cls.__name__ # Here we could add some helper methods or attributes to c if self.frame_id: frametype_class_dict[self.frame_id] = cls return cls @staticmethod def get_class_from_frame_identifier(frame_identifier): return frametype_class_dict.get(frame_identifier) @ID3v2FrameClass(None) class ID3v2Frame(object): pass @ID3v2FrameClass("TIT2") class ID3v2TitleFrame(ID3v2Frame): pass @ID3v2FrameClass("COMM") class ID3v2CommentFrame(ID3v2Frame): pass title_class = ID3v2FrameClass.get_class_from_frame_identifier('TIT2') comment_class = ID3v2FrameClass.get_class_from_frame_identifier('COMM') print title_class print comment_class Decorating class ID3v2Frame Decorating class ID3v2TitleFrame Decorating class ID3v2CommentFrame <class '__main__.ID3v2TitleFrame'> <class '__main__.ID3v2CommentFrame'>