Reputation: 678
We have this class:
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')
As BoardStaff
is a dataclass, one can easily do print(Emp)
to receive:
BoardStaff(fullname='Jack London', address='Unknown', degree='MA', rank=10, badges=False, cases={}, date=datetime.datetime(2021, 8, 10, 11, 36, 50, 693428))
.
However, I want some attributes (i.e. the last 5 ones) to be excluded from the representation, so I had to define __str__
method and manually exclude some attributes like so:
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)
But is there a better way to do the exclusion, like using some parameters when defining the attributes?
Upvotes: 21
Views: 9703
Reputation: 7569
Obvious solution
Simply define your attributes as fields
with the argument repr=False
:
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')
This works nicely alongside marking attributes as "private" by giving them names starting with leading underscores, as others have suggested in the comments.
More advanced solutions
If you're looking for a more general solution that doesn't involve defining so many field
s with repr=False
, you could do something like this. It's pretty similar to the solution you thought up yourself, but it creates a __repr__
that's more similar to the usual 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)
(N.B. I had to reorder your fields slightly, as having default-argument parameters before parameters with no default will raise an error.)
If you don't want to hardcode your __repr__
fields into your __repr__
methods, you could mark your non-__repr__
fields as private attributes, as was suggested in the comments by @DarkKnight, and use this as a signal for your __repr__
method:
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)
You could even potentially write your own decorator that would generate custom __repr__
methods for you on a class-by-class basis. E.g., this decorator will generate __repr__
methods that will only include the arguments you pass to the decorator:
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)
Upvotes: 32