我有一个类 CameraInterface
,它有两个属性:
录音
(一个 bool 值)recording_quality
(具有三个可能值的枚举)
用户可以按下硬件按钮(这是一个 Raspberry Pi 项目)来选择 recording_quality
,但如果相机已经在 recording
,硬件将闪烁/嗡嗡声/否则表明您不能在录制时更改recording_quality
。
我希望 CameraInterface
对象强制执行此要求。我不希望 future 的我能够忘记这个要求,并且不小心尝试设置 recording_quality
而没有首先确保 recording 是 False
。执行此操作最符合 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();
}
}
}
更新
关于我在寻找什么的说明:
- 如果可能,我想避免使用异常来控制流程。
- 我想将
recording_quality
的有时可设置的特性构建到CameraInterface
的编程接口(interface)中,这样您就不必等到运行时才找到表明你做错了什么。 - 我已经了解 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
请您参考如下方法:
您可以使用 @property
其 setter 引发异常:
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 变得复杂,并且很容易导致竞争条件,即相机在您测试状态之后但在更改质量设置之前开始录制。
异常(exception)的是你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
并不代表您认为的意思。