web-dev-qa-db-ja.com

検査pythonクラス属性

ユーザー定義のクラス属性である属性を安全に識別できるように、クラスを検査する方法が必要です。問題は、dir()、inspect.getmembers()などの関数が、__class____doc____dict____hash__などの定義済みの属性を含むすべてのクラス属性を返すことです。これはもちろん理解可能であり、名前付きメンバーのリストを無視するだけでよいと主張することもできますが、残念ながら、これらの事前定義された属性はPythonの異なるバージョンで変更されるため、プロジェクトが魅力的になります。 pythonプロジェクトで変更されました-そして私はそれが好きではありません。

例:

>>> class A:
...   a=10
...   b=20
...   def __init__(self):
...     self.c=30
>>> dir(A)
['__doc__', '__init__', '__module__', 'a', 'b']
>>> get_user_attributes(A)
['a','b']

上記の例では、インスタンス属性であるため、ユーザー定義のクラス属性['a'、 'b']のみを安全に取得する方法が必要です。だから私の質問は...上記の架空の関数get_user_attributes(cls)で誰かが私を手伝ってくれる?

追伸私は、クラスをASTレベルで解析することによって問題を解決するために、かなり簡単な時間を費やしました。しかし、すでに解析されたオブジェクトをASTノードツリーに変換する方法が見つかりません。クラスがバイトコードにコンパイルされると、すべてのAST情報が破棄されると思います。

よろしくヤコブ

33

以下は難しい方法です。これが簡単な方法です。なぜそれがすぐに起こらなかったのか分からない。

_import inspect

def get_user_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return [item
            for item in inspect.getmembers(cls)
            if item[0] not in boring]
_

はじめに

_def get_user_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    attrs = {}
    bases = reversed(inspect.getmro(cls))   
    for base in bases:
        if hasattr(base, '__dict__'):
            attrs.update(base.__dict__)
        Elif hasattr(base, '__slots__'):
            if hasattr(base, base.__slots__[0]): 
                # We're dealing with a non-string sequence or one char string
                for item in base.__slots__:
                    attrs[item] = getattr(base, item)
            else: 
                # We're dealing with a single identifier as a string
                attrs[base.__slots__] = getattr(base, base.__slots__)
    for key in boring:
        del attrs['key']  # we can be sure it will be present so no need to guard this
    return attrs
_

これはかなり堅牢なはずです。基本的には、無視するobjectのデフォルトのサブクラスにある属性を取得することで機能します。次に、渡されたクラスのmroを取得し、サブクラスキーがスーパークラスキーを上書きできるように逆順でトラバースします。キーと値のペアの辞書を返します。 _inspect.getmembers_のようなキー、値のタプルのリストが必要な場合は、attrs.items()またはlist(attrs.items())を返すPython 3。

実際にmroをトラバースせず、属性をサブクラスで直接定義するだけの場合は、簡単です。

_def get_user_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    if hasattr(cls, '__dict__'):
        attrs = cls.__dict__.copy()
    Elif hasattr(cls, '__slots__'):
        if hasattr(base, base.__slots__[0]): 
            # We're dealing with a non-string sequence or one char string
            for item in base.__slots__:
                attrs[item] = getattr(base, item)
            else: 
                # We're dealing with a single identifier as a string
                attrs[base.__slots__] = getattr(base, base.__slots__)
    for key in boring:
        del attrs['key']  # we can be sure it will be present so no need to guard this
    return attrs
_
29
aaronasterling

「特別な属性」の両端にある二重下線は、2.0以前はpythonの一部でした。近い将来に変更される可能性はほとんどありません。

class Foo(object):
  a = 1
  b = 2

def get_attrs(klass):
  return [k for k in klass.__dict__.keys()
            if not k.startswith('__')
            and not k.endswith('__')]

print get_attrs(Foo)

['a'、 'b']

6
nate c

Aaronasterlingに感謝します。必要な式をくれました:-)最終的なクラス属性インスペクター関数は次のようになります。

def get_user_attributes(cls,exclude_methods=True):
  base_attrs = dir(type('dummy', (object,), {}))
  this_cls_attrs = dir(cls)
  res = []
  for attr in this_cls_attrs:
    if base_attrs.count(attr) or (callable(getattr(cls,attr)) and exclude_methods):
      continue
    res += [attr]
  return res

クラス属性変数のみを返す(exclude_methods = True)か、メソッドを取得します。上記の関数の最初のテストでは、古いスタイルと新しいスタイルの両方をサポートしていますpythonクラス。

/ヤコブ

3

新しいスタイルクラスを使用する場合、親クラスの属性を単純に差し引くことができますか?

class A(object):
    a = 10
    b = 20
    #...

def get_attrs(Foo):
    return [k for k in dir(Foo) if k not in dir(super(Foo))]

編集:違います。 __dict____module__および__weakref__は、オブジェクトから継承するときに表示されますが、オブジェクト自体にはありません。あなたはこれらを特別なケースにすることができます-それらが非常に頻繁に変わるとは思えません。

2
Thomas K

スレッドを壊死させてすみません。 2019年現在、そのような一般的な使用法を処理する単純な関数(またはライブラリ)がまだないことに驚いています。

私はこのアイデアをアーロナスターリングに感謝します。実際、setコンテナはそれを表現するためのより簡単な方法を提供します:

class dummy:    pass

def abridged_set_of_user_attributes(obj):
    return set(dir(obj))-set(dir(dummy))

def abridged_list_of_user_attributes(obj):
    return list(abridged_set_of_user_attributes(obj))

inキーワードが1つしかないにもかかわらず、2つのforキーワードが複合されているため、リスト内包表記を使用した元のソリューションは、実際には2レベルのループです。

1
Hoi Wong