美文网首页
元类和自省

元类和自省

作者: yangyuecho | 来源:发表于2019-12-02 14:03 被阅读0次

    自省和反射

    自省 (introspection): 可以在运行时获得对象的类型信息

    type introspection is the ability of a program to examine the type or properties of an object at runtime

    例子; python 中最常见的 dir

        def __init__(self, val) -> None:
            self.x = val
    
        def bar(self):
            return self.x
    
    ...
    
    >>> dir(Foo(5))
    # ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
    # '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__',
    # '__repr__', '__setattr__', '__str__', '__weakref__', 'bar', 'x']
    

    反射 (reflection) 是包括自省的,指检查,自省以及修改自身结构和行为的能力。

    Introspection should not be confused with reflection, which goes a step further and is the ability for a program to manipulate the values, meta-data, properties and/or functions of an object at runtime.
    reflection is the ability of a process to examine, introspect, and modify its own structure and behavior.

    例子:

    # Without reflection
    obj = Foo()
    obj.hello()
    
    # With reflection
    obj = globals()['Foo']()
    getattr(obj, 'hello')()
    
    # With eval
    eval('Foo().hello()')
    

    元类是什么

    python 中,所有的数据类型都可以视为对象,当然也可以自定义对象。

    类也是一种对象,类同时拥有创建对象(类实例)的能力。

    class myObject(object):
        pass
    
    item = myObject()
    
    print(myObject) # <class '__main__.myObject'>
    print(item) # <__main__.myObject object at 0x10a936080>
    

    当使用 class 关键字时,Python 解释器自动创建这个对象。但就和Python 中的大多数事情一样,Python 仍然提供给了手动处理的方法:

    type 除了可以告诉你一个对象的类型是什么,还能动态的创建类。

    print(type(1))  # <class 'int'>
    print(type('1'))    # <class 'str'>
    print(type(item))   # <class '__main__.myObject'>
    print(type(myObject))   # <class 'type'>
    
    # type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
    myMetaClass = type('myMetaClass', (), {})
    print(myMetaClass)  # <class '__main__.myMetaClass'>
    print(myMetaClass()) # <__main__.myMetaClass object at 0x10a9367f0>
    

    元类就是用来创建类的“东西”,在 Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字 class 时 Python 在幕后做的事情,而这就是通过元类来实现的。

    元类的使用

    声明元类

    class Spam(metaclass=Meta):  # 3.0 and later
    class Spam(Eggs, metaclass=Meta):  # Other supers okay
    
    class spam(object): # 2.6 version(only)
        __metaclass__ = Meta  
    

    自定义元类

    使用简单的工厂函数

    任何可调用对象都可以用作一个元类,只要它接受传递的参数并且返回与目标类兼容的一个对象

    # 元类会自动将你通常传给‘type’的参数作为自己的参数传入
    def upper_attr(future_class_name, future_class_parents, future_class_attr):
        '''返回一个类对象,将属性都转为大写形式'''
        #  选择所有不以'__'开头的属性
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
       # 将它们转为大写形式
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
     
        # 通过'type'来做类对象的创建
        return type(future_class_name, future_class_parents, uppercase_attr)
    
    class Foo(metaclass=upper_attr):
        bar = 'bip'
        
    f = Foo()
    print(f.BAR)    # bip
    

    从技术上讲,实例属性查找通常只是搜索实例及其所有类的dict字典;元类不包含在实例查找中。

    用常规类重载类创建调用

    class UpperAttrMetaclass(type):
        def __new__(cls, name, bases, dct):
            attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
            uppercase_attr = dict((name.upper(), value) for name, value in attrs)
    #         这种方式其实不是OOP。我们直接调用了type,而且我们没有改写父类的__new__方法。
    #         return type(future_class_name, future_class_parents, uppercase_attr)
    #         复用type.__new__方法
    #         return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
    #         如果使用super方法的话,我们还可以使它变得更清晰一些
            return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
    
    class Foo(metaclass=UpperAttrMetaclass):
        bar = 'bip'
        
    f = Foo()
    print(f.BAR)    # bip
    

    元类其实只做了三件事:

    • 拦截类的创建
    • 修改类
    • 返回修改之后的类

    为什么用类而不是函数

    由于 metaclass 可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?这里有好几个原因:

    • 意图会更加清晰。当你读到UpperAttrMetaclass(type)时,你知道接下来要发生什么。

    • 使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。

    • 可以把代码组织的更好。使用元类的时候通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。

    • 可以使用 new, init 以及 call 这样的特殊方法。它们能帮你处理不同的任务。

    为什么要使用元类

    元类的主要用途是创建API。一个典型的例子是Django ORM。

    对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

    • Monkey patching
    • class decorators

    以下是一个计数功能分别用装饰器和元类实现的例子:

    from types import FunctionType
    
    
    def tracer(func):
        calls = 0
        def on_call(*args, **kwargs):
            nonlocal calls
            calls += 1
            print('call %s to %s' % (calls, func.__name__))
            return func(*args, **kwargs)
        return on_call
    
    
    class Person1:
        @tracer
        def __init__(self, name, pay):
            self.name = name
            self.pay = pay
            
        @tracer
        def giveRaise(self, percent):
            self.pay *= (1.0 + percent)
            
        @tracer
        def lastName(self):
            return self.name.split()[-1]
        
        
    bob = Person1('Bob Smith', 50000)
    sue = Person1('Sue Jones', 100000)
    print(bob.name, sue.name)
    sue.giveRaise(.10)
    print(sue.pay)
    print(bob.lastName(), sue.lastName())
    
    # call 1 to __init__
    # call 2 to __init__
    # Bob Smith Sue Jones
    # call 1 to giveRaise
    # 110000.00000000001
    # call 1 to lastName
    # call 2 to lastName
    # Smith Jones    
    
    class MetaTrace(type):
        def __new__(meta, classname, supers, classdict):
            for attr, attrval in classdict.items():
                if type(attrval) is FunctionType:
                    classdict[attr] = tracer(attrval)
            return type.__new__(meta, classname, supers, classdict)
    
    class Person2(metaclass=MetaTrace):
        def __init__(self, name, pay):
            self.name = name
            self.pay = pay
    
        def giveRaise(self, percent):
            self.pay *= (1.0 + percent)
            
        def lastName(self):
            return self.name.split()[-1]
        
    bob = Person2('Bob Smith', 50000)
    sue = Person2('Sue Jones', 100000)
    print(bob.name, sue.name)
    sue.giveRaise(.10)
    print(sue.pay)
    print(bob.lastName(), sue.lastName())
    
    # call 1 to __init__
    # call 2 to __init__
    # Bob Smith Sue Jones
    # call 1 to giveRaise
    # 110000.00000000001
    # call 1 to lastName
    # call 2 to lastName
    # Smith Jones
    
    

    单例的例子:

    # 单例模式
    class Singleton(type):
        def __init__(self, *args, **kwargs):
            self.__instance = None
            super().__init__(*args, **kwargs)
    
        def __call__(self, *args, **kwargs):
            if self.__instance is None:
                self.__instance = super().__call__(*args, **kwargs)
                return self.__instance
            else:
                return self.__instance
    
    # Example
    class Spam(metaclass=Singleton):
        def __init__(self):
            print('Creating Spam')
              
    # 非元类单例
    class _Spam:
        def __init__(self):
            print('Creating Spam')
    
    _spam_instance = None
    
    def Spam():
        global _spam_instance
    
        if _spam_instance is not None:
            return _spam_instance
        else:
            _spam_instance = _Spam()
            return _spam_instance    
    

    缓存例子:

    import weakref
    
    class Cached(type):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.__cache = weakref.WeakValueDictionary()
    
        def __call__(self, *args):
            if args in self.__cache:
                return self.__cache[args]
            else:
                obj = super().__call__(*args)
                self.__cache[args] = obj
                return obj
    
    # Example
    class Spam(metaclass=Cached):
        def __init__(self, name):
            print('Creating Spam({!r})'.format(name))
            self.name = name
    

    相关文章

      网友评论

          本文标题:元类和自省

          本文链接:https://www.haomeiwen.com/subject/rdzxgctx.html