什么是元编程
软件开发中很重要的一条原则就是“不要重复自己的工作(Don’t repeat youself)”,也就是说当我们需要复制粘贴代码时候,通常都需要寻找一个更加优雅的解决方案,在python中,这类问题常常会归类为“元编程”
元编程目的
是创建函数和类,并用他们操作代码(例如修改,生成,或者包装自己已有的代码)。尽可能的使代码优雅简洁。具体而言,通过编程的方法,在更高的抽象层次上对一种层次的抽象的特性进行修改
元编程应用
给函数添加一个包装(装饰器)
注意:对wraps装饰器的使用进行补充说明,在类装饰器中使用闭包会导致生成的对象不再是被装饰的类的实例,而是在装饰器函数创建的子类的实例,这会影响__name__和__doc__等属性,在上篇我们使用@wraps装饰器对函数装饰器进行操作让问题得到解决,但在类装饰器中这一方法无效。
元类
在理解元类之前,您需要掌握Python中的类。Python对于从Smalltalk语言借用的类是非常奇怪的。在大多数语言中,类只是描述如何生成对象的代码片段。在Python中也是如此:
1 2 3 4 5 6 7 | >>> class ObjectCreator( object ): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c> |
一旦使用关键字class
,Python就会执行它并创建一个OBJECT。指示
1 2 3 | >>> class ObjectCreator( object ): ... pass ... |
在内存中创建一个名为“ObjectCreator”的对象。这个对象(类)本身能够创建对象(实例),这就是为什么它是一个类。但是,它仍然是一个对象,因此:
-
- 您可以将其分配给变量
- 你可以复制它
- 你可以添加属性
- 您可以将其作为函数参数传递
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | >>> print(ObjectCreator) # you can print a class because it's an object < class '__main__.ObjectCreator' > >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # you can pass a class as a parameter < class '__main__.ObjectCreator' > >>> print(hasattr(ObjectCreator, 'new_attribute' )) False >>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class >>> print(hasattr(ObjectCreator, 'new_attribute' )) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c> |
动态创建类
由于类是对象,因此您可以像任何对象一样动态创建它们。首先,您可以使用class
以下命令在函数中创建类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> def choose_class(name): ... if name == 'foo' : ... class Foo( object ): ... pass ... return Foo # return the class , not an instance ... else : ... class Bar( object ): ... pass ... return Bar ... >>> MyClass = choose_class( 'foo' ) >>> print(MyClass) # the function returns a class , not an instance < class '__main__.Foo' > >>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c> |
但它还不是我们想要的,创建类应该有更优的方法,由于类是对象,因此它们必须由某些东西生成。
使用class
关键字时,Python会自动创建此对象。但与Python中的大多数内容一样,它为您提供了手动执行此操作的方法。我们可用通过type函数查看对象的类型:
1 2 3 4 5 6 7 8 | >>> print(type(1)) <type 'int' > >>> print(type( "1" )) <type 'str' > >>> print(type(ObjectCreator)) <type 'type' > >>> print(type(ObjectCreator())) < class '__main__.ObjectCreator' > |
type除了可以查看数据类型外,还
有一个特殊的能力,它也可以动态创建类。type
可以将类的描述作为参数,并返回一个类。查看type内部原理
:
1 2 3 | type(name of the class , tuple of the parent class ( for inheritance, can be empty), dictionary containing attributes names and values) |
例如:
1 2 | >>> class MyShinyClass( object ): ... pass |
可以通过以下方式手动创建:
1 2 3 4 5 | >>> MyShinyClass = type( 'MyShinyClass' , (), {}) # returns a class object >>> print(MyShinyClass) < class '__main__.MyShinyClass' > >>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec> |
type
接受字典来定义类的属性。所以:
1 2 | >>> class Foo( object ): ... bar = True |
可以翻译成:
1 | >>> Foo = type( 'Foo' , (), { 'bar' :True}) |
并用作普通类:
1 2 3 4 5 6 7 8 9 | >>> print(Foo) < class '__main__.Foo' > >>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) True |
当然,你可以继承它,所以:
1 2 | >>> class FooChild(Foo): ... pass |
会解释成:
1 2 3 4 5 | >>> FooChild = type( 'FooChild' , (Foo,), {}) >>> print(FooChild) < class '__main__.FooChild' > >>> print(FooChild.bar) # bar is inherited from Foo True |
最后,如果想要为我们创建的类添加方法,只需使用正确的签名定义函数并将其指定为属性即可。
1 2 3 4 5 6 7 8 9 10 11 | >>> def echo_bar(self): ... print(self.bar) ... >>> FooChild = type( 'FooChild' , (Foo,), { 'echo_bar' : echo_bar}) >>> hasattr(Foo, 'echo_bar' ) False >>> hasattr(FooChild, 'echo_bar' ) True >>> my_foo = FooChild() >>> my_foo.echo_bar() True |
在动态创建类之后,您可以添加更多方法,就像向正常创建的类对象添加方法一样。
1 2 3 4 5 6 | >>> def echo_bar_more(self): ... print( 'yet another method' ) ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, 'echo_bar_more' ) True |
在Python中,类是对象,您可以动态地动态创建类。这是Python在您使用关键字时所执行的操作class
,它通过使用元类来实现。
什么是元类(终于讲到重点了)
元类是创建类的“类”。我们可以定义类来创建实例,python一切皆对象,类也不列外,它是通过元类来创建。类是创建实例的蓝图,元类是创建类的蓝图。可以很容易地看出,Python类中也需要是第一类对象才能启用此行为。
例如:
1 2 | MyClass = MetaClass() my_object = MyClass() |
通过type来创建:
1 | MyClass = type( 'MyClass' , (), {}) |
这是因为该函数type
实际上是一个元类。type
是Python用于在幕后创建所有类的元类。
为什么是小写type而不是大学Type?
type与str
创建字符串对象int
的类创建整数对象的类类似,它也只是创建类对象的类。我们通过检查__class__
属性来查看。
一切,一切,一切重要的事情说三遍,都是Python中的一个对象。这包括整数,字符串,函数和类。所有这些都是对象。所有这些都是从一个类创建的:
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> age = 35 >>> age.__class__ <type 'int' > >>> name = 'bob' >>> name.__class__ <type 'str' > >>> def foo(): pass >>> foo.__class__ <type 'function' > >>> class Bar( object ): pass >>> b = Bar() >>> b.__class__ < class '__main__.Bar' > |
那么__class__
的__class__
?
1 2 3 4 5 6 7 8 | >>> age.__class__.__class__ <type 'type' > >>> name.__class__.__class__ <type 'type' > >>> foo.__class__.__class__ <type 'type' > >>> b.__class__.__class__ <type 'type' > |
因此,元类只是创建类对象的东西。我们也称它为类工厂
type
是Python使用的内置元类,我们也可以创建自己的元类。
__mataClass__属性
在Python 2中,我们在编写类时添加属性:
1 2 3 | class Foo( object ): __metaclass__ = something... [...] |
如果引用__mataClass__属性,python将使用元类类创建Foo,但是这样class Foo(object),类对象Foo并不是在内存中创建的
Python将会在父类中查找__metaclass__,如果没有,就继续向父类的父类查找,如果还是没有,就在模块中找,还是没有的话就用缺省的MetaClass即type
创建类。
当你这样做时:
Python会执行以下操作:
如果有__metaclass__
属性,将会在内存中创建一个类对象,名称Foo
使用是__metaclass__
。如果Python找不到__metaclass__
,它将__metaclass__
在MODULE级别查找,并尝试执行相同的操作(但仅适用于不继承任何内容的类,基本上是旧式类)。
如果还是找不到__metaclass__
,它将使用Bar
's(第一个父级)自己的元类(可能是默认的type
)来创建类对象。
Python中的元类3
在Python 3中更改了设置元类的语法:
1 2 | class Foo( object , metaclass=something): ... |
即__metaclass__
不再使用该属性,而是支持基类列表中的关键字参数。但是并不会影响元类的功能。
python3中我们可以将属性作为关键字参数传递给元类,如下所示:
1 2 | class Foo( object , metaclass=something, kwarg1=value1, kwarg2=value2): ... |
自定义元类
一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类
自定义元类的主要目的是:
-
- 拦截类的创建
- 读取类的信息,可以做修改
- 返回新的类
通过传入不同的字符串动态的创建不同的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def create_class(name): if name == 'user' : class User: def __str__(self): return "user" return User elif name == "company" : class Company: def __str__(self): return "company" return Company if __name__ == '__main__' : Myclass = create_class( "user" ) my_obj = Myclass() print(my_obj) #user print(type(my_obj)) #< class '__main__.create_class.<locals>.User' > |
用type创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # 一个简单type创建类的例子 #type(object_or_name, bases, dict) #type里面有三个参数,第一个类名,第二个基类名,第三个是属性 User = type( "User" ,(),{ "name" : "derek" }) my_obj = User() print(my_obj.name) #derek#带方法的创建<br><br>def say(self): #必须加self return "i am derek" User = type( "User" ,(),{ "name" : "derek" , "say" :say}) my_obj = User() print(my_obj.name) #derek print(my_obj.say()) #i am derek |
让type创建的类继承一个基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def say(self): #必须加self return "i am derek" class BaseClass: def answer(self): return "i am baseclass" #type里面有三个参数,第一个类名,第二个基类名,第三个是属性 User = type( "User" ,(BaseClass,),{ "name" : "derek" , "say" :say}) if __name__ == '__main__' : my_obj = User() print(my_obj.name) #d erek print(my_obj.say()) # i am derek print(my_obj.answer()) # i am baseclass |
但是在实际编码中,我们一般不直接用type去创建类,而是用元类的写法,自定义一个元类metaclass去创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # 把User类创建的过程委托给元类去做,这样代码的分离性比较好 class MetaClass(type): def __new__(cls, *args, **kwargs): return super().__new__(cls,*args, **kwargs) class User(metaclass=MetaClass): def __init__(self,name): self.name = name def __str__(self): return "test" if __name__ == '__main__' : #python中类的实例化过程,会首先寻找metaclass,通过metaclass去创建User类 my_obj = User(name= "derek" ) print(my_obj) #test |
还有一个典型的自定义元类例子就是Django ORM。
元类的主要用例是创建API。
它允许您定义如下内容:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是如果你这样做:
guy = Person(name='bob', age='35')
print(guy.age)
它不会返回一个IntegerField
对象。它将返回一个int
,甚至可以直接从数据库中获取它。
因为models.Model
定义__metaclass__
它会使用一些魔法将Person
您刚刚使用简单语句定义的内容转换为数据库字段的复杂sql。
Django通过公开一个简单的API并使用元类,从这个API中重新创建代码来完成幕后的实际工作,从而使复杂的外观变得简单。