簡體   English   中英

Python:如何從“框架”object 中檢索 class 信息?

[英]Python: How to retrieve class information from a 'frame' object?

是否可以從框架 object 中檢索任何 class 信息? I know how to get the file (frame.f_code.co_filename), function (frame.f_code.co_name) and line number (frame.f_lineno), but would like to be able to also get the name of the class of the active object框架的實例(如果不在實例中,則為無)。

我不相信,在框架對象級別,有任何方法可以找到已被調用的實際 python 函數對象。

但是,如果您的代碼依賴於通用約定:命名方法的實例參數self ,那么您可以執行以下操作:

def get_class_from_frame(fr):
  import inspect
  args, _, _, value_dict = inspect.getargvalues(fr)
  # we check the first parameter for the frame function is
  # named 'self'
  if len(args) and args[0] == 'self':
    # in that case, 'self' will be referenced in value_dict
    instance = value_dict.get('self', None)
    if instance:
      # return its class
      return getattr(instance, '__class__', None)
  # return None otherwise
  return None

如果您不想使用getargvalues ,您可以直接使用frame.f_locals而不是value_dictframe.f_code.co_varnames[:frame.f_code.co_argcount]而不是args

請記住,這仍然僅依賴於約定,因此它不可移植,並且容易出錯:

  • 如果非方法函數使用self作為第一個參數名稱,那么get_class_from_frame將錯誤地返回第一個參數的類。
  • 使用描述符時可能會產生誤導(它將返回描述符的類,而不是正在訪問的實際實例的類)。
  • @classmethod@staticmethod不會接受self參數,而是使用描述符實現的。
  • 當然還有更多

根據您到底想要做什么,您可能需要花一些時間深入挖掘並找到所有這些問題的解決方法(您可以檢查返回的類中是否存在 frame 函數並共享相同的源,檢測描述符調用是可能的,類方法等也一樣。)

這有點短,但大致相同。 如果類名不可用,則返回 None。

def get_class_name():
    f = sys._getframe(1)

    try:
        class_name = f.f_locals['self'].__class__.__name__
    except KeyError:
        class_name = None

    return class_name

我剛剛遇到了這篇文章,因為我遇到了同樣的問題。 然而,由於已經列出的所有原因,我並不認為“自我”方法是一個可接受的解決方案。

下面的代碼演示了一種不同的方法:給定一個框架對象,它在全局變量中搜索具有匹配成員名稱和代碼塊的對象。 搜索很難窮盡,因此可能不會發現所有類,但找到的類應該是我們正在尋找的類,因為我們會驗證匹配代碼。

代碼的目標是在函數名前面加上它的類名,如果找到的話:

def get_name( frame ):
  code = frame.f_code
  name = code.co_name
  for objname, obj in frame.f_globals.iteritems():
    try:
      assert obj.__dict__[name].func_code is code
    except Exception:
      pass
    else: # obj is the class that defines our method
      name = '%s.%s' % ( objname, name )
      break
  return name

請注意使用__dict__而不是getattr來防止捕獲派生類。

進一步注意,如果self = frame.f_locals['self']; obj = self.__class__可以避免全局搜索self = frame.f_locals['self']; obj = self.__class__ self = frame.f_locals['self']; obj = self.__class__給出匹配,或者obj in self.__class__.__bases__任何obj in self.__class__.__bases__或更深,所以肯定有優化/混合的空間。

如果方法是類方法,則該類將是第一個參數。 如果每個調用堆棧幀存在第一個 arg ,這會打印出它的類型:

    def some_method(self):
        for f in inspect.getouterframes(inspect.currentframe() ):
            args, _,_, local_dict = inspect.getargvalues(f[0])
            if args: 
                first_arg = args[0]
                first_value = local_dict[first_arg]
                print(type(first_value).__name__) 

你好,原諒死靈; 但這些都不適合我,所以受到這里的一些片段的啟發,我做了一個替代方案。 這就是我如何在 python 3.7 中為類方法做到的:

import inspect;

def QLNAME(frame):

    code = frame.f_code;
    name = code.co_name;
    qual = None;

    for cls in ( obj for obj in frame.f_globals.values()  #
                     if  inspect.isclass(obj)             ):

        if hasattr(cls, name):

            member = getattr(cls, name);
            if not inspect.isfunction(member): continue;

            if member.__code__ == code:
                qual = member.__qualname__;
                break;

    return qual;

它的工作方式實際上很簡單,我們檢查

  1. 對於框架全局范圍內的類
  2. 如果這個班級有一個成員分享有問題的名字
  3. 該成員是否為函數
  4. 最后,比較兩個函數的代碼

如果 Nº4 為真,那么我們就知道cls是一個具有某些方法member的類,其代碼與我們從框架中獲得的代碼相同。

一切都很好,但這並不完美。 就我測試過的而言,它不適用於屬性修飾的方法,而且我還不確定如何處理這種極端情況。 但它在常規和靜態方法上對我有用,這是我目前所需要的,所以足夠公平。

但是請注意,這非常慢,盡管我認為這是不言而喻的。 根據我自己的基准測試,每 100,000 個調用通常需要大約 2 到 3 秒。 這對於我當前的用例來說是可以接受的,但是如果您需要一次多次使用它,您可能需要先進行一些優化。 我會把它交給你能干的人(:

希望搜索引擎在這里絆倒的其他人可以找到一些有用的東西。

干杯。

以下答案通過使用 GC 獲取類型:

另一個死靈,但這次有一個明確的答案,具有完全想要的結果。

這個想法是利用 GC 中保存的對 function 的引用。 底線:使用下面定義的函數,返回值是調用 class:

import inspect

def get_type_of_calling_func():
    frame = inspect.currentframe().f_back
    func = get_func_from_frame(frame)
    return get_class_from_func(func)

class Class:
    def f(self):
        return get_type_of_calling_func()

print(Class().f())
# <class '__main__.Class'>

下面在兩個實用函數中給出了主要功能(划分是為了可用性)。 這些函數的定義是:

import gc
from types import FunctionType, MethodType, FrameType
from typing import Optional, Union, Iterator, Mapping


def get_func_from_frame(frame: FrameType) -> Optional[FunctionType]:
    # Nifty trick - get the function from the reference to its code object
    refs = gc.get_referrers(frame.f_code)

    for ref in refs:
        if isinstance(ref, FunctionType):
            return ref

    return None


def get_class_from_func(func: Union[FunctionType, classmethod, MethodType]) -> Optional[type]:
    cls_qual_name, func_name = func.__qualname__.rsplit('.', 1)
    assert func_name == func.__name__

    # If classmethod or method
    if not isinstance(func, FunctionType):
        self = getattr(func, '__self__', None)
        for possibility in (self, type(self)):
            if isinstance(possibility, type) and possibility.__qualname__ == cls_qual_name:
                return possibility

    # Recursively check GC references
    visited_ids = set()
    items_to_check = [func]

    # The function is referenced within the type's dict, or its descriptor
    while len(items_to_check) > 0:
        obj = items_to_check[0]
        items_to_check = items_to_check[1:]

        obj_id = id(obj)
        if obj_id in visited_ids:
            continue
        visited_ids.add(obj_id)

        refs = gc.get_referrers(obj)
        for ref in refs:
            if isinstance(ref, type) and ref.__qualname__ == cls_qual_name:
                return ref

            if (
                    hasattr(ref, '__get__') or
                    # Then this is a descriptor (e.g. `classmethod`). Check if this works with the descriptor
                    isinstance(ref, Mapping)
                    # Then this is possibly a `__dict__` of a type
            ):
                items_to_check.append(ref)

    return None

暫無
暫無

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

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