掌握python的dataclass,让你的代码更简洁优雅
<p><code>dataclass</code>是从<code>Python3.7</code>版本开始,作为标准库中的模块被引入。<br>随着<code>Python</code>版本的不断更新,<code>dataclass</code>也逐步发展和完善,为<code>Python</code>开发者提供了更加便捷的数据类创建和管理方式。</p><p><code>dataclass</code>的主要功能在于帮助我们简化数据类的定义过程。<br>本文总结了几个我平时使用较多<code>dataclass</code>技巧。</p>
<h1 id="1-传统的类定义方式">1. 传统的类定义方式</h1>
<p>首先,从平时量化分析的场景中简化一个关于 <strong>币交易</strong> 的类用来演示。<br>简化之后,这里只保留5个字段,分别是<strong>交易ID</strong>,<strong>交易对</strong>,<strong>价格</strong>,<strong>是否成功</strong>和<strong>参与交易的地址列表</strong>。</p>
<pre><code class="language-python">class CoinTrans:
def __init__(
self,
id: str,
symbol: str,
price: float,
is_success: bool,
addrs: list,
) -> None:
self.id = id
self.symbol = symbol
self.price = price
self.addrs = addrs
self.is_success = is_success
</code></pre>
<p><code>Python</code>传统定义类的方式,如上通过<code>__init__</code>函数来初始化对象的各个属性。</p>
<p>通过这个类构造对象并打印:</p>
<pre><code class="language-python">if __name__ == "__main__":
coin_trans = CoinTrans("id01", "BTC/USDT", "71000", True, ["0x1111", "0x2222"])
print(coin_trans)
</code></pre>
<p>运行结果:</p>
<pre><code><__main__.CoinTrans object at 0x0000022A891FADD0>
</code></pre>
<p>这里只是打印出对象的地址,并没有按照我们期望的那样打印对象各个属性的值。</p>
<p>传统的类中,我们如果希望打印出可读的结果,需要自己去实现<code>__str__</code>函数。</p>
<pre><code class="language-python"># 在上面的 CoinTrans 类中添加下面的方法
def __str__(self) -> str:
return f"交易信息:{self.id}, {self.symbol}, {self.price}, {self.addrs}, {self.is_success}"
</code></pre>
<p>再次运行,结果如下:</p>
<pre><code>交易信息:id01, BTC/USDT, 71000, ['0x1111', '0x2222'], True
</code></pre>
<h1 id="2-dataclass装饰器定义类">2. dataclass装饰器定义类</h1>
<p>下面看看使用<code>dataclass</code>装饰器来定义上面同样的类有多简单。</p>
<pre><code class="language-python">from dataclasses import dataclass
@dataclass
class CoinTrans:
id: str
symbol: str
price: float
is_success: bool
addrs: list
</code></pre>
<p>再次运行:</p>
<pre><code class="language-python">if __name__ == "__main__":
coin_trans = CoinTrans("id01", "BTC/USDT", "71000", True, ["0x1111", "0x2222"])
print(coin_trans)
</code></pre>
<p>得到如下结果:</p>
<pre><code>CoinTrans(id='id01', symbol='BTC/USDT', price='71000', is_success=True, addrs=['0x1111', '0x2222'])
</code></pre>
<p>不需要<code>__init__</code>,也不需要<code>__str__</code>,只要通过 <code>@dataclass</code>装饰之后,就可以打印出对象的具体内容。</p>
<h2 id="21-默认值">2.1. 默认值</h2>
<p><code>dataclass</code>装饰器的方式来定义类,设置默认值很简单,直接在定义属性时就可以设置。</p>
<pre><code class="language-python">@dataclass
class CoinTrans:
id: str = "id01"
symbol: str = "BTC/USDT"
price: float = "71000.8"
is_success: bool = True
addrs: list = ["0x1111", "0x2222"]
if __name__ == "__main__":
coin_trans = CoinTrans()
print(coin_trans)
</code></pre>
<p>运行之后发现,在<code>addrs</code>属性那行会报错:</p>
<pre><code>ValueError: mutable default <class 'list'> for field addrs is not allowed: use default_factory
</code></pre>
<p>大概的意思就是,<code>list</code>作为一种可变的类型(引用类型,会有被其他对象意外修改的风险),不能直接作为默认值,需要用工厂方法来产生默认值。<br>其他字符串,数值,布尔类型的数据则没有这个问题。</p>
<p>我们只要定义个函数来产生此默认值即可。</p>
<pre><code class="language-python">def gen_list():
return ["0x1111", "0x2222"]
@dataclass
class CoinTrans:
id: str = "id01"
symbol: str = "BTC/USDT"
price: float = "71000.8"
is_success: bool = True
addrs: list = field(default_factory=gen_list)
if __name__ == "__main__":
coin_trans = CoinTrans()
print(coin_trans)
</code></pre>
<p>再次运行,可以正常执行:</p>
<pre><code>CoinTrans(id='id01', symbol='BTC/USDT', price='71000.8', is_success=True, addrs=['0x1111', '0x2222']
</code></pre>
<h2 id="22-隐藏敏感信息">2.2. 隐藏敏感信息</h2>
<p>我们打印对象信息的时候,有时执行打印其中几个属性的信息,涉及敏感信息的属性不希望打印出来。<br>比如,上面的对象,如果不想打印出<code>is_success</code>和<code>addrs</code>的信息,可以设置<code>repr=False</code>。</p>
<pre><code class="language-python">@dataclass
class CoinTrans:
id: str = "id01"
symbol: str = "BTC/USDT"
price: float = "71000.8"
is_success: bool = field(default=True, repr=False)
addrs: list = field(default_factory=gen_list, repr=False)
</code></pre>
<p>再次运行后显示:</p>
<pre><code>CoinTrans(id='id01', symbol='BTC/USDT', price='71000.8')
</code></pre>
<h2 id="23-只读对象">2.3. 只读对象</h2>
<p>数据分析时,大部分下情况下,原始数据读取之后是不能修改的。<br>这种情况下,我们可以用<code>dataclass</code>的<code>frozen</code>属性来设置数据类只读,防止不小心篡改了数据。</p>
<p>未设置<code>frozen</code>属性之前,可以随意修改对象的属性,比如:</p>
<pre><code class="language-python">if __name__ == "__main__":
coin_trans = CoinTrans()
print(f"修改前: {coin_trans}")
coin_trans.symbol = "ETH/USDT"
print(f"修改后: {coin_trans}")
</code></pre>
<p>运行结果:</p>
<pre><code>修改前: CoinTrans(id='id01', symbol='BTC/USDT', price='71000.8')
修改后: CoinTrans(id='id01', symbol='ETH/USDT', price='71000.8')
</code></pre>
<p>设置<code>frozen</code>属性之后,看看修改属性值会怎么样:</p>
<pre><code class="language-python">@dataclass(frozen=True)
class CoinTrans:
id: str = "id01"
#... 省略 ...
</code></pre>
<p>再次运行,会发现修改属性会触发异常。</p>
<pre><code>修改前: CoinTrans(id='id01', symbol='BTC/USDT', price='71000.8')
Traceback (most recent call last):
File "D:\projects\python\samples\data_classes\main.py", line 66, in <module>
coin_trans.symbol = "ETH/USDT"
^^^^^^^^^^^^^^^^^
File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'symbol'
</code></pre>
<h2 id="24-转化为元组和字典">2.4. 转化为元组和字典</h2>
<p>最后,<code>dataclasses</code>模块还提供了两个函数可以很方便的将<strong>数据类</strong>转换为<strong>元组</strong>和<strong>字典</strong>。<br>这在和其他分析程序交互时非常有用,因为和其他程序交互时,参数一般都用<strong>元组</strong>或者<strong>字典</strong>这种简单通用的结构,<br>而不会直接用自己定义的数据类。</p>
<pre><code class="language-python">from dataclasses import dataclass, field, astuple, asdict
if __name__ == "__main__":
coin_trans = CoinTrans()
print(astuple(coin_trans))
print(asdict(coin_trans))
</code></pre>
<p>运行结果:</p>
<pre><code>('id01', 'BTC/USDT', '71000.8', True, ['0x1111', '0x2222'])
{'id': 'id01', 'symbol': 'BTC/USDT', 'price': '71000.8', 'is_success': True, 'addrs': ['0x1111', '0x2222']}
</code></pre>
<h1 id="3-总结">3. 总结</h1>
<p>在<code>Python</code>中,数据类主要用于存储数据,并通常包含属性和方法来操作这些数据。<br>然而,在定义数据类时,我们通常需要编写一些重复性的代码,如构造函数、属性访问器和字符串表示等。<br><code>dataclass</code>装饰器的出现,使得这些通用方法的生成变得自动化,从而极大地简化了数据类的定义过程。</p>
<p>总的来说,<code>dataclass</code>通过简化数据类的创建和管理过程,提高了开发效率,是我们在数据分析时的一个非常有用的工具。</p><br><br>
来源:https://www.cnblogs.com/wang_yb/p/18077397
頁:
[1]