Python高级语法-贯彻回顾-元类(4.99.1)

@

1.为什么要掌握元类

在django中编写models的时候遇到了元类的相关操作
并且在mini-web框架编写的时候也遇到了相关的问题
意识到深入理解元类非常的重要所以补票重学

学习且引用来自博客:https://www.cnblogs.com/intimacy/p/8119449.html

2.正文

python中一切都是对象,类也是对象
那么意味着,类可以执行以下操作

  • 1)将他赋值给一个变量
  • 2)拷贝他
  • 3)给他设置属性
  • 4)将他当做方法的参数传递  

>>> print(ObjectCreator) # 打印此对象
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # 将此对象当做方法的参数
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # 给此对象添加属性
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # 将此对象赋值给一个变量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

因为类是对象,那么意味着可以动态的生成
粗略的以为,在java等语言中,类使用来定义一个对象的,也就是用来定义起亚数据类型的,私以为不可以更改。但是python好像就可以更改类的源代码,导致你想在运行的时候重新规定一下类中的属性和方法也是可以的,比如下面的代码传入foo参数的时候在内存中定义一个class对象,但是传入其他的时候就不定义

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # 返回Foo类,而不是对象
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # 方法返回类,不返回对象
<class '__main__.Foo'>
>>> print(MyClass()) # 可以通过方法返回的类创建对象
<__main__.Foo object at 0x89c6d4c>

但这似乎不是动态创建的,因为我们写了一段代码来生成他,然后python解释器遇到class关键字的时候就自动给我们程序创建了一个类对象,那么如何自己创建一个,使用type

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

type在我们学习python的历程中可以知道是用来判断数据类型的,但是type确实可以用来创建一个类对象,格式如下

type(类对象的名称, 元组形式的所有父类对象 (继承用,可以为空),包含属性名和属性值的字典)

>>> class MyShinyClass(object):
...       pass
-------------------------------------------------------------------
>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一个类对象
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 通过返回的类对象创建一个对象
<__main__.MyShinyClass object at 0x8997cec>

上面两部分代码实现的功能是一样的
再如

>>> class Foo(object):
...       bar = True
--------------------------------------------------------------
>>> Foo = type('Foo', (), {'bar':True})


#可以向正常的类一样使用
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

这两部分代码的功能也是一样的,所以让我们再次熟悉一下type创建类对象的使用方法,第一个参数为类的名字,第二个参数为多继承使用的元组,第三个参数为一个列表,用来定义类中的方法和属性

>>> 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

定义方法的时候如上,当然可以再定义了这个类的时候往里面加其他的方法

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

经过上述可以知道在python中,类就是对象,你能在运行中动态的创建类

那么metaclass是啥呢
metaclass是创建class的东西,定义类的目的,就是为了创建一个对象,但是我们已经知道了,python中类就是一个对象,那么metaclass就是创建这种对象(类)的东西,他们是用来描述类的,可以理解为

MyClass = MetaClass()
MyObject = MyClass()
#使用metaclass创建了一个类对象,在使用这个类对象去创建其他的对象

我们已经知道,type可以为我们创建一个对象
是因为方法type实际上是一个metaclass,type就是python用来创建其他类的metaclass, 你可以通过__class__属性来验证这一点 也就是说type就是用来创建其他class的class,在Python中一切皆对象,一切皆对象,包括int str function(方法) class(类),所有一切都是对象,而且他们都是有某一个类创建

>>> 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是啥呢

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
所以一个metaclass就是用来创建类对象的东西,你可以称之为“类工厂”。而type就是Python內建的metaclass(类工厂),当然你能定义你自己的metaclass

在python中创建一个对象之前,回去找metaclass,如果没有指定的话就使用type来创建类对象

Python会:
    1、在Foo中有没有指定__metaclass__属性
    2、如果找到,就会通过__metaclass__指定的东西创建Foo类对象(注意此处是类对象)
    3、如果找不到,就会去MODULE(模块)级别去找,并创建Foo类对象(但只适用于没有继承任何父类的类,基本上就是老式类)
    4、如果都找不到,就会通过Bar(第一个父类)的metaclass(很有可能是type)来创建Foo类对象,
    5、这里要注意__metaclass__不会被继承,而父类的__class__(Bar.__class__)会被子类继承.即:如果Bar用了__metaclass__属性通过type()而不是type.__new__()创建Bar类对象,那么子类不会继承这种行为。
现在问题是:可以将什么指定给__metaclass__。
答案是:能创建类的东西。
什么能创建类呢?type type的子类 和所有使用type的

自定义metaclass

metaclass的主要作用就是创建类时使类发生变化
通常做API时才会用到,就是创建和上下文相吻合的类(这句翻的太烂)。
举个例子:你决定所有模块下的类的属性都要大写,有很多种方法实现此需求,其中一种就是在module级别设置__metaclass__属性。
这样一来,此模块下所有类都通过指定的metaclass创建,你只需要告诉metaclass将所有属性大写即可。
幸运的是__metaclass__只要可被调用即可,它不必是一个类。
所以我们先举个例子,用一个方法做metaclass
# the metaclass will automatically get passed the same argument that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
   """
       Return a class object, with the list of its attribute turned
       into uppercase.
   """

   # pick up any attribute that doesn't start with '__' and uppercase it
   uppercase_attr = {}
   for name, val in future_class_attr.items():
           if not name.startswith('__'):
                   uppercase_attr[name.upper()] = val
           else:
                   uppercase_attr[name] = val

   # let `type` do the class creation
   return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
   # but we can define __metaclass__ here instead to affect only this class
   # and this will work with "object" children
   bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

现在使用一个类做metaclass来实现

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
   # __new__ is the method called before __init__
   # it's the method that creates the object and returns it
   # while __init__ just initializes the object passed as parameter
   # you rarely use __new__, except when you want to control how the object
   # is created.
   # here the created object is the class, and we want to customize it
   # so we override __new__
   # you can do some stuff in __init__ too if you wish
   # some advanced use involves overriding __call__ as well, but we won't
   # see this
   def __new__(upperattr_metaclass, future_class_name,
               future_class_parents, future_class_attr):

       uppercase_attr = {}
       for name, val in future_class_attr.items():
           if not name.startswith('__'):
               uppercase_attr[name.upper()] = val
           else:
               uppercase_attr[name] = val

       return type(future_class_name, future_class_parents, uppercase_attr)

上面代码说的new方法是在init方法之前调用的,new方法是创建一个对象并返回他,init方法只是通过传入参数初始化一个对象,我们几乎用不到new方法,如果你不是想去控制类对象如何生成

上面的方式不是很"面向对象",因为我们直接调用了type方法,而且没有重写也没有调用父类的__new__方法,下面这种方式就好一些

**class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)**
你可能已经注意到了参数"upperattr_metaclass",这个参数并没有什么特殊的,__new__方法总是以所在类中的类对象作为第一个参数,就像普通方法中的self参数一样,将实例作为第一个参数。
当然我用的名字都见名知意,但就像self,所有的参数也有简短的名字,所以一个真正的metaclass会像下面这样:
class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)
我们可以通过使用super关键字使类更清晰:
class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
实际上,metaclass在做黑魔法时尤其 有用,也就是做复杂的事情。至于metaclass本身,还是很简单的:
    1、拦截类的生成
    2、修改类
    3、返回修改后的类

为什么用metaclass而不用方法呢?
既然__metaclass__能接收一切可被调用的东西,为什么要用class呢,class可是比方法复杂的多啊!!!
主要是以下几个原因:
1、目的更明确。你看到UpperAttrMetaclass(type),就知道是什么意思
2、能用"面向对象编程",metaclass可以继承metaclass可以重写父类方法,甚至可以使用其他metaclass。
3、某个类如果你指定了metaclass,则它的子类是这个metaclass的实例,但如果指定了metaclass为function则不是(一个类不能是某个方法的实例)。
4、代码结构会更好。上面使用metaclass的例子已经非常简单了,通过使用metaclass的情况都是比较复杂的。将所有方法组织在一个class中,能使你的代码更易读。
5、可以在__new__,__init__和__call__中做你想做的事,虽然你能都在__new__写,但有些人还是习惯在__init_中实现。
6、叫metaclass,该死的,这名字还是有些意义的。

  • metaclass的主要用于创建API,典型的例子就是Django ORM。它允许你这样定义:
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__,而指向的metaclass将你定义的简单的Person类变为数据库中的一个字段。
    利用metaclass,Django暴露一套API使得复杂的事情看起来很简单,而在幕后做真正的工作。

最后:
首先,类就是能创建实例的对象。实际上,类是metaclass的实例

>>> class Foo(object): pass
>>> id(Foo)
142630324
在Python中一切都是对象(实例),他们不是类的实例就是metaclass的实例。
除了type。type的metaclass是自己,实际上在纯Python中是不能创建type的,而是通过在Python的实现层作弊实现的。
其次,metaclass是很复杂的。如果你只要对类做一点点调整,就不要使用metaclass,你可以用另外两种技术来实现:
    猴子补丁(monkey patching)
    装饰器
当你需要对类做调整,99%的情况下你可以通过以上两种方式实现。
但是98%的情况是:你完全不需要对类做调整

关于作者

个人博客网站
个人GitHub地址
个人公众号:
在这里插入图片描述

posted @ 2019-09-17 10:51  BothSavage  阅读(170)  评论(0编辑  收藏  举报