【问题标题】:How to use a mixin metaclass to modify instance object on initialization如何使用 mixin 元类在初始化时修改实例对象
【发布时间】:2018-08-11 20:52:10
【问题描述】:

我正在研究Advent of Code 2017 problems 以获得乐趣,其中一些要求您解释和执行伪汇编程序。我想编写一个重用模块,为此提供必要的基础设施。具体来说,我想提供沙盒化的 execeval 函数,以及解析解释命名空间中的变量的函数。我阅读了元类并开始构建一个,但我陷入了实现细节。我设想能够做到这一点:

class InterpreterMixinMeta(type):
    """see below for my attempt"""
    pass

class InterpreterMixin(metaclass=InterpreterMixinMeta):
    pass

class Program(InterpreterMixin):
    """does the actual work of running a pseudo-assembly program"""
    pass

这是我在InterpreterMixinMeta 的初步尝试。如何在将使用 mixin 的对象上创建属性和方法?属性,例如 execsevals 的本地字典的唯一副本,val() 等方法。

class InterpreterMixinMeta(type):
    """
    A constructor for InterpreterMixin. Creates a new mixin class that
    exposes functions needed for interpreting pseudo-assembly programs:
    `execs`, `evals`, and `val`
    """

    @staticmethod
    def init_execs(exc_locals: dict):
        """Creates a persistent sandbox environment for calls to `exec`"""
        sbox_globals = {'__builtins__': None}

        def sbox_exec(source: str):
            return exec(source, sbox_globals, exc_locals)

        return sbox_exec

    @staticmethod
    def init_evals(exc_locals: dict):
        """Creates a persistent sandbox environment for calls to `eval`"""
        sbox_globals = {'__builtins__': None}

        def sbox_eval(expr: str):
            return eval(expr, sbox_globals, exc_locals)

        return sbox_eval

    def val(self, x: str) -> str:
        """Returns the integer value of the input, formatted as a string"""
        if x.isdigit():
            return x
        else:
            return str(self.evals(x))

    def __new__(mcs, name, bases, nspace):
        # how do I let each object define a myLocals dict?
        nspace['execs'] = InterpreterMixinMeta.init_execs(myLocals)
        return super().__new__(mcs, name, bases, nspace)

【问题讨论】:

  • 完全不清楚您是否需要一个元类:您是否尝试过仅使用继承?
  • 不过,输入是可信的。该练习提供了伪代码,我的 program 对象将其转换为由 mixin 中的 evalsexecs 函数运行的硬编码 Python 语句。用户输入永远不会直接执行。至于元类的使用,我试图以这种方式解决问题,以便更多地了解元类的使用。毕竟,这只是一种爱好。为什么不通过实施来挑战自己?
  • 也许只是不要称它为“安全”,那么。使用元类可能不仅更难而且更丑:我怀疑你最终需要定义更多神奇的名字,比如__slots__
  • 我将“安全”更改为“沙盒”以移除红鲱鱼。
  • 我知道你在那里做了什么。

标签: python python-3.x metaclass


【解决方案1】:

我没有使用元类就解决了这个问题。我首先不得不退后一步,重新考虑我的对象模型。最终,我要定义的角色和关系是:

  • Executor:一个类实例,它定义沙盒命名空间的状态并公开一个 API 以与之交互
  • 解释器:定义用于将特定输入语法转换为特定 Python 语法的 API 的类。它使用解释器(实例)来执行最终的语法。
  • 程序:要按顺序执行的语句列表以及用于执行它们并检索有关其结果的信息的便捷 API。

每个执行程序(实例)都需要为execeval 函数使用沙箱初始化自己。 这不是元类的用途:元类是用于初始化类对象...而不是类 instance 对象。要初始化一个类实例对象,我需要编写一个类。就是这样:只是一个类。

所以我写了一个 Executor,它用自己的 localsglobals dicts 初始化 executor 对象。它还需要提供在调用时始终使用这些字典的函数。这就是闭包的用途。我在每个实例对象中创建闭包的方法是在初始化对象后对其进行修补。

这是生成的 Executor 类:

from types import MethodType

class Executor:
    """Is a sandboxed executor and evaluator of Python statements."""

    def __init__(self, sbox_globals: dict=None, sbox_locals: dict=None):

        # Set up execution context for this instance
        if sbox_globals is not None:
            sbox_globals.setdefault('__builtins__', None)
            self.sbox_globals = sbox_globals
        else:
            self.sbox_globals = {'__builtins__': None}
        self.sbox_locals = {} if sbox_locals is None else sbox_locals

        # Monkey patch closures onto this instance
        def sbox_exec(self, source: str):
            """A persistent sandbox environment for calls to `exec`"""
            return exec(source, self.sbox_globals, self.sbox_locals)

        def sbox_eval(self, expr: str):
            """A persistent sandbox environment for calls to `eval`"""
            return eval(expr, self.sbox_globals, self.sbox_locals)

        self.execs = MethodType(sbox_exec, self)
        self.evals = MethodType(sbox_eval, self)

有了这个工具,我编写了一个同时实现解释器和程序角色的类,称之为Interpreter。一个解释器(实例)组成一个执行器(实例)并实现iterator protocol。它看起来像这样:

from collections.abc import Iterator

class Interpreter(Iterator):
    """
    Is a translator from input grammar to Python grammar. Is also an iterator
      that executes one statement per call.
    Has an Executor and uses it when called in iteration.
    """

    def __init__(self, instruction_set: list):
        self.exc = Executor(sbox_locals={})

        # iterator protocol
        self.instrs = instruction_set
        self.i = 0

    def __iter__(self):
        return super().__iter__()

    def __next__(self):
        if 0 <= self.i < len(self.instrs):

            instr = self.instrs[self.i][0]
            param = self.instrs[self.i][1]

            if instr == 'foo':
                cmd = f'py_foo({param})'
                self.exc.execs(cmd)
                ret = f'{self.i}: ' + cmd
            elif instr == 'bar':
                cmd = f'py_bar({param})'
                self.exc.execs(cmd)
                ret = f'{self.i}: ' + cmd
            else:
                ret = f'{self.i}: {instr} no-op'

            self.i += 1
            return ret
        else:
            raise StopIteration

【讨论】:

    猜你喜欢
    • 2020-09-27
    • 2023-03-14
    • 2018-08-14
    • 2013-07-04
    • 1970-01-01
    • 2016-06-13
    • 2013-02-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多