クラス属性を読み取ってリストに格納するメタクラスを作成していますが、リスト(cls.columns)が宣言の順序(つまり、_mycol2
_、_mycol3
_、zut
、cool
、menfin
、a
私の例では):
_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
を直接使用しても、役に立ちません。
どうすれば続行できますか?
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']
このアプローチは既存のクラスでは役に立ちませんが、イントロスペクションを使用する必要があります。
これが私が開発したばかりの回避策です:
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
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__']
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
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でのみ機能します。
メソッドを除外する答え:
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)