【问题标题】:Python class __init__ layout?Python 类 __init__ 布局?
【发布时间】:2013-04-16 15:17:32
【问题描述】:

在 python 中,写一个__init__ 定义是不是很糟糕:

class someFileType(object):
    def __init__(self, path):
        self.path = path
        self.filename = self.getFilename()
        self.client = self.getClient()
        self.date = self.getDate()
        self.title = self.getTitle()
        self.filetype = self.getFiletype()
    def getFilename(self):
        '''Returns entire file name without extension'''
        filename = os.path.basename(self.path)
        filename = os.path.splitext(filename)
        filename = filename[0]
        return filename
    def getClient(self):
        '''Returns client name associated with file'''
        client = self.filename.split()
        client = client[1] # Assuming filename is formatted "date client - docTitle"
        return client

初始化变量在哪里调用返回字符串的函数?或者它被认为是惰性编码?主要是为了避免我在想引用文件的某些方面时将something.filetype 写成something.getFiletype()

此代码是按客户端、然后按文档类型以及基于文件名中的数据的其他操作将文件分类到文件夹中。

【问题讨论】:

  • 完全没问题。甚至更容易阅读。
  • 对我来说看起来不错,除非您的 getFoo() 函数之一碰巧需要很长时间,在这种情况下,您可能希望将其推迟到实际需要时。
  • 我更喜欢缓存@property。
  • @MartijnPieters 关心发布这是作为答案吗?
  • @freakish 这就是 OP 代码的作用。

标签: python class init


【解决方案1】:

不,我不明白为什么那会是不好的形式。事实上,在创建实例时只计算一次这些值可能是个好主意。

您还可以使用缓存propertys 将计算推迟到需要时:

class SomeFileType(object):
    _filename = None
    _client = None

    def __init__(self, path):
        self.path = path

    @property
    def filename(self):
        if self._filename is None: 
            filename = os.path.basename(self.path)
            self._filename = os.path.splitext(filename)[0]
        return self._filename

    @property
    def client(self):
        '''Returns client name associated with file'''
        if self._client is None:
            client = self.filename.split()
            self._client = client[1] # Assuming filename is formatted "date client - docTitle"
        return self._client

现在,访问somefiletypeinstance.client会根据需要触发self.filename的计算,并缓存它自己的计算结果。

在这种特定情况下,您可能还想将.path 设为属性;一个带有清除缓存值的设置器:

class SomeFileType(object):
    _filename = None
    _client = None

    def __init__(self, path):
        self._path = path

    @property
    def path(self):
        return self._path

    @path.setter
    def path(self, value):
        # clear all private instance attributes
        for key in [k for k in vars(self) if k[0] == '_']:
            delattr(self, key)
        self._path = value

    @property
    def filename(self):
        if self._filename is None: 
            filename = os.path.basename(self.path)
            self._filename = os.path.splitext(filename)[0]
        return self._filename

    @property
    def client(self):
        '''Returns client name associated with file'''
        if self._client is None:
            client = self.filename.split()
            self._client = client[1] # Assuming filename is formatted "date client - docTitle"
        return self._client

因为基于property 的缓存确实增加了一些复杂性开销,所以您需要考虑是否真的值得花时间;对于您的具体简单示例,可能不是。属性的计算成本确实非常低,除非您计划创建大量这些类,否则与必须维护按需缓存属性的心理成本相比,提前计算属性的开销可以忽略不计。

【讨论】:

  • 虽然这段代码确实有效,但我认为如果将 _filename = None 移到 init 方法中而不是在被财产。
  • +1 用于在__init__ 方法之外执行_filename = None。以前从未见过这种特殊的成语。 :)
  • @Aya:它被称为类属性。方法也是真正的类属性;它们只是恰好解析为函数对象而不是另一种类型的类的属性。
  • 这个习语的另一个有趣的事实是你调用了use del inst.member,所以它会再次返回“默认值”。
  • @MartijnPieters 必须...保存...一...纳秒。 ;-) 不,你可能是对的。
【解决方案2】:

您的代码正在做两件不同的事情:

a) 通过将某些计算属性公开为变量而不是函数来简化类 API。

b) 预先计算它们的值。

第一个任务是属性的用途;直接使用会使您的代码更简单,而不是更复杂,并且(同样重要)会使意图更清晰:

class someFileType(object):
    @property
    def filename(self):
        return os.path.basename(self.path)

然后您可以编写var.filename,然后您将动态地根据路径计算文件名。

@Martijn 的解决方案添加了缓存,它还负责 b 部分(预计算)。至少在您的示例中,计算很便宜,所以我认为这样做没有任何好处。

相反,缓存或预计算会引发一致性问题。考虑以下 sn-p:

something = someFileType("/home/me/document.txt")
print something.filename    # prints `document`
...
something.path = "/home/me/document-v2.txt"
print something.filename   # STILL prints `document` if you cache values

最后一条语句应该打印什么?如果你缓存你的计算,你仍然会得到document而不是document-v2除非你确定没有人会尝试改变基本变量的值,您需要避免缓存,或采取措施确保一致性。最简单的方法是禁止修改path——属性之一是designed to do.

结论:使用属性来简化你的界面。不要缓存计算,除非出于性能原因需要。如果您缓存,请采取措施确保一致性,例如通过将基础值设为只读。

附言。这些问题类似于数据库规范化(非规范化设计会引发一致性问题),但在 python 中,您有更多资源来保持同步。

【讨论】:

  • 请注意:如果类编写正确,那么对 something.path 的赋值将使缓存的文件名无效,并在下次请求时触发重新计算。
  • 对,这是完全禁止修改的替代方法。但它需要为path 定义一个设置器(这将使所有计算值归零)或(对于较重的应用程序)设置标志或时间戳并在每次访问计算值时检查它。更重要的是,它需要时间、思考和调试才能使其正确。 “如果写得好”是神奇的词。
  • 缓存值正常。
  • 他们会(或者至少他们应该),我和你在一起。但是@Martijn 的很多赞成的答案没有。 (我知道这是示例代码,但底线是“这是一个很好的方法”)。
  • 同意。 “获取一次,永远返回”学派有很多用途(我想到了一些单例模式),但它们可能非常危险。
猜你喜欢
  • 2019-12-03
  • 1970-01-01
  • 1970-01-01
  • 2011-09-26
  • 1970-01-01
  • 2013-10-18
  • 2020-09-23
  • 2021-06-10
  • 1970-01-01
相关资源
最近更新 更多