2016-09-10 3 views
2

Я хочу сделать что-то решительно unpythonic. Я хочу создать класс, который позволяет пересылать декларации своих атрибутов класса. (Если вы должны знать, что я пытаюсь сделать некоторые сладкий синтаксис для синтаксического анализатора комбинаторов.)Использование метакласса, чтобы вперед декларации

Это вид, что я пытаюсь сделать:

a = 1 
class MyClass(MyBaseClass): 
    b = a # Refers to something outside the class 
    c = d + b # Here's a forward declaration to 'd' 
    d = 1 # Declaration resolved 

Мое текущее направление, чтобы сделать метакласса так что, когда d не найден, я поймаю исключение NameError и верну экземпляр некоторого фиктивного класса. Я позвоню ForwardDeclaration. Я черпаю вдохновение от AutoEnum, который использует магию метакласса для объявления значений перечисления с голой идентификацией и без назначения.

Ниже приведено то, что у меня есть. Недостающую часть: как же я по-прежнему нормальное разрешение имен и поймать NameError S:

class MetaDict(dict): 
    def __init__(self): 
     self._forward_declarations = dict() 
    def __getitem__(self, key): 
     try: 
      ### WHAT DO I PUT HERE ??? ### 
      # How do I continue name resolution to see if the 
      # name already exists is the scope of the class 
     except NameError: 
      if key in self._forward_declarations: 
       return self._forward_declarations[key] 
      else: 
       new_forward_declaration = ForwardDeclaration() 
       self._forward_declarations[key] = new_forward_declaration 
       return new_forward_declaration 

class MyMeta(type): 
    def __prepare__(mcs, name, bases): 
     return MetaDict() 

class MyBaseClass(metaclass=MyMeta): 
    pass 

class ForwardDeclaration: 
    # Minimal behavior 
    def __init__(self, value=0): 
     self.value = value 
    def __add__(self, other): 
     return ForwardDeclaration(self.value + other) 
+0

Не могли бы вы устранить это, используя аргументы по умолчанию (атрибуты в этом случае)? – Rockybilly

+0

Если я не понял вас, это не сработает, потому что я не буду знать, когда я определяю «MyBaseClass», какие атрибуты «MyClass» будут. Если бы я это сделал, вы были бы правы; Я мог бы просто объявить их как 'd = ForwardDeclaration()' в базовом классе. – drhagen

+0

Это звучит смешно отвратительно! Отлично сработано! Неясно, какова конкретная проблема/вопрос? –

ответ

1

начать с:

def __getitem__(self, key): 
     try: 
      return super().__getitem__(key) 
     except KeyError: 
      ... 

Но это не позволит вам получить глобальные переменные за пределами класс корпус. Вы также можете использовать __missin__ метод, который зарезервирован именно для подклассов Dict:

class MetaDict(dict): 
    def __init__(self): 
     self._forward_declarations = dict() 

    # Just leave __getitem__ as it is on "dict" 
    def __missing__(self, key): 
     if key in self._forward_declarations: 
      return self._forward_declarations[key] 
     else: 
      new_forward_declaration = ForwardDeclaration() 
      self._forward_declarations[key] = new_forward_declaration 
      return new_forward_declaration 

Как вы можете видеть, что это не то, что «UnPythonic» - расширенный Python вещи, такие как SymPy и SQLAlchemy должны прибегать к этому чтобы они делали свою приятную магию - просто убедитесь, что она очень хорошо документирована и протестирована.

Теперь, чтобы разрешить глобальные (модульные) переменные, вам нужно немного отвлечься - и, возможно, что-то, что может быть недоступно во всех реализациях Python, - это: интроспекция кадра, где тело класса чтобы получить его глобалам:

import sys 
... 
class MetaDict(dict): 
    def __init__(self): 
     self._forward_declarations = dict() 

    # Just leave __getitem__ as it is on "dict" 
    def __missing__(self, key): 
     class_body_globals = sys._getframe().f_back.f_globals 
     if key in class_body_globals: 
      return class_body_globals[key] 
     if key in self._forward_declarations: 
      return self._forward_declarations[key] 
     else: 
      new_forward_declaration = ForwardDeclaration() 
      self._forward_declarations[key] = new_forward_declaration 
      return new_forward_declaration 

Теперь, когда вы здесь - ваши специальные словари достаточно хороши, чтобы избежать NameErrors, но ваши ForwardDeclaration объекты далеко не достаточно умный - при работе:

a = 1 
class MyClass(MyBaseClass): 
    b = a # Refers to something outside the class 
    c = d + b # Here's a forward declaration to 'd' 
    d = 1 

что происходит это при c становится объектом ForwardDeclaration, но суммируется с мгновенным значением d, которое равно нулю. На следующей строке d просто перезаписывается значением 1 и больше не является ленивым объектом. Таким образом, вы можете просто объявить c = 0 + b.

Чтобы преодолеть это, ForwardDeclaration должен быть классом, разработанным в смартфоне, так что его значения всегда лениво оцениваются, и он ведет себя так же, как в подходе «реактивный программирование»: то есть: обновление значения будет каскадным обновлением в все остальные значения, которые зависят от него. Я думаю, что полная реализация рабочего «реактивного» класса FOrwardDeclaration падает с точки зрения этого вопроса. - У меня есть код игрушек, чтобы сделать это на github на https://github.com/jsbueno/python-react.

Даже при правильном «Реактивном» ForwardDeclaration класса, вы должны исправить свой словарь еще раз, чтобы d = 1 класса работы:

class MetaDict(dict): 
    def __init__(self): 
     self._forward_declarations = dict() 

    def __setitem__(self, key, value): 
     if key in self._forward_declarations: 
      self._forward_declations[key] = value 
      # Trigger your reactive update here if your approach is not 
      # automatic 
      return None 
     return super().__setitem__(key, value) 
    def __missing__(self, key): 
     # as above 

И, наконец, есть способ избежать havign реализовать полностью реактивный знает класс - вы можете разрешить все ожидающие FOrwardDependencies на __new__ методе метакласса - (так что ваши объекты ForwardDeclaration являются вручную «заморожены» во время создания класса, и нет дальнейших забот -)

что-то вдоль:

from functools import reduce 

sentinel = object() 
class ForwardDeclaration: 
    # Minimal behavior 
    def __init__(self, value=sentinel, dependencies=None): 
     self.dependencies = dependencies or [] 
     self.value = value 
    def __add__(self, other): 
     if isinstance(other, ForwardDeclaration): 
      return ForwardDeclaration(dependencies=self.dependencies + [self]) 
     return ForwardDeclaration(self.value + other) 

class MyMeta(type): 
    def __new__(metacls, name, bases, attrs): 
     for key, value in list(attrs.items()): 
       if not isinstance(value, ForwardDeclaration): continue 
       if any(v.value is sentinel for v in value.dependencies): continue 
       attrs[key] = reduce(lambda a, b: a + b.value, value.dependencies, 0) 

     return super().__new__(metacls, name, bases, attrs) 
    def __prepare__(mcs, name, bases): 
     return MetaDict() 

И, в зависимости от вашей иерархии классов, и что именно вы делаете, rememebr также обновить один класс Словарь _forward_dependencies с _forward_dependencies созданного на его предок. И если вам нужен какой-либо оператор, отличный от +, как вы уже отметили, вам нужно будет хранить информацию о самом операторе - на данный момент, возможно, также будет использовать sympy.

+0

Поиск существующих имен требует включения '__builtins__', который выполняется после' f_globals'. – drhagen

+0

Поиск существующих имен требует также проверки переменных внешних областей (если 'MyClass' определен в функции, которая сама может быть определена в функции). Я смотрел «проверять», но не мог понять, как пройти внешний масштаб рамки. – drhagen

Смежные вопросы