zoukankan      html  css  js  c++  java
  • 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地址
    个人公众号:
    在这里插入图片描述

  • 相关阅读:
    [HNOI2004]L语言
    快速沃尔什变换FWT
    [BZOJ1486][HNOI2009]最小圈
    [BZOJ4819][SDOI2017]新生舞会
    [POJ2976]Dropping tests
    CTSC2018&APIO2018游记
    [Luogu3769][CH弱省胡策R2]TATT
    [BZOJ3489]A simple rmq problem
    [BZOJ4066]简单题
    [BZOJ2648]SJY摆棋子
  • 原文地址:https://www.cnblogs.com/simon-idea/p/11532259.html
Copyright © 2011-2022 走看看