Python 动态设置非实例类属性

3

我试图动态地添加类属性,但不是在实例级别。例如,我可以手动执行以下操作:

class Foo(object):
    a = 1
    b = 2
    c = 3

我希望能够做到以下事情:

class Foo(object):
    dct = {'a' : 1, 'b' : 2, 'c' : 3}
    for key, val in dct.items():
        <update the Foo namespace here>

我希望能够在不从类外部调用类的情况下(以便它是可移植的),或者不使用额外的类/装饰器来实现这一点。这种可能吗?
3个回答

5
从你的示例代码来看,你希望在创建类时同时进行此操作。在这种情况下,假设你使用的是CPython,则可以使用 locals()
class Foo(object):
    locals().update(a=1, b=2, c=3)

这是因为在定义类时,locals() 引用的是类命名空间。这是与具体实现相关的行为,可能在 Python 的后续版本或替代实现中不起作用。
下面展示了一个使用类工厂的不那么“脏”的版本。基本思想是通过 type() 构造函数将字典转换为类,然后将其用作新类的基类。为了方便地定义带有最少语法的属性,我使用了 ** 习惯用法来接受属性。
def dicty(*bases, **attrs):
    if not bases:
        bases = (object,)
    return type("<from dict>", bases, attrs)

class Foo(dicty(a=1, b=2, c=3)):
    pass

# if you already have the dict, use unpacking

dct = dict(a=1, b=2, c=3)

class Foo(dicty(**dct)):
    pass

这只是一种调用type()的语法糖。例如,下面这样使用也可以:

 class Foo(type("<none>", (object,), dict(a=1, b=2, c=3))):
     pass

虽然这在当前的CPython上可以工作,但不能保证在其他Python实现或未来版本的CPython上工作。您永远不应该修改locals()返回的字典。(修改globals()是可以的,至少它是有保证可以工作的。) - Sven Marnach
+1:这太棒了!比我提出的混乱元类解决方案好多了。如果可以的话,我会给予+10分的理由(“指向类命名空间”)!今天我学到了很多东西。谢谢互联网! - Daren Thomas
@DarenThomas:我不会把文档明确禁止的东西称为“好”的解决方案。我宁愿称之为肮脏的黑客行为,仅仅是凭借运气而已。 - Sven Marnach
1
好的,文档说这个字典“不应该”被修改,这并不是完全“禁止”。(我很确定之所以这样说是因为大多数情况下它不会按照你的期望工作。)它也不是凭“运气”工作的——这是一些实现细节。肮脏的技巧?是的,我承认。 - kindall
添加了第二种不那么肮脏和hacky的方法。 :-) - kindall
@kindall:我上次的评论主要是为了稍微冷静一下Daren对这个解决方案的热情。而且我认为,如果我没有考虑到未定义行为并且结果恰好是我预期的,这在某种程度上是“偶然的”。 :) - Sven Marnach

2

已接受的答案是一个不错的方法。然而,一个缺点是你最终会得到一个额外的父对象在MRO继承链中,这并不是真正必要的,甚至可能会令人困惑:

>>> Foo.__mro__
(<class '__main__.Foo'>, <class '__main__.<from dict>'>, <class 'object'>)

另一种方法是使用装饰器。像这样:
def dicty(**attrs):
    def decorator(cls):
        vars(cls).update(**attrs)
        return cls
    return decorator

@dicty(**some_class_attr_namespace)
class Foo():
    pass

以此方式,您可以避免继承链中的额外对象。 @decorator 语法只是一种漂亮的说法:
Foo = dicty(a=1, b=2, c=3)(Foo)

可爱的方法。 - kindall

2
你是说像这样的东西吗:
def update(obj, dct):
    for key, val in dct.items():
        obj.setattr(key, val)

那就开始吧。
update(Foo, {'a': 1, 'b': 2, 'c': 3})

这是可行的,因为类也是一个对象 ;)

如果你想把所有内容都移入到类中,可以尝试这个方法:

class Foo(object):
    __metaclass__ = lambda t, p, a: return type(t, p, a['dct'])
    dct = {'a': 1, 'b': 2, 'c': 3}

这将创建一个新类,其中成员是dct,但所有其他属性都不存在 - 因此,您需要更改最后一个参数type以包含所需的内容。我在这里找到了如何做到这一点:Python中的元类是什么?


这是个人口味问题,但我更喜欢使用类方法而不是函数。 - jcollado
但我无法弄清楚设置属性的机制。换句话说,我可以轻松地手动设置a = 1,但仅使用字典{'a':1},这变得更加困难,因为setattr函数需要对象作为其第一个参数(在类定义中我无法提供)。我觉得__setattr__方法或使用元类是正确的方法,但我无法弄清楚。 - gnr
@mlauria:“为了可移植性,我更喜欢在类的启动中完成所有操作”——这与可移植性完全无关。只需在类定义后直接添加对update()的调用即可。 - Sven Marnach
@sven:也许可移植性不是正确的词,我的意思是我希望“from mymodule import Foo”能正常工作(即使使用“from a import b”风格被反对),而无需在模块中运行其他命令。无论如何,你会选择达伦的元类实现还是金德尔的局部变量使用方式? - gnr
1
如果您在模块mymodule中运行更新,那么from mymodule import Foo将能够正常工作。 - Xavier Combelle
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接