有没有Python'shortcut'定义一个class变量等于自己名字的字符串版本?

Is there a Python 'shortcut' to define a class variable equal to a string version of its own name?

这有点傻,但我想知道 Python 中是否有简洁的方法来定义包含自己名称的字符串表示的 class 变量。例如,可以定义:

class foo(object):
    bar = 'bar'
    baz = 'baz'
    baf = 'baf'

根据消耗的行数,可能更简洁的写法是:

class foo(object):
    bar, baz, baf = 'bar', 'baz', 'baf'

尽管如此,我仍然必须将每个标识符键入两次,在作业的每一侧各一次,并且打字错误的机会很普遍。

我想要的是 sympy 在其 var 方法中提供的东西:

sympy.var('a,b,c')

上面将变量abc注入命名空间,定义为对应的sympy符号变量。

是否有类似的东西可以对纯字符串执行此操作?

class foo(object):
    [nifty thing]('bar', 'baz', 'baf')

编辑: 注意,我希望能够在使用 foo:

的代码中将它们作为单独的标识符访问
>>> f = foo(); print(f.bar)
bar

附录: 鉴于对这个问题的兴趣,我想我会提供更多关于我为什么要这样做的背景信息。我目前有两个用例:(1)一组自定义异常的类型代码(每个 Exception subclass 都有一个不同的类型代码集); (2) 轻量级枚举。我想要的功能集是:

  1. 只需在源定义中键入一次类型代码/枚举名称(或值)。 class foo(object): bar = 'bar' 工作正常,但意味着我必须在源代码中输入两次,这对较长的名称来说很烦人,并且有打字错误的风险。
  2. 为 IDE 自动完成公开的有效类型代码/枚举值。
  3. 内部存储为可理解字符串的值:

    1. 对于 Exception subclasses,我希望能够将 myError.__str__ 定义为类似于 return self.typecode + ": " + self.message + " (" + self.source + ")" 的东西,而不必做很多事情dict-fu 将 self.typecodeint 值反向引用为可理解且有意义的字符串。
    2. 对于枚举,我希望能够从 e = myEnum.widget; print(e) 获得 widget 作为输出,同样不需要很多 dict-fu。

      • 我知道这会增加开销。我的应用程序对速度不敏感(用于驱动单独程序的基于 GUI 的工具),所以我认为这根本不重要。
  4. 直接的成员资格测试,通过还包括(比如)一个包含所有类型代码/枚举字符串值的 frozenset 作为 myError.typecodes/myEnum.E class是的。这解决了通过 if not enumVal in myEnum.E: raise(ValueError('Invalid enum value: ' + str(enumVal))).

  5. 等简单完整性检查意外(或故意……但为什么呢?!)使用无效类型代码/枚举字符串的潜在问题
  6. 能够通过from errmodule import squirrelerror导入单个枚举/异常子classes,以避免使用不相关的异常子classes使使用环境的命名空间混乱.我相信这会禁止任何需要 post-twiddling 的解决方案,就像 Sinux 提出的那样。
  7. 对于枚举用例,我宁愿避免引入额外的包依赖性,因为我(认为我)不关心官方 enum class 中可用的任何额外功能。无论如何,它仍然无法解决#1。

除了#1 之外,我已经找到了令我满意的所有上述实施方式。我对 #1 的解决方案(不破坏其他解决方案)的兴趣部分是希望将类型代码/枚举值输入到源代码中以防止打字错误,部分是因为懒惰。 (刚刚输入关于该主题的巨大 SO 问题的人说。)

如何将变量定义为空字符串,然后获取它们的名称:

class foo(object):
    def __getitem__(self, item):
        return item


foo = foo()
print foo['test']

我推荐使用 collections.namedtuple:

示例:

>>> from collections import namedtuple as nifty_thing
>>> Data = nifty_thing("Data", ["foo", "bar", "baz"])
>>> data = Data(foo=1, bar=2, baz=3)
>>> data.foo
1
>>> data.bar
2
>>> data.baz
3

旁注: 如果你是 using/on Python 3.x 我会根据@user2357112 的评论推荐 Enum .这是 Python 3+

的标准化方法

更新: 好吧,如果我在这里理解 OP 的 确切 要求,我认为唯一的方法是做到这一点( 大概 sympy 也这样做 ) 是将 names/variables 注入 globals()locals() 命名空间。示例:

#!/usr/bin/env python


def nifty_thing(*names):
    d = globals()
    for name in names:
        d[name] = None


nifty_thing("foo", "bar", "baz")

print foo, bar, bar

输出:

$ python foo.py 
None None None

注意: 我真的不推荐这个! :)

更新 #2: 你在问题中展示的另一个例子是这样实现的:

#!/usr/bin/env python


import sys


def nifty_thing(*names):
    frame = sys._getframe(1)
    locals = frame.f_locals

    for name in names:
        locals[name] = None


class foo(object):

    nifty_thing("foo", "bar", "baz")


f = foo()

print f.foo, f.bar, f.bar

输出:

$ python foo.py 
None None None

注意: 这是受 zope.interface.implements().

的启发

这与您要求的不完全一样,但它似乎应该可以完成工作:

class AutoNamespace(object):
    def __init__(self, names):
        try:
            # Support space-separated name strings
            names = names.split()
        except AttributeError:
            pass
        for name in names:
            setattr(self, name, name)

演示:

>>> x = AutoNamespace('a b c')
>>> x.a
'a'

如果你想做 SymPy 用 var 做的事情,你可以,但我强烈建议不要这样做。也就是说,这是一个基于 sympy.var:

源代码的函数
def var(names):
    from inspect import currentframe
    frame = currentframe().f_back
    try:
        names = names.split()
    except AttributeError:
        pass
    for name in names:
        frame.f_globals[name] = name

演示:

>>> var('foo bar baz')
>>> bar
'bar'

它将始终创建全局变量,即使您从函数或 class 内部调用它也是如此。 inspect 用于获取调用者的全局变量,而 globals() 将获取 var 自己的全局变量。

current_list  = ['bar', 'baz', 'baf']

class foo(object):
    """to be added"""

for i in current_list:
    setattr(foo, i, i)

然后 运行 这个:

>>>f = foo()
>>>print(f.bar)
bar
>>>print(f.baz)
baz

这里是 bman 想法的扩展。这有其优点和缺点,但至少它确实适用于某些自动完成器。

class FooMeta(type):
    def __getattr__(self, attr):
        return attr

    def __dir__(self):
        return ['bar', 'baz', 'baf']

class foo:
    __metaclass__ = FooMeta

这允许像 foo.xxx'xxx' 那样访问所有 xxx,但也可以通过 __dir__ 引导自动完成。

找出我要找的东西:

>>> class tester:
...     E = frozenset(['this', 'that', 'the', 'other'])
...     for s in E:
...         exec(str(s) + "='" + str(s) + "'") # <--- THIS
...         
>>> tester()
<__main__.tester instance at 0x03018BE8>
>>> t = tester()
>>> t.this
'this'
>>> t.that in tester.E
True

只需定义一次元素字符串,我很确定它会满足我在问题中列出的所有要求。在实际实现中,我打算将str(s) + "='" + str(s) + "'"封装在一个辅助函数中,这样我就可以在for循环中调用exec(helper(s))。 (我很确定 exec 必须放在 class 的主体中,而不是放在辅助函数中,否则新变量将被注入到辅助函数,不是 class.)

的辅助函数

编辑: 经过详细测试,这不起作用——exec 的使用阻止了 IDE 的内省知道存在创建的变量。

我认为你可以使用 metaclasses 实现一个相当漂亮的解决方案,但我不够流利地使用它们来呈现它作为答案,但我确实有一个似乎可行的选项相当不错:

def new_enum(name, *class_members):                
    """Builds a class <name> with <class_members> having the name as value."""

    return type(name, (object, ), { val : val for val in class_members })

Foo = new_enum('Foo', 'bar', 'baz', 'baf')

这应该重新创建您作为示例给出的 class,如果您愿意,可以通过将调用的第二个参数更改为 class type(name, bases, dict) 来更改继承。