OO设计:一个对象,可以在访问标题名时导出到“行”,而不需要重复mys

2024-09-28 23:36:48 发布

您现在位置:Python中文网/ 问答频道 /正文

对不起,标题写得不好。我希望一个简单的例子能说明这一点。下面是我想做的最简单的方法:

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())

对于这个小例子来说,这很管用,但我觉得我在柠檬课上“重复我自己”。而在我试图编写的实际代码中(我要导出的变量数量是~50而不是4个,并且where to\u row调用了许多执行一系列奇怪计算的私有方法),这就变得很尴尬了。你知道吗

在编写生成行的代码时,我需要不断引用“headers”变量,以确保以正确的顺序构建列表。如果我想更改输出的变量,我需要确保行和头是并行更改的(这正是DRY要防止的,对吧?)。你知道吗

有没有更好的方法来设计这个代码?我一直在玩函数装饰,但没有什么坚持。理想情况下,我应该仍然能够在没有特定lemon实例的情况下获取标题(即,它应该是一个类变量或类方法),并且我不希望每个变量都有一个单独的方法。你知道吗


Tags: to方法代码self标题out例子headers
2条回答

我们可以用元类shenanegans来做这个。。。你知道吗

在Python2中,属性在dict中传递给元类,而不是 为了保持顺序,我们还需要一个基类来处理 区分应映射到行中的类属性。在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描述符来描述我们希望映射到 行。slot是一种很好的获取数据描述符的方法,不需要做很多工作。一个 不过,需要注意的是,我们必须手动删除helper实例才能使 实插槽描述符可见。你知道吗

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))

对于计算字段,我们可以记忆结果。注释的回忆录 如果没有内存泄漏,实例很棘手,我们需要weakref。或者,我们 可以安排另一个插槽来存储缓存的值。这也不是很线程安全,但非常接近。你知道吗

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__的 描述符,按顺序。你知道吗

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]

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

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

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

相关问题 更多 >