美文网首页
元类和自省

元类和自省

作者: 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

相关文章

  • 元类和自省

    自省和反射 自省 (introspection): 可以在运行时获得对象的类型信息 type introspect...

  • python类和元类

    前段时间在B站上看到python类和元类的视频讲解,整理了视频中的代码,相信看完后对类有进一步的了解。 1.py ...

  • [objc 解释]:类和元类

    翻译参考链接:[objc 解释]:类和元类 英文原文:[objc explain]: Classes and me...

  • 使用枚举类和元类

    获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量 创建类的三种方式: 正常情况下,我们都...

  • 类和元类 class and metaclass

    本文为大地瓜原创,欢迎知识共享,转载请注明出处。虽然你不注明出处我也没什么精力和你计较。作者微信号:christg...

  • [objc 解释]:类和元类

    笔者翻译自[objc explain]: Classes and metaclasses Object-C 是基于...

  • 友元类和嵌套类

    友元类 友元函数用于类的扩展接口中,类并非只能拥有友元函数,也可以将类作为友元,该类称为友元类。 友元类的所有方法...

  • iOS 对类,元类,根元类关系图的验证

    一.类,元类,根元类关系图 网上流传一张类,元类,根元类的关系图。详细描述了它们相互间的继承关系和isa指向关系。...

  • Qt:The Meta-Object System

    翻译自:The Meta-Object System 本文是 Qt 的元对象系统和自省功能(introspecti...

  • Category实现原理

    依赖runtime 动态的将分类的方法和类方法合并到类对象和元类对象的方法列表中 (对实例对象 类对象 元类对...

网友评论

      本文标题:元类和自省

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