【问题标题】:python dynamically set non-instance class attributespython动态设置非实例类属性
【发布时间】:2011-12-13 14:20:02
【问题描述】:

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

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>

我希望能够做到这一点,而无需从类外部调用类(因此它是可移植的),或者无需额外的类/装饰器。这可能吗?

【问题讨论】:

    标签: python attributes metaprogramming


    【解决方案1】:

    从您的示例代码来看,您希望在创建类的同时执行此操作。在这种情况下,假设您使用的是 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,但它是 not guaranteed to work on other Python implementations or future versions of CPython。你不应该修改locals() 返回的字典。 (不过,修改 globals() 没问题。至少它可以保证工作。)
    • +1:太棒了!比我提出的凌乱的元类解决方案要好得多。如果可以的话,我会给它背后的推理(“引用类名称空间”)+10!直到吨的东西。感谢互联网!
    • @DarenThomas:我不会把文档明确禁止的东西称为“伟大”的解决方案。我宁愿称其为碰巧起作用的肮脏黑客。
    • 好吧,文档说这本字典“不应该”被修改,这几乎不是“禁止的”。 (我很确定它是这么说的,因为很多时候,它并没有达到你的预期。)它也不是“偶然”起作用的——它是一个实现细节。肮脏的黑客?是的,我会拥有它。
    • 添加了第二个不那么肮脏和hacky的方法。 :-)
    【解决方案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 以包含你想要的东西。我在这里找到了如何做到这一点:What is a metaclass in Python?

    【讨论】:

    • 这只是个人喜好问题,但我更喜欢使用类方法而不是函数。
    • 这两个(通过更新函数或类方法使用 setattr)都很好,但它们需要在类外调用 - 为了可移植性,我更愿意在类的启动中完成所有操作,如果有可能
    • 哦,好吧,那我想我误解了你。您上面的解决方案工作得很好:代码将在执行类定义时运行。这与设置类变量和方法的机制相同。
    • 但我无法弄清楚设置属性的机制。换句话说,我可以轻松地手动设置 a = 1,但仅使用字典 {'a' : 1} 这变得更加困难,因为 setattr 函数需要对象作为其第一个参数(我无法在其中提供类定义)。我觉得使用 setattr 方法或使用元类是可行的方法,但我想不通。
    • 如果您在模块 mymodule 中运行更新,from mymodule import Foo 将正常工作
    【解决方案3】:

    接受的答案是一个不错的方法。但是,一个缺点是您最终会在 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)
    

    【讨论】:

    • 可爱的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-24
    • 2021-06-17
    • 2014-11-15
    • 2016-03-17
    • 1970-01-01
    相关资源
    最近更新 更多