Skip to main content
 首页 » 编程设计

python之有时只读属性

2025年05月04日27insus

我有一个类 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(); 
        } 
    } 
} 

更新

关于我在寻找什么的说明:

  1. 如果可能,我想避免使用异常来控制流程。
  2. 我想将 recording_quality 的有时可设置的特性构建到 CameraInterface 的编程接口(interface)中,这样您就不必等到运行时才找到表明你做错了什么。
  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 

请您参考如下方法:

您可以使用 @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 并不代表您认为的意思。