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地址
    个人公众号:
    在这里插入图片描述

  • 相关阅读:
    字符串 CSV解析 表格 逗号分隔值 通讯录 电话簿 MD
    Context Application 使用总结 MD
    RxJava RxPermissions 动态权限 简介 原理 案例 MD
    Luban 鲁班 图片压缩 MD
    FileProvider N 7.0 升级 安装APK 选择文件 拍照 临时权限 MD
    组件化 得到 DDComponent JIMU 模块 插件 MD
    gradlew 命令行 build 调试 构建错误 Manifest merger failed MD
    protobuf Protocol Buffers 简介 案例 MD
    ORM数据库框架 SQLite 常用数据库框架比较 MD
    [工具配置]requirejs 多页面,多入口js文件打包总结
  • 原文地址:https://www.cnblogs.com/simon-idea/p/11532259.html
Copyright © 2011-2022 走看看