--When I was practicing writing code with DDD, I tried and errored the type check method of ValueObject, so a memo at that time --As an article practice for Qiita (first post).
--Since the processing at the time of initialization is applied by __post_init__ ()
, type check is executed here.
--Type check with is instance
--Get the expected value type from self.__annotations__
--Convert instance to dict with dataclasses.asdict (self)
. Multiply this by is instance.
--init () is automatically generated.
--You don't need to put an argument in __init __ ()
and do self.hoge = arg_hoge
.
--Suitable for creating ValueObjects.
class Users:
def __init__(self, user_name: str, user_id: int):
self.user_name = user_name
self.user_id = user_id
class User:
user_name: str
user_id: int
It's better to write in dataclass!
This is the main subject.
It's typed with ʻuser_name: str or ʻuser_id: int
, and it looks like it's type-checked, but it's actually a normal annotation.
Even though it is specified as str type, it is entered as int type.
import dataclasses
@dataclasses.dataclass(frozen=True)
class User:
user_name: str
user_id: int
c = User(user_name='Fletchinder', user_id=1)
print(f'{c.user_id}:{c.user_name}')
c_fail = User(user_name=2, user_id='Talonflame')
print(f'{c_fail.user_id}:{c_fail.user_name}')
> python .\dataclass test.py
1:Fletchinder
Talonflame:2
Anything can be put in as above.
__post_init__ ()
By defining the __post_init__ ()
function in the dataclass as shown below, you can write the processing at the time of initialization.
Let's type check here using ʻis instance`
@dataclasses.dataclass(frozen=True)
class User:
user_name: str
user_id: int
def __post_init__(self):
if not isinstance(self.user_name, str):
raise Exception
if not isinstance(self.user_id, int):
raise Exception
> python .\dataclass test.py
1:Fletchinder
Traceback (most recent call last):
File ".\dataclass test.py", line 17, in <module>
c_fail = User(user_name=2, user_id='Talonflame')
File "<string>", line 4, in __init__
File ".\dataclass test.py", line 10, in __post_init__
raise Exception
Exception
As expected, I was able to make an exception!
In the examples so far, there were two, ʻuser_name and ʻuser_id
, but it is difficult if there are many.
Therefore, I want to limit the number of type checks written in __post_init__
to the number of variables.
That's why I wrote the following.
@dataclasses.dataclass(frozen=True)
class User:
user_name: str
user_id: int
def __post_init__(self):
# 1.Convert User instance to dict type with asdict
user_dict = dataclasses.asdict(self)
# 2. self.__annotations__Get the expected value type from
# self.__annotations__Contains the name of the argument and the type it specifies as a dict.
#From now on, get the type of the expected value and check the type with isinstance.
for user_arg_name, user_arg_expected_type in self.__annotations__.items():
# 3.is instance execution
#From the User converted to dict type, specify the target variable with the Key of the annotation and execute it.
if not isinstance(user_dict[user_arg_name], user_arg_expected_type):
print(f'{user_arg_name} is not ok')
raise Exception
else:
print(f'{user_arg_name} is ok')
I put in the details in the comments.
Get all the arguments (variables) that the instance has with ʻasdict, get the expected value type with
self.__ annotations__, and multiply by ʻis instance
.
Excluding comment out and print
, you can write in about 4 or 5 lines
> python .\dataclass test.py
user_name is ok
user_id is ok
1:Fletchinder
user_name is not ok
Traceback (most recent call last):
File ".\dataclass test.py", line 21, in <module>
c_fail = User(user_name=2, user_id='Talonflame')
File "<string>", line 4, in __init__
File ".\dataclass test.py", line 13, in __post_init__
raise Exception
Exception
If you specify the type with typing etc., this method will not work.
The following is the one in which the argument type is specified by List [int]
without comment.
import dataclasses
from typing import List
@dataclasses.dataclass(frozen=True)
class User:
user_name: str
user_id: int
status_list: List[int]
def __post_init__(self):
user_dict = dataclasses.asdict(self)
for user_arg_name, user_arg_expected_type in self.__annotations__.items():
if not isinstance(user_dict[user_arg_name], user_arg_expected_type):
print(f'{user_arg_name} is not ok')
raise Exception
else:
print(f'{user_arg_name} is ok')
status_list=[50,51]
c = User(user_name='Fletchinder', user_id=1, status_list=status_list)
print(f'{c.user_id}:{c.user_name}')
c_fail = User(user_name=2, user_id='Talonflame', status_list=status_list)
print(f'{c_fail.user_id}:{c_fail.user_name}')
I'm getting an error in the list as shown below.
> python .\dataclass test.py
user_name is ok
user_id is ok
Traceback (most recent call last):
File ".\dataclass test.py", line 27, in <module>
c = User(user_name='Fletchinder', user_id=1, status_list=status_list)
File "<string>", line 5, in __init__
File ".\dataclass test.py", line 19, in __post_init__
if not isinstance(user_dict[user_arg_name], user_arg_expected_type):
File "C:\Users\proje\AppData\Local\Programs\Python\Python37\lib\typing.py", line 708, in __instancecheck__
return self.__subclasscheck__(type(obj))
File "C:\Users\proje\AppData\Local\Programs\Python\Python37\lib\typing.py", line 716, in __subclasscheck__
raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks
As you can see by doing the following, List [int] is a type for specifying the type, so it is not the actual list type.
>>> from typing import List
>>> print(type(List[dir]))
<class 'typing._GenericAlias'>
It seems that it is necessary to convert at the time of type check.
Type checking using __post_init__
seems fine.
However, if you are using typing
when specifying the type, it seems that you need to devise another way to check it by turning it with a for statement.
It may be possible to check from the outside instead of putting it in the code with mypy
etc.
(If the environment is solid, such as automatic confirmation at the time of push, this is also available)
--Python Documentation contents dataclasses --- Dataclasses
Recommended Posts