【问题标题】:Exclude some attributes from __str__ representation of a dataclass从数据类的 __str__ 表示中排除某些属性
【发布时间】:2021-10-13 18:20:39
【问题描述】:

我们有这个课程:

from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Dict

@dataclass
class BoardStaff:
    date: str = datetime.now()
    fullname: str
    address: str

    ## attributes to be excluded in __str__:
    degree: str
    rank: int = 10
    badges: bool = False
    cases_dict: Dict[str, str] = field(default_factory=dict)
    cases_list: List[str] = field(default_factory=list)

Emp = BoardStaff('Jack London', address='Unknown', degree='MA')

由于BoardStaff 是一个数据类,因此可以轻松地通过print(Emp) 接收:
BoardStaff(fullname='Jack London', address='Unknown', degree='MA', rank=10, badges=False, cases={}, date=datetime.datetime(2021, 8, 10, 11, 36, 50, 693428))

但是,我希望从表示中排除一些属性(即最后 5 个),所以我必须定义 __str__ 方法并手动排除一些属性,如下所示:

    def __str__(self):
        str_info = {
            k: v
            for k, v in self.__dict__.items()
            if k not in ['degree', 'rank', 'other'] and v
        }
        return str(str_info)

但是有没有更好的排除方法,比如在定义属性时使用一些参数?

【问题讨论】:

  • 有些人喜欢使用前导下划线来表示以这种方式命名的变量不应该被类的用户直接访问。您可以做的是使用此“约定”来指示以这种方式命名的变量不包含在 str dunder 函数中。因此,在您的字典理解中,您可以检查属性的名称以查看它是否以“_”开头并忽略/排除它
  • @DarkKnight 你是对的,但问题是这些属性/变量仅在 str 中被排除,我将在其他方法中实际使用它们。跨度>
  • @nino 如果您声明一个类变量,例如,_a,那么您只需将其称为 self._a 使用这样的名称没有什么特别之处 - 这只是一个约定。不过,使用前导双下划线是另一回事
  • @DarkKnight 我同意。还。您的方式似乎比为每个属性分配repr=False 要干净得多。但我必须在两者之间做出决定,因为:1)初始属性的数量相当多(15),2)它们在整个脚本中被重复使用。

标签: python class attributes python-dataclasses


【解决方案1】:

明显的解决方案

只需使用参数repr=False 将您的属性定义为fields

from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Dict

@dataclass
class BoardStaff:
    date: str = datetime.now()
    fullname: str
    address: str

    ## attributes to be excluded in __str__:
    degree: str = field(repr=False)
    rank: int = field(default=10, repr=False)
    badges: bool = field(default=False, repr=False)
    cases_dict: Dict[str, str] = field(default_factory=dict, repr=False)
    cases_list: List[str] = field(default_factory=list, repr=False)

Emp = BoardStaff('Jack London', address='Unknown', degree='MA')

正如其他人在 cmets 中所建议的那样,这可以很好地与将属性标记为“私有”一起使用,因为它们的名称以前导下划线开头。

更高级的解决方案

如果您正在寻找一种更通用的解决方案,它不涉及使用repr=False 定义这么多fields,您可以这样做。它与您自己想出的解决方案非常相似,但它创建的__repr__ 更类似于通常的dataclass __repr__

from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Dict
    
@dataclass
class BoardStaff:
    fullname: str
    address: str
    degree: str
    date: str = datetime.now()
    rank: int = 10
    badges: bool = False
    cases_dict: Dict[str, str] = field(default_factory=dict)
    cases_list: List[str] = field(default_factory=list)

    def __repr__(self):
        dict_repr = ', '.join(
            f'{k}={v!r}'
            for k, v in filter(
                lambda item: item[0] in {'fullname', 'address', 'date'},
                self.__dict__.items()
            )
        )

        return f'{self.__class__.__name__}({dict_repr})'

Emp = BoardStaff('Jack London', address='Unknown', degree='MA')
print(Emp)

(请注意,我不得不稍微重新排序您的字段,因为在没有默认值的参数之前有默认参数参数会引发错误。)

如果您不想将您的 __repr__ 字段硬编码到您的 __repr__ 方法中,您可以将您的非 __repr__ 字段标记为私有属性,正如 @DarkKnight 在 cmets 中所建议的那样,并使用它作为__repr__ 方法的信号:

from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Dict
    
@dataclass
class BoardStaff:
    fullname: str
    address: str
    _degree: str
    date: str = datetime.now()
    _rank: int = 10
    _badges: bool = False
    _cases_dict: Dict[str, str] = field(default_factory=dict)
    _cases_list: List[str] = field(default_factory=list)

    def __repr__(self):
        dict_repr = ', '.join(
            f'{k}={v!r}'
            for k, v in filter(
                lambda item: not item[0].startswith('_'),
                self.__dict__.items()
            )
        )

        return f'{self.__class__.__name__}({dict_repr})'

Emp = BoardStaff('Jack London', address='Unknown', _degree='MA')
print(Emp)

您甚至可以编写自己的装饰器,为您逐个类生成自定义 __repr__ 方法。例如,此装饰器将生成 __repr__ 方法,其中仅包含您传递给装饰器的参数:

from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Dict
from functools import partial

def dataclass_with_repr_fields(
    keys, init=True, eq=True, order=False, 
    unsafe_hash=False, frozen=False, cls=None
):
    if cls is None:
        return partial(
            dataclass_with_repr_fields, keys, init=init, 
            eq=eq, order=order,  unsafe_hash=unsafe_hash,
            frozen=frozen)

    cls = dataclass(
        cls, init=init, repr=False, eq=eq, order=order, 
        unsafe_hash=unsafe_hash, frozen=frozen
    )

    def __repr__(self):
        dict_repr = ', '.join(
            f'{k}={v!r}'
            for k, v in filter(
                lambda item: item[0] in keys,
                self.__dict__.items()
            )
        )

        return f'{self.__class__.__name__}({dict_repr})'

    cls.__repr__ = __repr__
    return cls


@dataclass_with_repr_fields({'fullname', 'address', 'date'})
class BoardStaff:
    fullname: str
    address: str
    degree: str
    date: str = datetime.now()
    rank: int = 10
    badges: bool = False
    cases_dict: Dict[str, str] = field(default_factory=dict)
    cases_list: List[str] = field(default_factory=list)
    

@dataclass_with_repr_fields({'name', 'surname'})
class Manager:
    name: str
    surname: str
    salary: int
    private_medical_details: str

Emp = BoardStaff('Jack London', address='Unknown', degree='MA')
print(Emp)
manager = Manager('John', 'Smith', 600000, 'badly asthmatic')
print(manager)

【讨论】:

    猜你喜欢
    • 2012-11-07
    • 1970-01-01
    • 2013-02-05
    • 2020-07-25
    • 1970-01-01
    • 1970-01-01
    • 2012-03-21
    • 2012-12-28
    • 1970-01-01
    相关资源
    最近更新 更多