簡體   English   中英

如何將函數裝飾器添加到接受此類作為裝飾器調用參數之一的類方法

[英]How to add function decorator to class method that accepts this class as one of decorator call arguments

當我嘗試這樣的代碼時:

def type_check(types):
  def decorator(f):
    def wrapper(self, v):
      if not type(v) in types:
        raise TypeError('Value is of type {} but allowed type(s): {}.'.format(type(v), types))
      return f(self, v)
    return wrapper
  return decorator

class MyList:
  @type_check(types=[list, MyList])
  def __init__(self, value):
    self.data = value[:]
  # ... next are operator overloading and other unrelated to question code ...

我得到以下回溯輸出:

 MyList @type_check(types=[list, MyList]) NameError: name 'MyList' is not defined

我通過從 MyListAnchor 繼承 MyList 並使用 isinstance() 而不是嚴格的類型相等來檢查類型來繞過這個:

def type_check(types):
  def decorator(f):
    def wrapper(self, v):
      if not any(filter(lambda t: isinstance(v, t), types)):
        raise TypeError('Value is of type {} but must be an instance(s) of: {}.'.format(type(v), types))
      return f(self, v)
    return wrapper
  return decorator

class MyListAnchor:
  pass

class MyList(MyListAnchor):
  @type_check(types=[list, MyListAnchor])
  def __init__(self, value):
    self.data = value[:]
  # ... next are operator overloading and other unrelated to question code ...

這是最好的解決方案嗎? 如何將函數裝飾器添加到接受此類作為裝飾器調用參數之一的類方法?

更新:我概括了裝飾器並解決了類裝飾器的初始問題:

"""
Type constrains format:
  'method_name': (pargs, kargs)
where pargs - list of constrains for positional only arguments, like this
  [[], [str, int], [Self]]
where empty list designate an absense of constrains and Self stands for class decorator
was added to.
Similarly kargs have format:
  {'aname_0': [], 'aname_1': [str, int], 'aname_2': [Self]}
WARNING:
  1) Positional only arguments in class methods constraned by this decorator must not
be passed by name (in keyworld-only args manner).
    Having "def foo(x, y, z): ..." call it like "foo(1, 2, 3)" but not "foo(1, 2, z=3)"
    or place constrains for "z" in either pargs or kargs. In either way you have to sacrifice
    some flexibility in way you pass args in method and do not mixing args passing styles (choose
    "foo(1, 2, 3)" or "foo(1, 2, z=3)") 
  2) When you call method with extra arguments (*pargs and **kwargs), extra arguments in *pargs and **kargs constrained
      by pargs[-1] or kargs[None].
"""
class Self: pass

def type_check(ptypes, ktypes):
  def decorator(f):
    def wrapper(*pargs, **kargs):
      i = 0
      while i < len(pargs):
        arg = pargs[i]
        types = ptypes[i] if i < len(ptypes) else ptypes[-1]
        if types and not type(arg) in types:
          raise TypeError('Value is of type {} but allowed type(s): {}.'.format(type(arg), types))
        i += 1
      for k, v in kargs.items():
        types = ktypes[k] if k in ktypes else ktypes[None]
        if not type(v) in types:
          raise TypeError('Value is of type {} but allowed type(s): {}.'.format(type(v), types))
      return f(*pargs, **kargs)
    return wrapper
  return decorator

def methods_constrained(meth_cons: dict):
  def decorator(c):
    for meth, (ptypes, ktypes) in meth_cons.items():
      for i in range(len(ptypes)):
        ptypes[i] = [c if t == Self else t for t in ptypes[i]]
      for k in ktypes:
        ktypes[k] = [c if t == Self else t for t in ktypes[k]]
      exec('c.{} = type_check(ptypes, ktypes)(c.__dict__[meth])'.format(meth))
    return c
  return decorator

以及用法示例:

import strict_types as st

@st.methods_constrained({
  '__init__': ([[], [list, st.Self]], {}),
  'foo': ([[], [dict, st.Self], [int, float], [str]], {'w': [st.Self], 'z': [float], 'qwerty': [bool], None: [bool]}),
})
class MyList:
  def __init__(self, value):
    self.data = value[:]
  def foo(self, x, y=1, *pargs, w, z=2, **kargs):
    print(self, x, y, pargs, w, z, kargs)
# ... next are operator overloading and other unrelated to question code ...

i = MyList([1, 2, 3])
i.foo({'key1': 'value'}, 3.14, 'a', 'b', 'c', 'd', w=i, z=0.1, qwerty=True, b0=False, b1=False, b2=True)

可能稍后我會從 args 注釋中添加關於約束的自動檢索信息(但在這種情況下,只允許一種類型)。 我也沒有考慮什么更好 - 嚴格類型相等或 isinstance() - 或者可能兩者都通過裝飾器的 args 選擇相等模式(默認為嚴格)。

PS:這個問題不是嘗試在 python 靜態類型中重新發明輪子,而只是對 python 裝飾器主題的練習。

您可以在定義類之后裝飾__init__

class MyList:
  def __init__(self, value):
    self.data = value[:]

MyList.__init__ = type_check(types=[list, MyList])(MyList.__init__)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM