将对象与可以表示它们的格式分开

时间:2012-09-20 04:39:04

标签: python design-patterns interface python-3.x software-design

我经常需要读取一个CSV文件并使用它来填充类似于内部表格的结构,并使用适当的对象:

column_types = {'Car Make' : str, 'Year' : int,
  'Quantity' : int, 'Weight' : float}
# read_from_file will call str()/int()/etc., passing it
# the string that it finds in each cell
t = Table.read_from_file('mytable.txt', column_types)
t[0]['Car Model']  # 'Nissan'
t[0]['Year'] # 2010
t[0]['Quantity'] # 40
t[0]['Weight'] # 2105.53

在内置转换不足的情况下,事情变得更加复杂。例如,在某些表格中,数量可以表示为数千。为此,我创建了诸如IntegerFormat

之类的类
class IntegerFormat(int): # this case is really simple, so I subclass built-in 
  def __init__(self, unit):
    self.unit = unit
  # create object from string
  def __call__(self, string):
    value = int(string) * self.unit

column_types = {'Car Make' : str, 'Year' : int,
  'Quantity' : IntegerFormat(1000)}
# no change to Table.read_from_file required
t = Table.read_from_file('mytable.txt', column_types)
t[0]['Quantity'] # 40000; now we know the correct units!

但是当与给定列对应的类不是很简单时,我遇到了问题。例如,类Performance表示一些与性能相关的特性,需要从'300 hp,0-60:mph 5.2 s','540 hp,1/4 mile:8 sec @ 140等字符串创建英里”。特定的字符串模式在整个单个表中都是相同的,事先是已知的,并且我有清晰的语法来描述它。

现在,我可以按照我之前的方法编写:

class PerformanceFormat:
  def __init__(self, pattern):
    # convert pattern into some internal form and store it in self.pattern
    # ...
  def __call__(self, string):
    # process the string to obtain parameters that Performance.__init__ expects
    # create Performance object and return it
    # ...
    return Performance(hp, torque, quarter_mile_time)

但是,PerformanceFormatPerformance紧密结合:如果Performance被修改为考虑到四分之一英里的最终速度(而不仅仅是四分之一英里的时间),{ {1}}也必须重写。

我可以将所有实际构造功能移动到PerformanceFormat,并将Performance限制为仅存储字符串模式。但在这种情况下,当PerformanceFormat需要读取相关单元格时,它不足以拥有Table.read_from_file()实例 - 它如何知道它是需要的PerformanceFormat类实例要创造?

我想我可以这样做:

Performance

这里column_types = {'Car Make' : str, 'Quantity' : IntegerFormat(1000), 'Performance' : (Performance, PerformanceType('hp: * torque: *')} 可以创建一个可以输入PerformanceType的标准表示,大大减少了两者之间的耦合:

Performance

或许更好,class Performance: def __init__(self, string, format): standard_representation = format(string) # ... 可以被动地存储模式,完全消除耦合:

PerformanceType

那很好。但是现在class Performance: def __init__(self, string, format): standard_representation = self.to_standard(string, format) # ... 必须做一些烦人的事情:它需要处理不同的情况,其中列由元组而不是单个可调用来描述。 (具体来说,它需要调用第一个元素,将它从单元格读取的值和元组中的第二个元素都传递给它。)

有更好,更清洁的方法吗?

1 个答案:

答案 0 :(得分:0)

这是在正则表达式中使用命名组的方法的草图,对应于构造函数中的关键字参数:

class RegexFormat:
    def __init__(self, cls, pattern):
        self.cls = cls
        self.regex = re.compile(pattern)

    def __call__(self, string):
        return self.cls(**self.regex.match(string).groupdict())

class Performance:
    def __init__(self, hp=None, torque=None, quarter_mile_time=None)
        #....

column_types = {'Car Make' : str,
  'Quantity' : IntegerFormat(1000),
  'Performance' : RegexFormat(Performance, 
      r'hp: (?P<hp>\d+) torque: (?P<torque>\d+)')}