简体   繁体   中英

Setting a member variable string whose value is its name

I want to replace string literals in my code, as I want to minimize risk of typos, especially in dict key sets:

a['typoh'] = 'this is bad'
  • I don't want to type things in twice (risk of a missed typo on the value)
  • I want it to be "trackable" by various IDEs (ie click thru to see where it is defined and escape completion).
  • Enums are out: 'Eaname' to get 'a' is dumb.

I have been told this can be done with slots , but I can't figure out how without a little trickery. I can think of a few ways below:

This is an unacceptable answer:

class TwiceIsNotNice(object):
    this_is_a_string = 'this_is_a_string'
    ... (five thousand string constants in)
    this_has_a_hard_to_spot_typographical_error = 
                   'this_has_a_had_to_spot_typographical_error'
    ... (five thousand more string constants)

A clear but annoying way is with a "Stringspace" class/object where the attributes are set via a string list passed in. This solves the minimized typo risk, is VERY easy to read, but has neither IDE trackability nor autocompletion. It's okay, but makes people complain (please don't complain here, I am simply showing how it could be done):

string_consts = Stringspace('a', 'b',...,'asdfasdfasdf')
print(string_consts.a)

... where:
class Stringspace(object):
    def __init__(self, *strlist):
        for s in strlist:
            setattr(self, s, s)

Another way is to define a class using a sentinel object, setting the value in a post phase. This is okay, is trackable, presents itself as an actual class, allows for aliases, etc. But it requires an annoying extra call at the end of the class:

same = object()

class StrList(object):
    this_is_a_strval = same
    this_is_another_strval = same
    this_gets_aliased = "to something else"

# This would of course could become a function
for attr in dir(StrList):
    if getattr(StrList, attr) is same:
        setattr(StrList, attr, attr) 

print(StrList.a)

If this is what the slot magic is supposedly about, then I am disappointed, as one would have to actually instantiate an object:

class SlotEnum(object):
    __slots__ = []
    def __init__(self):
       for k in self.__slots__:
            setattr(self, k, k)

class Foo(SlotEnum):
    __slots__ = ['a', 'b']

foo_enum_OBJECT = Foo()
print(foo_enum_OBJECT.a)

I found one solution at this external link using a custom meta class, for your class containing the string member variables:

Step 1 of 2: The custom meta class can be defined like this:

class MetaForMyStrConstants(type):
    def __new__(metacls, cls, bases, classdict):
        object_attrs = set(dir(type(cls, (object,), {})))
        simple_enum_cls = super().__new__(metacls, cls, bases, classdict)
        simple_enum_cls._member_names_ = set(classdict.keys()) - object_attrs
        non_members = set()
        for attr in simple_enum_cls._member_names_:
            if attr.startswith('_') and attr.endswith('_'):
                non_members.add(attr)
            else:
                setattr(simple_enum_cls, attr, attr)

        simple_enum_cls._member_names_.difference_update(non_members)

        return simple_enum_cls

Step 2 of 2: The class defining your strings can be defined like this (with dummy values, eg, empty tuples):

class MyStrConstants(metaclass=MetaForMyStrConstants):
    ONE_LONG_STR = ()
    ANOTHER_LONG_STR = ()
    THE_REAL_LONGEST_STR = ()

Testing it out:

print (MyStrConstants.ONE_LONG_STR)
print (MyStrConstants.ANOTHER_LONG_STR)
print (MyStrConstants.THE_REAL_LONGEST_STR)

Output:

ONE_LONG_STR
ANOTHER_LONG_STR
THE_REAL_LONGEST_STR
  • Enums are out: Eaname to get a is dumb.
from enum import Enum, auto

class StrEnum(str, Enum):
    "base class for Enum members to be strings matching the member's name"

    def __repr__(self):
        return '<%s.%s>' % (self.__class__.__name__, self.name)

    def __str__(self):
        return self.name


class E(StrEnum):

    a = auto()
    this_is_a_string = auto()
    no_typo_here = auto()
>>> print(repr(E.a))
<E.a>

>>> print(E.a)
a

>>> print('the answer is: %s!' % E.a)
the answer is: a!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM