2

抱歉,标题措辞不当。我希望一个简单的例子能说明问题。这是做我想做的最简单的方法:

class Lemon(object):

    headers = ['ripeness', 'colour', 'juiciness', 'seeds?']

    def to_row(self):
        return [self.ripeness, self.colour, self.juiciness, self.seeds > 0]

def save_lemons(lemonset):
    f = open('lemons.csv', 'w')
    out = csv.writer(f)
    out.write(Lemon.headers)
    for lemon in lemonset:
        out.writerow(lemon.to_row())

这对于这个小例子来说没问题,但我觉得我在 Lemon 课上“重复自己”。在我尝试编写的实际代码中(我要导出的变量数量约为 50 而不是 4,并且 to_row 调用了许多进行大量奇怪计算的私有方法),它变得很尴尬。

当我编写代码来生成一行时,我需要不断地引用“headers”变量以确保我以正确的顺序构建我的列表。如果我想更改正在输出的变量,我需要确保 to_row 和 headers 正在并行更改(正是 DRY 旨在防止的那种事情,对吧?)。

有没有更好的方法可以设计此代码?我一直在玩函数装饰器,但没有任何问题。理想情况下,我应该仍然能够在没有特定柠檬实例的情况下获得标题(即它应该是类变量或类方法),并且我不希望每个变量都有单独的方法。

4

2 回答 2

2

在这种情况下,getattr()是您的朋友:它允许您根据字符串名称获取变量。例如:

def to_row(self):
    return [getattr(self, head) for head in self.headers]

编辑:要正确使用 header seeds?,您需要设置seeds?对象的属性。setattr(self, 'seeds?', self.seeds > 0)在 return 语句的正上方。

于 2012-11-17T00:45:38.093 回答
1

我们可以使用一些元类恶作剧来做到这一点......

在 python 2 中,属性在字典中传递给元类,不保留顺序,我们还需要一个基类,以便我们可以区分应该映射到行中的类属性。在 python3 中,我们可以省去几乎所有这些基本描述符类。

import itertools
import functools
@functools.total_ordering
class DryDescriptor(object):
    _order_gen = itertools.count()
    def __init__(self, alias=None):
        self.alias = alias
        self.order = next(self._order_gen)

    def __lt__(self, other):
        return self.order < other.order

对于我们希望映射到行中的每个属性,我们都需要一个 Python 描述符。插槽是一种无需太多工作即可获取数据描述符的好方法。不过需要注意的是,我们必须手动删除帮助程序实例以使真正的插槽描述符可见。

class slot(DryDescriptor):
    def annotate(self, attr, attrs):
        del attrs[attr]
        self.attr = attr
        slots = attrs.setdefault('__slots__', []).append(attr)

    def annotate_class(self, cls):
        if self.alias is not None:
            setattr(cls, self.alias, getattr(self.attr))

对于计算域,我们可以记忆结果。在没有内存泄漏的情况下,从带注释的实例中记忆是很棘手的,我们需要弱引用。或者,我们可以安排另一个插槽来存储缓存值。这也不是线程安全的,但非常接近。

import weakref
class memo(DryDescriptor):
    _memo = None
    def __call__(self, method):
        self.getter = method
        return self

    def annotate(self, attr, attrs):
        if self.alias is not None:
            attrs[self.alias] = self

    def annotate_class(self, cls): pass

    def __get__(self, instance, owner):
        if instance is None:
            return self
        if self._memo is None:
            self._memo = weakref.WeakKeyDictionary()
        try:
            return self._memo[instance]
        except KeyError:
            return self._memo.setdefault(instance, self.getter(instance))

在元类上,找到我们上面创建的所有描述符,按创建顺序排序,并指示对新创建的类进行注释。这不能正确处理派生类,并且可以使用其他一些便利,例如__init__所有插槽。

class DryMeta(type):
    def __new__(mcls, name, bases, attrs):
        descriptors = sorted((value, key) 
                             for key, value 
                             in attrs.iteritems() 
                             if isinstance(value, DryDescriptor))

        for descriptor, attr in descriptors:
            descriptor.annotate(attr, attrs)

        cls = type.__new__(mcls, name, bases, attrs)
        for descriptor, attr in descriptors:
            descriptor.annotate_class(cls)

        cls._header_descriptors = [getattr(cls, attr) for descriptor, attr in descriptors]
        return cls

最后,我们希望继承一个基类,以便我们可以拥有一个to_row 方法。这只是按顺序调用所有__get__相应描述符的所有 s。

class DryBase(object):
    __metaclass__ = DryMeta

    def to_row(self):
        cls = type(self)
        return [desc.__get__(self, cls) for desc in cls._header_descriptors]

假设所有这些都隐藏起来,看不到,使用此功能的类的定义几乎没有重复。唯一的缺点是为了实用,每个字段都需要一个 python 友好的名称,因此我们有 alias关联的'seeds?'has_seeds

class ADryRow(DryBase):
    __slots__ = ['seeds']

    ripeness = slot()
    colour = slot()
    juiciness = slot()

    @memo(alias='seeds?')
    def has_seeds(self):
        print "Expensive!!!"
        return self.seeds > 0
>>> my_row = ADryRow()
>>> my_row.ripeness = "tart"
>>> my_row.colour = "#8C2"
>>> my_row.juiciness = 0.3479
>>> my_row.seeds = 19
>>>
>>> print my_row.to_row()
Expensive!!!
['tart', '#8C2', 0.3479, True]
>>> print my_row.to_row()
['tart', '#8C2', 0.3479, True]
于 2012-11-17T03:15:33.503 回答