जब मैं एक एनम बनाने के लिए कार्यात्मक एपीआई का उपयोग करता हूं, तो मुझे एक एनम ऑब्जेक्ट वापस मिलता है जो मनमाने ढंग से असाइनमेंट की अनुमति देता है (यानी इसमें __dict__ है):

e = enum.Enum('Things',[('foo',1),('bar',2)])
e.baz = 3

आइटम सूची में प्रकट नहीं होता है:

list(e)
[<foo.foo: 1>, <foo.bar: 2>]

लेकिन इसे अभी भी संदर्भित किया जा सकता है:

if thing == e.baz: ...

अब जबकि ऐसा होने की संभावना नहीं है, एक कारण है कि मैं एक एनम का उपयोग करना चाहता हूं, वर्तनी की गलतियों और स्ट्रिंग शाब्दिक को रोकने के लिए, और उन चीजों के लिए जब एक मॉड्यूल आयात किया जाता है या जितनी जल्दी हो सके पकड़ा जाता है।

क्या गतिशील रूप से एक एनम बनाने का कोई तरीका है जो __slots__ ऑब्जेक्ट की तरह व्यवहार करता है जो मनमानी विशेषताओं को असाइन करने की अनुमति नहीं देता है?

3
aaa90210 20 जिंदा 2019, 08:59

1 उत्तर

सबसे बढ़िया उत्तर

एक एनम क्लास को पूरी तरह से 'केवल पढ़ने के लिए' बनाने के लिए, केवल एक मेटा क्लास की आवश्यकता होती है जो __setattr__ हुक जो सभी एट्रिब्यूट असाइनमेंट को रोकता है। चूंकि मेटाक्लास इसे बनाए जाने के बाद के बाद वर्ग से जुड़ा हुआ है, इसलिए उचित एन्यूमरेटेड मान निर्दिष्ट करने में कोई समस्या नहीं है।

एथन के उत्तर की तरह, मैं कस्टम मेटाक्लास के आधार के रूप में EnumMeta वर्ग का उपयोग कर रहा हूं:

from enum import EnumMeta, Enum

class FrozenEnumMeta(EnumMeta):
    "Enum metaclass that freezes an enum entirely"
    def __new__(mcls, name, bases, classdict):
        classdict['__frozenenummeta_creating_class__'] = True
        enum = super().__new__(mcls, name, bases, classdict)
        del enum.__frozenenummeta_creating_class__
        return enum

    def __call__(cls, value, names=None, *, module=None, **kwargs):
        if names is None:  # simple value lookup
            return cls.__new__(cls, value)
        enum = Enum._create_(value, names, module=module, **kwargs)
        enum.__class__ = type(cls)
        return enum

    def __setattr__(cls, name, value):
        members = cls.__dict__.get('_member_map_', {})
        if hasattr(cls, '__frozenenummeta_creating_class__') or name in members:
            return super().__setattr__(name, value)
        if hasattr(cls, name):
            msg = "{!r} object attribute {!r} is read-only"
        else:
            msg = "{!r} object has no attribute {!r}"
        raise AttributeError(msg.format(cls.__name__, name))

    def __delattr__(cls, name):
        members = cls.__dict__.get('_member_map_', {})
        if hasattr(cls, '__frozenenummeta_creating_class__') or name in members:
            return super().__delattr__(name)
        if hasattr(cls, name):
            msg = "{!r} object attribute {!r} is read-only"
        else:
            msg = "{!r} object has no attribute {!r}"
        raise AttributeError(msg.format(cls.__name__, name))

class FrozenEnum(Enum, metaclass=FrozenEnumMeta):
    pass

निदान में आसानी के लिए उपरोक्त पहले से उपलब्ध विशेषताओं और नई विशेषताओं के बीच अंतर करता है। यह विशेषता हटाने को भी रोकता है, जो शायद उतना ही महत्वपूर्ण है!

यह गणना के लिए मेटाक्लास और FrozenEnum आधार वर्ग दोनों भी प्रदान करता है; Enum के बजाय इसका उपयोग करें।

नमूना Color एन्यूमरेशन को फ्रीज करने के लिए:

>>> class Color(FrozenEnum):
...     red = 1
...     green = 2
...     blue = 3
...
>>> list(Color)
[<Color.red: 1>, <Color.green: 2>, <Color.blue: 3>]
>>> Color.foo = 'bar'
Traceback (most recent call last):
    # ...
AttributeError: 'Color' object has no attribute 'foo'
>>> Color.red = 42
Traceback (most recent call last):
    # ...
Cannot reassign members.
>>> del Color.red
Traceback (most recent call last):
    # ...
AttributeError: Color: cannot delete Enum member.

ध्यान दें कि सभी विशेषता परिवर्तनों की अनुमति नहीं है, किसी नई विशेषता की अनुमति नहीं है, और हटाना भी अवरुद्ध है। जब नाम एनम सदस्य होते हैं, तो हम त्रुटि संदेशों को स्थिर रखने के लिए मूल EnumMeta हैंडलिंग को सौंपते हैं।

यदि आपका एनम उन गुणों का उपयोग करता है जो एनम वर्ग पर विशेषताओं को बदलते हैं, तो आपको या तो उन्हें श्वेतसूची में डालना होगा, या एकल अंडरस्कोर से शुरू होने वाले नामों को सेट करने की अनुमति देनी होगी; में __setattr__ निर्धारित करें कि उन अपवादों के लिए super().__setattr__(name, value) सेट करने और उपयोग करने के लिए कौन से नाम अनुमत होंगे, ठीक उसी तरह जैसे कोड अब ध्वज विशेषता का उपयोग करके वर्ग निर्माण और बाद के परिवर्तनों के बीच अंतर करता है।

उपरोक्त वर्ग का उपयोग Enum() की तरह प्रोग्रामेटिक रूप से एक एन्यूमरेशन बनाने के लिए किया जा सकता है:

e = FrozenEnum('Things', [('foo',1), ('bar',2)]))

डेमो:

>>> e = FrozenEnum('Things', [('foo',1), ('bar',2)])
>>> e
<enum 'Things'>
>>> e.foo = 'bar'
Traceback (most recent call last):
    # ...
AttributeError: Cannot reassign members.
4
Martijn Pieters 25 जिंदा 2019, 13:47