如何用Mixin元类在初始化时修改实例?伪汇编模块开发遇阻
Hey there! Let's tackle this problem step by step—since you're working on Advent of Code 2017, those pseudo-assembly problems are always a fun challenge, and building a reusable framework is such a smart approach. Below's a practical, structured solution that covers all your requirements:
1. Sandboxed exec/eval for Safe Isolated Execution
First, we need a safe execution environment to run pseudo-assembly code without risking unintended side effects. We'll create a restricted namespace that only includes the operations your pseudo-assembly needs, then wrap exec and eval to use this sandbox by default.
Sandbox Implementation
class SandboxedEnvironment: def __init__(self): # Define a minimal set of safe built-ins (customize for your pseudo-assembly needs) self.safe_builtins = { 'int': int, 'float': float, '+': lambda a, b: a + b, '-': lambda a, b: a - b, '*': lambda a, b: a * b, '//': lambda a, b: a // b, '%': lambda a, b: a % b, '==': lambda a, b: a == b, '!=': lambda a, b: a != b, '<': lambda a, b: a < b, '>': lambda a, b: a > b, } # Local namespace to store pseudo-assembly variables (registers, memory, etc.) self.local_namespace = {} def safe_exec(self, code: str): """Execute code in the isolated sandbox environment.""" exec(code, {'__builtins__': self.safe_builtins}, self.local_namespace) def safe_eval(self, expr: str) -> any: """Evaluate an expression in the sandbox and return the result.""" return eval(expr, {'__builtins__': self.safe_builtins}, self.local_namespace)
Why This Works
- The
safe_builtinsdict locks down execution to only operations your pseudo-assembly uses (no file access, system calls, etc.). - The
local_namespaceacts as the state for your interpreter—storing registers, program counters, or memory values between instructions.
2. Variable Resolution for Interpreter Namespaces
Next, we need a way to parse and resolve variables from the sandbox's namespace, handling both named registers (like reg_a) and literal values (like 5) that are common in pseudo-assembly.
Variable Resolution Method
Add this to the SandboxedEnvironment class:
def resolve_variable(self, var_ref: str) -> any: """Resolve a variable reference (register name or literal) from the sandbox.""" # Check if it's a named variable in the namespace if var_ref in self.local_namespace: return self.local_namespace[var_ref] # Try to parse as a numeric literal try: return int(var_ref) except ValueError: try: return float(var_ref) except ValueError: raise NameError(f"Undefined variable or invalid literal: {var_ref}")
Customization Tip
If your pseudo-assembly uses memory addresses (like [0x10]), extend this method to parse the address and fetch values from a memory dictionary stored in local_namespace.
3. Using Mixins (or Metaclasses) to Modify Instances on Initialization
Let's clarify: Mixins are simpler for modifying instance behavior at initialization, while metaclasses are for class-level changes. Here's how to use both, depending on your needs:
Option 1: Mixin Class (Simpler for Instance Setup)
If you just want to add default state (like registers) to every interpreter instance, a mixin is the way to go—it's clean and avoids overcomplicating with metaclasses.
class InterpreterInitMixin: def __init__(self, *args, **kwargs): # Run the parent class's initialization first super().__init__(*args, **kwargs) # Add default pseudo-assembly state to the sandbox self.local_namespace.update({ 'reg_a': 0, 'reg_b': 0, 'pc': 0, # Program counter for instruction flow 'memory': {}, # Optional: for memory-based pseudo-assembly }) # Combine the mixin with our sandbox to create a ready-to-use interpreter class PseudoAssemblyInterpreter(InterpreterInitMixin, SandboxedEnvironment): def __init__(self): super().__init__()
Option 2: Metaclass (For Class-Level Enforcement)
If you need to enforce initialization logic across multiple interpreter subclasses, a metaclass can modify the __init__ method dynamically when the class is created.
class InterpreterMeta(type): def __new__(cls, name, bases, attrs): # Capture the original __init__ if it exists original_init = attrs.get('__init__') def modified_init(self, *args, **kwargs): # Run the original init first if original_init: original_init(self, *args, **kwargs) # Inject default state self.local_namespace.update({ 'reg_a': 0, 'reg_b': 0, 'pc': 0, }) # Replace the class's __init__ with our modified version attrs['__init__'] = modified_init return super().__new__(cls, name, bases, attrs) # Apply the metaclass to our sandbox class PseudoAssemblyInterpreter(SandboxedEnvironment, metaclass=InterpreterMeta): def __init__(self): super().__init__()
Putting It All Together (Example Usage)
Here's how you'd use this framework to run a simple pseudo-assembly snippet:
# Create an interpreter instance interpreter = PseudoAssemblyInterpreter() # Convert pseudo-assembly to Python-compatible code (adjust for your problem's syntax) pseudo_code = """ reg_a += 3 reg_b = reg_a * 2 pc += 1 """ # Execute the code and check results interpreter.safe_exec(pseudo_code) print(interpreter.resolve_variable('reg_a')) # Output: 3 print(interpreter.resolve_variable('reg_b')) # Output: 6 print(interpreter.resolve_variable('pc')) # Output: 1
Key Tips for Advent of Code
- Customize the Sandbox: Add any operations your specific problem requires (bitwise shifts, logical ops, etc.) to
safe_builtins. - Add Instruction Parsing: Extend the interpreter to parse raw pseudo-assembly strings (like
inc reg_a) and convert them to code thatsafe_execcan run. - Error Handling: Wrap
safe_execin try-except blocks to catch invalid instructions or variable errors, making debugging easier.
内容的提问来源于stack exchange,提问作者Stobber

