Skip to main content
 首页 » 编程设计

Pythonic 类和 Python 之禅

2025年05月04日20小虾米

Python之禅说:

“应该有一种——最好只有一种——显而易见的方法来做到这一点。”

假设我想创建一个构建金融交易的类。该类应允许用户构建交易,然后调用 sign() 方法签署交易,为通过 API 调用广播做准备。

该类将具有以下参数:

sender 
recipient 
amount 
signer (private key for signing) 
metadata 
signed_data 

所有这些都是字符串,除了 amount 是一个 int,除了最后两个之外都是必需的:metadata 是一个可选参数,signed_data 在方法 sign() 被调用。

我们希望所有参数在签名发生之前都经过某种验证,这样我们就可以通过向用户提出适当的错误来拒绝格式错误的交易。

使用经典的 Python 类和构造函数,这似乎很简单:

class Transaction: 
    def __init__(self, sender, recipient, amount, signer, metadata=None): 
            self.sender = sender 
            self.recipient = recipient 
            self.amount = amount 
            self.signer = signer 
 
            if metadata: 
                self.metadata = metadata 
 
    def is_valid(self): 
        # check that all required parameters are valid and exist and return True,  
        # otherwise return false 
 
    def sign(self): 
        if self.is_valid(): 
            # sign transaction 
            self.signed_data = "pretend signature" 
        else: 
            # raise InvalidTransactionError 

或具有属性:

class Transaction: 
    def __init__(self, sender, recipient, amount, signer, metadata=None): 
        self._sender = sender 
        self._recipient = recipient 
        self._amount = amount 
        self._signer = signer 
        self._signed_data = None 
 
        if metadata: 
            self._metadata = metadata 
 
    @property 
    def sender(self): 
        return self._sender 
 
    @sender.setter 
    def sender(self, sender): 
        # validate value, raise InvalidParamError if invalid 
        self._sender = sender 
 
    @property 
    def recipient(self): 
        return self._recipient 
 
    @recipient.setter 
    def recipient(self, recipient): 
        # validate value, raise InvalidParamError if invalid 
        self._recipient = recipient 
 
    @property 
    def amount(self): 
        return self._amount 
 
    @amount.setter 
    def amount(self, amount): 
        # validate value, raise InvalidParamError if invalid 
        self._amount = amount 
 
    @property 
    def signer(self): 
        return self._signer 
 
    @signer.setter 
    def signer(self, signer): 
        # validate value, raise InvalidParamError if invalid 
        self._signer = signer 
 
    @property 
    def metadata(self): 
        return self._metadata 
 
    @metadata.setter 
    def metadata(self, metadata): 
        # validate value, raise InvalidParamError if invalid 
        self._metadata = metadata 
 
    @property 
    def signed_data(self): 
        return self._signed_data 
 
    @signed_data.setter 
    def signed_data(self, signed_data): 
        # validate value, raise InvalidParamError if invalid 
        self._signed_data = signed_data 
 
    def is_valid(self): 
        return (self.sender and self.recipient and self.amount and self.signer) 
 
    def sign(self): 
        if self.is_valid(): 
            # sign transaction 
            self.signed_data = "pretend signature" 
        else: 
            # raise InvalidTransactionError 
            print("Invalid Transaction!") 

我们现在可以在设置时验证每个值,所以在我们签名时我们知道我们有有效的参数,is_valid() 方法只需要检查是否已设置所有必需的参数.与在单个 is_valid() 方法中进行所有验证相比,这对我来说感觉更像 Pythonic,但我不确定所有额外的样板代码是否真的值得。

使用数据类:

@dataclass 
class Transaction: 
    sender: str 
    recipient: str 
    amount: int 
    signer: str 
    metadata: str = None 
    signed_data: str = None 
 
    def is_valid(self): 
        # check that all parameters are valid and exist and return True,  
        # otherwise return false 
 
    def sign(self): 
        if self.is_valid(): 
            # sign transaction 
            self.signed_data = "pretend signature" 
        else: 
            # raise InvalidTransactionError 
            print("Invalid Transaction!") 

将此与方法进行比较

1,这还不错。它简洁、干净、可读,并且已经内置了 __init__()、__repr__() 和 __eq__() 方法。另一方面,与Approach相比

2 我们回到通过大量的 is_valid() 方法验证所有输入。

我们可以尝试将属性与数据类一起使用,但这实际上比听起来更难。根据this blog post可以这样做:

@dataclass 
class Transaction: 
    sender: str 
    _sender: field(init=False, repr=False) 
    recipient: str 
    _recipient: field(init=False, repr=False) 
   . . . 
   # properties for all parameters 
 
    def is_valid(self): 
        # if all parameters exist, return True,  
        # otherwise return false 
 
    def sign(self): 
        if self.is_valid(): 
            # sign transaction 
            self.signed_data = "pretend signature" 
        else: 
            # raise InvalidTransactionError 
            print("Invalid Transaction!") 

有没有一种而且只有一种明显的方法可以做到这一点?是否建议将数据类用于此类应用程序?

请您参考如下方法:

作为一般规则,不限于 Python,编写“fails fast”代码是个好主意:也就是说,如果在运行时出现问题,您希望它被检测到并发出信号(例如,通过尽早抛出异常。

特别是在调试上下文中,如果错误是设置了无效值,您希望在设置值时抛出异常,以便堆栈跟踪包括设置无效值的方法。如果在使用该值时抛出异常,则您无法指示代码的哪一部分导致了无效值。

在您的三个示例中,只有第二个示例允许您遵循这一原则。它可能需要更多的样板代码,但与没有有意义的堆栈跟踪的调试相比,编写样板代码很容易并且不会花费太多时间。

顺便说一句,如果您有执行验证的 setter ,那么您也应该从构造函数中调用这些 setter ,否则可能会创建一个初始状态无效的对象。