web-dev-qa-db-ja.com

宣言されたのと同じ順序でクラス属性を読み取る方法は?

クラス属性を読み取ってリストに格納するメタクラスを作成していますが、リスト(cls.columns)が宣言の順序(つまり、_mycol2_、_mycol3_、zutcoolmenfina私の例では):

_import inspect
import pprint

class Column(object):
    pass

class ListingMeta(type):
    def __new__(meta, classname, bases, classDict):
        cls = type.__new__(meta, classname, bases, classDict)
        cls.columns = inspect.getmembers(cls, lambda o: isinstance(o, Column)) 
        cls.nb_columns = len(cls.columns)
        return cls

class Listing(object):
    __metaclass__ = ListingMeta
    mycol2 = Column()
    mycol3 = Column()
    zut = Column()
    cool = Column()
    menfin = Column()
    a = Column()

pprint.pprint(Listing.columns)
_

結果:

_[('a', <__main__.Column object at 0xb7449d2c>),
 ('cool', <__main__.Column object at 0xb7449aac>),
 ('menfin', <__main__.Column object at 0xb7449a8c>),
 ('mycol2', <__main__.Column object at 0xb73a3b4c>),
 ('mycol3', <__main__.Column object at 0xb744914c>),
 ('zut', <__main__.Column object at 0xb74490cc>)]
_

これは、ListingクラスのColumn()属性の宣言順序を尊重しません。 classDictを直接使用しても、役に立ちません。

どうすれば続行できますか?

41
Eric

Pythonの現在のバージョンでは、クラスの順序は保持されます。詳細については、 PEP52 を参照してください。

古いバージョンの言語(3.5以下、ただし2.xではない)では、クラス名前空間にOrderedDictを使用するメタクラスを提供できます。

import collections 

class OrderedClassMembers(type):
    @classmethod
    def __prepare__(self, name, bases):
        return collections.OrderedDict()

    def __new__(self, name, bases, classdict):
        classdict['__ordered__'] = [key for key in classdict.keys()
                if key not in ('__module__', '__qualname__')]
        return type.__new__(self, name, bases, classdict)

class Something(metaclass=OrderedClassMembers):
    A_CONSTANT = 1

    def first(self):
        ...

    def second(self):
        ...

print(Something.__ordered__)
# ['A_CONSTANT', 'first', 'second']

このアプローチは既存のクラスでは役に立ちませんが、イントロスペクションを使用する必要があります。

38
Thomas Perl

これが私が開発したばかりの回避策です:

import inspect

class Column(object):
    creation_counter = 0
    def __init__(self):
        self.creation_order = Column.creation_counter
        Column.creation_counter+=1

class ListingMeta(type):
    def __new__(meta, classname, bases, classDict):
        cls = type.__new__(meta, classname, bases, classDict)
        cls.columns = sorted(inspect.getmembers(cls,lambda o:isinstance(o,Column)),key=lambda i:i[1].creation_order) 
        cls.nb_columns = len(cls.columns)
        return cls

class Listing(object):
    __metaclass__ = ListingMeta
    mycol2 = Column()
    mycol3 = Column()
    zut = Column()
    cool = Column()
    menfin = Column()
    a = Column()


for colname,col in Listing.columns:
    print colname,'=>',col.creation_order
15
Eric

python 3.6の場合、これがデフォルトの動作になりました。PEP520を参照してください: https://www.python.org/dev/peps/pep-0520/

class OrderPreserved:
    a = 1
    b = 2
    def meth(self): pass

print(list(OrderPreserved.__dict__.keys()))
# ['__module__', 'a', 'b', 'meth', '__dict__', '__weakref__', '__doc__']
11
Conchylicultor

Python 2.xを使用している場合は、Lennartが提案しているようなハックが必要です。Python 3.xを使用している場合は、 PEP 3115 必要なことを実行する例が含まれているため、Column()インスタンスのみを確認するように例を変更するだけです。

 # The custom dictionary
 class member_table(dict):
    def __init__(self):
       self.member_names = []

    def __setitem__(self, key, value):
       # if the key is not already defined, add to the
       # list of keys.
       if key not in self:
          self.member_names.append(key)

       # Call superclass
       dict.__setitem__(self, key, value)

 # The metaclass
 class OrderedClass(type):

     # The prepare function
     @classmethod
     def __prepare__(metacls, name, bases): # No keywords in this case
        return member_table()

     # The metaclass invocation
     def __new__(cls, name, bases, classdict):
        # Note that we replace the classdict with a regular
        # dict before passing it to the superclass, so that we
        # don't continue to record member names after the class
        # has been created.
        result = type.__new__(cls, name, bases, dict(classdict))
        result.member_names = classdict.member_names
        return result

 class MyClass(metaclass=OrderedClass):
    # method1 goes in array element 0
    def method1(self):
       pass

    # method2 goes in array element 1
    def method2(self):
       pass
6
Duncan

1)Python 3.6のクラス定義の属性は、名前がソースに表示されるのと同じ順序であるため、この順序は新しいクラスの__dict__属性に保持されるようになりました(- https://docs.python.org/3.6/whatsnew/3.6.html#whatsnew36-pep52 ):

class Column:
    pass

class MyClass:
    mycol2 = Column()
    mycol3 = Column()
    zut = Column()
    cool = Column()
    menfin = Column()
    a = Column()

print(MyClass.__dict__.keys())

次のような出力が表示されます(MyClass.__dict__はOrderedDictのように使用できます):

dict_keys(['__module__', 'mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a', '__dict__', '__weakref__', '__doc__'])

Pythonによって追加された余分な__xxx__フィールドに注意してください。無視する必要があるかもしれません。

2)以前のPython 3.xバージョンの場合、@ Duncanの回答に基づくソリューションを使用できますが、より簡単です。 __prepare__メソッドは単純なOrderDictではなくdictを返すという事実を使用しているため、__new__呼び出しの前に収集されたすべての属性が順序付けられます。

from collections import OrderedDict

class OrderedClass(type):
    @classmethod
    def __prepare__(mcs, name, bases): 
         return OrderedDict()

    def __new__(cls, name, bases, classdict):
        result = type.__new__(cls, name, bases, dict(classdict))
        result.__fields__ = list(classdict.keys())
        return result

class Column:
    pass

class MyClass(metaclass=OrderedClass):
    mycol2 = Column()
    mycol3 = Column()
    zut = Column()
    cool = Column()
    menfin = Column()
    a = Column()

これで、属性__fields__を使用して、必要な順序で属性にアクセスできます。

m = MyClass()
print(m.__fields__)
['__module__', '__qualname__', 'mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a']

typeクラスから生まれたattrs'__module__''__qualname__'があることに注意してください。それらを取り除くには、次の方法で名前をフィルタリングできます(OrderedClass.__new__を変更)。

def __new__(cls, name, bases, classdict):
    result = type.__new__(cls, name, bases, dict(classdict))
    exclude = set(dir(type))
    result.__fields__ = list(f for f in classdict.keys() if f not in exclude)
    return result    

myClassからの属性のみを提供します。

['mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a']

3)python2.7には__prepare__定義がないため、この回答はpython3.xでのみ機能します。

6
Nikolai Saiko

メソッドを除外する答え:

from collections import OrderedDict
from types import FunctionType


class StaticOrderHelper(type):
    # Requires python3.
    def __prepare__(name, bases, **kwargs):
        return OrderedDict()

    def __new__(mcls, name, bases, namespace, **kwargs):
        namespace['_field_order'] = [
                k
                for k, v in namespace.items()
                if not k.startswith('__') and not k.endswith('__')
                    and not isinstance(v, (FunctionType, classmethod, staticmethod))
        ]
        return type.__new__(mcls, name, bases, namespace, **kwargs)


class Person(metaclass=StaticOrderHelper):
    first_name = 'First Name'
    last_name = 'Last Name'
    phone_number = '000-000'

    @classmethod
    def classmethods_not_included(self):
        pass

    @staticmethod
    def staticmethods_not_included(self):
        pass

    def methods_not_included(self):
        pass


print(Person._field_order)
3
o11c