2016-09-10 10 views
2

私は決して平凡でないことをしたい。クラス属性の前方宣言を可能にするクラスを作成したいと思います。 (あなたが知っている必要がある場合、私はパーサコンビネータのためのいくつかの甘い構文を作るしようとしています。)メタクラスを使用して前方宣言を可能にする

これは私が作るしようとしているものの一種である:

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からインスピレーションを受けています。メタクラスマジックを使用して、裸の識別子と割り当てなしのenum値を宣言しています。

以下は私が今までに持っているものです。不足している部分がある:どのように私は、通常の名前解決を継続し、NameErrorのキャッチでください:で起動するには

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

私があなたを誤解していない限り、私は 'MyClass'の属性を' MyBaseClass'と定義した時点ではわからないので、うまくいきません。もし私がしたら、あなたは正しいでしょう。基本クラスの 'd = ForwardDeclaration()'のようにすべて宣言できます。 – drhagen

+0

これは陽気にうんざりするように聞こえる!よくやった!特定の問題/質問が何であるかはっきりしていませんか? –

答えて

1

を:

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

しかし、それはあなたが外でグローバル変数を取得することはできませんクラスボディ。 ます。また、辞書のサブクラスのために、正確に予約されて__missin__方法を使用することができます:あなたが見ることができるように

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を回避する方法があります意識クラス - あなたはメタクラスの__new__法上の保留中のすべてのFOrwardDependenciesを解決することができます - (あなたの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() 

そして、あなたのクラス階層と正確に何をやっているに応じて、また、その祖先で作成し_forward_dependenciesで1クラスのdictの_forward_dependenciesを更新するrememebr。 +以外の演算子が必要な場合は、注意したように、演算子自体に関する情報を保持する必要があります。この時点では、sympyを使用することもできます。

+0

既存の名前の検索には 'f_globals'の後に検索される' __builtins__'を含める必要があります。 – drhagen

+0

既存の名前の検索では、( 'MyClass'が関数で定義されている場合は、それ自体が関数で定義されるかもしれない)外部スコープの変数を調べる必要もあります。私は '検査する 'ことを見たが、フレームの外側の範囲をどのように歩くのか分からなかった。 – drhagen

関連する問題