【问题标题】:programmatically define attributes on class definition以编程方式定义类定义的属性
【发布时间】:2014-02-21 18:06:28
【问题描述】:

我有一些对象为应用程序保存恒定的数据库值,它们看起来像这样:

class TestObject(object):
    """reflects constants in _test_object"""
    _pairs = (
        ( 1       , 'A' ),         
        ( 2       , 'B' ),         
        ( 4       , 'C' ),         
        ( 8       , 'D' ),          
    )
    mapping = setup(_pairs)

mapping 函数创建一个字典,它根据_pairs 为我提供 k,v 和 v,k 查找。它们都是“id,name”的形式。

为了让应用更简单,我也经常将“文本值”设置为属性:

class TestObject(object):
    ...
    ...
    A = 1
    B = 2
    C = 4
    D = 8

虽然这使得使用类非常棒,但重新输入所有内容并使其保持同步有点笨拙。另外,我必须编写单元测试以确保属性与 _pairs 中的正确 id 匹配。

我一直在尝试寻找一种方法来跳过将其写出来,而只是在应用程序启动时使用属性自动填充每个 TestObject 类。

我知道我可以重写该类并让它检查 _pairs/mapping 属性以进行故障转移,但我真的很感兴趣是否可以根据需要以编程方式填充该类。还没有__class__,因为尚未定义该类。

要明确一点,我想做的事情是这样的:

class NotValidPython():
    _pairs = ((1,'A'),)
    for (k,v) in _pairs:
        setattr( __class__, v, k )

【问题讨论】:

    标签: python class


    【解决方案1】:

    使用装饰器:

    def dynamic_attrs(cls):
        for v, k in getattr(cls, '_pairs', []):
            setattr(cls, k, v)
        # you also optionally delete the _pairs attr here using del cls._pairs
        return cls
    
    @dynamic_attrs
    class TestObject(object):
        """reflects constants in _test_object"""
        _pairs = (
            ( 1       , 'A' ),
            ( 2       , 'B' ),
            ( 4       , 'C' ),
            ( 8       , 'D' ),
        )
    

    【讨论】:

    • 哇。杰出的!非常感谢。
    【解决方案2】:

    可以为此使用元类。您可以覆盖元类中的__new__,然后填充属性字典,或者覆盖__init__,然后覆盖属性字典或使用setattr 在类对象本身上设置属性。

    使用__new__

    class Meta(type):
    
        def __new__(meta, clsname, bases, dct):
            dct.update(dict((k, v) for v, k in dct.pop('_pairs')))
            return type.__new__(meta, clsname, bases, dct)
    
    class TestObject(object):
        __metaclass__ = Meta
    
        """reflects constants in _test_object"""
        _pairs = (
            ( 1       , 'A' ),         
            ( 2       , 'B' ),         
            ( 4       , 'C' ),         
            ( 8       , 'D' ),          
        )
    

    使用__init__

    class Meta(type):
    
        def __init__(cls, clsname, bases, dct):
            attrs = dct.pop('_pairs')
            for v, k in attrs:
                setattr(cls, k, v)
    

    【讨论】:

    • 这太棒了,但它不能满足我的需要。如果我有 AnotherTestObject ,其值为 ((1,'F'),(2,'G'),)... 所有数据都在 meta-class 的字典中,所以会有冲突和属性是不是普通班级的本地人。
    • @JonathanVanasco 对不起,我没明白你的意思,碰撞是什么意思?
    • 查看这个要点 - gist.github.com/jvanasco/9141157,它说明了命名空间冲突。因为 K/V 存储在元类而不是类上,所以您只能将Meta 用于单个类。如果另一个使用Meta 的类具有“A”值,它将覆盖所有先前的“A”值;如果另一个类提供 'Z' 值,它将使所有其他类都可以使用 'Z'。
    • @JonathanVanasco 啊!对不起,我的错误在那里。我应该使用__init__ 而不是__new__。我已经更新了我的解决方案并包含了__init____new__ 两个版本。
    【解决方案3】:

    无需使用元类。只需将您的代码放在与类定义相同的模块中,但在类定义之外

    class TestClass():
        _pairs = ((1,'A'),)
    
    for (k, v) in TestClass._pairs:
        setattr(TestClass, v, k)
    

    【讨论】:

    • 这种方法的问题是我需要识别所有适用的类;这就是为什么我想把它整合到类定义中。
    • 在那种情况下,我可能会选择@F.J 的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-12
    • 2015-11-22
    • 2013-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多