【问题标题】:Sometimes-readonly property有时只读属性
【发布时间】:2015-05-17 07:19:07
【问题描述】:

我有一个类CameraInterface,它有两个属性:

  • recording(布尔值)
  • recording_quality(具有三个可能值的枚举)

用户可以通过硬件按钮(这是一个树莓派项目)选择recording_quality,但是如果摄像头已经是recording,硬件会闪烁/嗡嗡声/否则表示无法更改recording_quality 在您录制时。

我希望 CameraInterface 对象强制执行此要求。我不希望未来的我能够忘记这个要求,并且在没有先确定recording is False 的情况下不小心尝试设置recording_quality。最 Pythonic 的方式是什么?

我最熟悉 Objective-C,我可能会尝试在外部创建属性 readonly,然后执行以下操作:

- (void)setQuality:(Quality)quality
    successHandler:(void (^)(void))successHandler
    failureHandler:(void (^)(void))failureHandler
{
    if ( self.recording ) {
        if ( failureHandler ) {
            failureHandler();
        }
    }
    else {
        self.quality = quality; // internally writable
        if ( successHandler ) {
            successHandler();
        }
    }
}

更新

澄清我在寻找什么:

  1. 如果可能,我希望避免对控制流使用异常。
  2. 我想将recording_quality 的有时可设置性质构建到CameraInterface 的编程接口中,这样您就不必等到运行时才发现自己做错了什么。
  3. 我已经了解 Python 属性。我只是在寻找使用它们的最佳方法(或替代方法)。

更新 2

Martijn Pieters’s answer 说服我使用运行时异常,并在尝试设置 recording_quality 时使用 try/catch。让我信服的部分原因是尝试在 Python 中实现我的 Objective-C 示例。在这样做的过程中,我意识到它确实更适合静态类型语言。让我信服的部分原因是 Martijn 的答案中链接的答案,它更详细地解释了异常是 Python 控制流中完全正常的一部分。

这是我上面的 Objective-C 示例的 Python 版本。我把它留在这里作为对其他人的警告:

import types

class CameraInterface(object):
    def __init__(self):
        self._recording_quality = 1
        self.recording = False

    @property
    def recording_quality(self):
        return self._recording_quality

    def set_recording_quality(self, quality, success_handler, failure_handler):
        if type(success_handler) is not types.FunctionType:
            raise ValueError("You must pass a valid success handler")
        if type(failure_handler) is not types.FunctionType:
            raise ValueError("You must pass a valid failure handler")

        if self.recording is True:
            # reject the setting, and call the failure handler
            failure_handler()
        else:
            self._recording_quality = quality
            success_handler()

def success():
    print "successfully set"

def failure():
    print "it was recording, so we couldn't set the quality"

camera = CameraInterface()
print "recording quality starting at {0}".format(camera.recording_quality)

camera.set_recording_quality(3, success, failure)
camera.recording = True
camera.set_recording_quality(2, success, failure)

打印出来:

recording quality starting at 1
successfully set
it was recording, so we couldn't set the quality

【问题讨论】:

    标签: python python-2.7 properties setter


    【解决方案1】:

    您可以使用@property,其设置器会引发异常:

    class CameraInterface(object):
        @property
        def recording_quality(self):
            return self._quality
    
        @recording_quality.setter
        def recording_quality(self, quality):
            if self.recording:
                raise CurrentlyRecordingError()
            self._quality = quality
    

    其中CurrentlyRecordingError 是自定义异常;它可能是ValueError 的子类。

    在Python中,使用异常处理特定情况下无法设置的属性的最自然方式。另见Is it a good practice to use try-except-else in Python?

    替代方法是使用Look-before-you-leap 方法,您必须在每次要设置质量值时显式测试摄像机记录状态。这会使 API 变得复杂,并且很容易导致竞争条件,即在您测试状态之后但在您更改质量设置之前相机开始录制。

    除了Ask for Forgiveness;这简化了对象的使用,因为您假设可以进行质量设置并且可以处理异常处理程序中的只读情况。

    从 Python 角度对您的 Objective-C 启发版本的一些进一步说明(除了不使用属性):

    • 当您可以使用isinstance() 时,不要使用type(..) is [not] typeobj
    • 您可以使用callable() function 来测试是否可以调用某些东西,而不是测试types.FunctionType;函数不是唯一的可调用对象。
    • 在 Python 中,您信任 API 的用户;如果他们传入了无法调用的东西,那是他们自己的错,您的对象不需要在这里进行显式类型检查。 Python 是一种供成年人自愿使用的语言。
    • if 声明已经测试了真实性;使用is True 不仅是多余的,而且在与其他比较运算符一起使用时会导致难以调试的错误,因为这些运算符是链式value == someothervalue is True 并不是你想的那样。

    【讨论】:

    • @ZevEisenberg:然后简单地忽略设置属性。但是在 Python 中,异常是处理这种情况的正常方式。与 C# 和 Java 不同,异常用于一直这样的控制流。
    • 然后我将使用异常来处理无效的代码路径,但我想将此行为作为 API 的一部分,因此CameraInterface 的用户甚至不会浪费时间尝试做错事。做错事不应该是可能
    • 在 Python 中经常是 API 的一部分,这就是我提出自定义异常的原因。异常不仅仅针对无效的代码路径。
    • @Zev:核心Python语言一直使用异常进行流控制;生成器有GeneratorExit,迭代器用StopIteration 表示它们已经耗尽,等等。它们是最终的带外信号,不会对其他语言造成性能损失。我为您添加了对核心 Python 开发人员 Raymond Hettinger 的帖子的引用。
    • 你让我信服了。我想这揭示了 Python 和 Objective-C 之间的区别。我尝试构建我的 Objective-C setter 的 Python 版本,但它很快变得笨拙。这绝对是一个针对更强类型语言的解决方案。在 Python 版本中,我在运行时错误检查上浪费了太多代码,以至于我失去了干净界面的所有好处。谢谢你让我看到光!
    【解决方案2】:

    你可以使用properties:

    class CameraInterface(object):
        def __init__(self):
            self.recording = False
            self._quality = None
    
        @property
        def recording_quality(self):
            return self._quality
    
        @recording_quality.setter
        def recording_quality(self, new):
            if self.recording:
                print("Can't change quality while recording")
            else:
                self.quality = new
    

    【讨论】:

    • 我宁愿避免等到运行时才知道我做错了什么。我更新了我的问题。
    【解决方案3】:

    您需要创建与属性同名的 Python 方法,然后使用 @property 装饰器来实现 getter 和 setter 功能:

    class CameraInterface(object):
        def __init__(self):
            self.recording = False
            self._recording_quality = False
    
        def start_recording(self):
            # do stuff
            self.recording = True
    
        @property
        def recording_quality(self):
            return self._recording_quality
    
        @recording_quality.setter
        def recording_quality(self, value):
            if self.recording:
                raise Exception("Whoa! You're already recording!")
            self._recording_quality = value
    

    这使您可以执行以下操作:

    camera = CameraInterface()
    camera.recording_quality = HD
    camera.start_recording()
    camera.recording_quality = SD # Exception!
    

    【讨论】:

      猜你喜欢
      • 2010-11-22
      • 1970-01-01
      • 2014-08-22
      • 2015-02-01
      • 2011-01-28
      • 1970-01-01
      • 2011-01-30
      • 2013-12-12
      相关资源
      最近更新 更多