ã¯ããã«: `attrs` ãšã¯äœãïŒãªã䟿å©ãªã®ãïŒ ð€
Python ã§ã¯ã©ã¹ãå®çŸ©ããéã__init__
, __repr__
, __eq__
ãšãã£ãç¹æ®ã¡ãœããïŒãã³ããŒã¡ãœããïŒãå®è£
ããã®ã¯ããã°ãã°å®åçã§éå±ãªäœæ¥ã«ãªããã¡ã§ããç¹ã«ãããŒã¿ä¿æãäž»ç®çãšããã¯ã©ã¹ã§ã¯ããããã®ã¡ãœããã®å®è£
ã¯ã»ãšãã©åããã¿ãŒã³ã«ãªããŸãã
ããã§ç»å Žããã®ã attrs
ã©ã€ãã©ãªã§ãïŒ ð attrs
ã¯ãã¯ã©ã¹å®çŸ©ã«ããããã®ãããªãã€ã©ãŒãã¬ãŒãã³ãŒãïŒå®åçãªã³ãŒãïŒãåçã«åæžããããç°¡æœã§ãèªã¿ããããä¿å®ããããã³ãŒããæžãããã®åŒ·åãªããŒã«ãæäŸããŸãã
attrs
ã¯ãHynek Schlawackæ°ã«ãã£ãŠ2015幎ã«éçºãéå§ãããPythonã³ãã¥ããã£ã§åºãåãå
¥ããããŠããŸãããã®åœ±é¿ã¯å€§ãããPython 3.7ã§æšæºã©ã€ãã©ãªãšããŠå°å
¥ããã dataclasses
ã¢ãžã¥ãŒã«ã¯ãattrs
ã«åŒ·ãã€ã³ã¹ãã€ã¢ãããŠããŸãã
attrs
ã䜿ãäž»ãªã¡ãªããã¯ä»¥äžã®éãã§ãïŒ
- ã³ãŒãéã®åæž:
__init__
,__repr__
,__eq__
ãªã©ã®èªåçæã«ãããèšè¿°éãå€§å¹ ã«æžããŸãã - å¯èªæ§ã®åäž: ã¯ã©ã¹ã®å±æ§å®çŸ©ã«éäžã§ããæ¬è³ªçã§ãªãã³ãŒããæžããããã¯ã©ã¹ã®æå³ãæ確ã«ãªããŸãã
- å ç¢æ§ã®åäž: ããªããŒã¿ãã³ã³ããŒã¿ãšãã£ãæ©èœã«ãããäžæ£ãªããŒã¿ãæã€ã€ã³ã¹ã¿ã³ã¹ã®çæãé²ãããããªããŸãã
- è±å¯ãªæ©èœ: æšæºã®
dataclasses
ãããå€ãã®é«åºŠãªæ©èœïŒããªããŒã¿ãã³ã³ããŒã¿ãslots
ã®ç°¡åãªå©çšãªã©ïŒãæäŸããŸãã
ãã®ããã°èšäºã§ã¯ãattrs
ã®åºæ¬çãªäœ¿ãæ¹ããã䟿å©ãªæ©èœããã㊠dataclasses
ãšã®æ¯èŒãŸã§ã詳现ã«è§£èª¬ããŠãããŸãããããattrs
ã®äžçãæ¢æ€ããŸãããïŒð
åºæ¬çãªäœ¿ãæ¹
ãŸã㯠attrs
ã®åºæ¬çãªäœ¿ãæ¹ãèŠãŠãããŸãããã
ã€ã³ã¹ããŒã«
attrs
ã¯æšæºã©ã€ãã©ãªã§ã¯ãªããããpip ã䜿ã£ãŠã€ã³ã¹ããŒã«ããå¿
èŠããããŸãã
$ pip install attrs
泚æ: ããã±ãŒãžå㯠attrs
ã§ãããã€ã³ããŒãããé㯠import attr
ãš s
ãä»ããªãç¹ã«æ³šæããŠãã ãããããã¯æŽå²çãªçµç·¯ã«ãããã®ã§ãã
ã¯ã©ã¹å®çŸ©: @attr.define ãš attr.field
attrs
ã䜿ã£ãŠã¯ã©ã¹ãå®çŸ©ããæãçŸä»£çã§æšå¥šãããæ¹æ³ã¯ã@attr.define
ãã³ã¬ãŒã¿ïŒãŸãã¯ãšã€ãªã¢ã¹ã® @attr.s
ïŒãš attr.field()
é¢æ°ïŒãŸãã¯ãšã€ãªã¢ã¹ã® attr.ib()
ïŒã䜿ãããšã§ãã@attr.define
ãš attr.field
ã¯æ¯èŒçæ°ããAPIã§ãããããæ瀺çã§å°æ¥æ§ãããããããã¡ãã䜿ãããšãæšå¥šãããŸãã
import attr
@attr.define
class Point:
x: float = attr.field()
y: float = attr.field()
# ã€ã³ã¹ã¿ã³ã¹å
p1 = Point(1.0, 2.0)
print(p1) # åºå: Point(x=1.0, y=2.0)
p2 = Point(x=1.0, y=2.0)
print(p1 == p2) # åºå: True
ãã®ã·ã³ãã«ãªã³ãŒãã ãã§ã以äžã®ããšãèªåçã«è¡ãããŸãã
__init__(self, x: float, y: float)
ã¡ãœããã®çæïŒåŒæ°ãåãåããã€ã³ã¹ã¿ã³ã¹å€æ°self.x
ãšself.y
ã«ä»£å ¥ããŸãã__repr__(self)
ã¡ãœããã®çæïŒãããã°ã«äŸ¿å©ãªã人éãèªã¿ããã圢åŒã®æååè¡šçŸïŒäŸ:Point(x=1.0, y=2.0)
ïŒãè¿ããŸãã__eq__(self, other)
,__ne__(self, other)
ã¡ãœããã®çæïŒãã¹ãŠã®å±æ§å€ãçããå Žåã«True
ãè¿ãæ¯èŒããžãã¯ãå®è£ ããŸãã- ãã®ä»ãæ¯èŒã¡ãœãã (
__lt__
,__le__
,__gt__
,__ge__
) ãããã·ã¥ã¡ãœãã (__hash__
) ããèšå®ã«å¿ããŠèªåçæãããŸãïŒããã©ã«ãã§ã¯æ¯èŒã¡ãœããã¯æå¹ãããã·ã¥ã¯eq=True
ãã€frozen=True
ã®å Žåã«çæïŒã
å±æ§ã®å®çŸ©ã«ã¯ attr.field()
ã䜿ããŸããåã¢ãããŒã·ã§ã³ (: float
) ãšçµã¿åãããããšã§ãããåå®å
šãªã³ãŒãã«ãªããŸããattrs
ã¯åã¢ãããŒã·ã§ã³ãèªèããŸãããå®è¡æã®åãã§ãã¯ã¯ããã©ã«ãã§ã¯è¡ããŸããïŒããªããŒã¿ã䜿ãã°å¯èœã§ãïŒã
ããã©ã«ãå€
å±æ§ã«ããã©ã«ãå€ãäžããããšãç°¡åã§ããattr.field()
ã® default
åŒæ°ã䜿çšããŸãã
import attr
from typing import List
@attr.define
class Config:
path: str = attr.field(default="/etc/myapp.conf")
retries: int = attr.field(default=3)
# å¯å€ãªããžã§ã¯ã (ãªã¹ããªã©) ã®ããã©ã«ãå€ã«ã¯ factory ã䜿ã
users: List[str] = attr.field(factory=list) # default=[] ã¯å±éºïŒ
c1 = Config()
print(c1) # åºå: Config(path='/etc/myapp.conf', retries=3, users=[])
c2 = Config(path="/opt/myapp.conf", users=["admin", "guest"])
print(c2) # åºå: Config(path='/opt/myapp.conf', retries=3, users=['admin', 'guest'])
éèŠ: ãªã¹ããèŸæžãªã©ã®å¯å€ãªããžã§ã¯ããããã©ã«ãå€ã«ããå Žåã¯ãdefault=[]
ã default={}
ã§ã¯ãªããfactory=list
ã factory=dict
ã䜿çšããŠãã ãããããã¯ãdefault
ã§æå®ããããªããžã§ã¯ãã¯ã¯ã©ã¹å®çŸ©æã«äžåºŠã ãçæããããã¹ãŠã®ã€ã³ã¹ã¿ã³ã¹ã§å
±æãããŠããŸããããæå³ããªãå¯äœçšãåŒãèµ·ããå¯èœæ§ãããããã§ãã factory
ã¯ã€ã³ã¹ã¿ã³ã¹ãçæããããã³ã«æ°ãããªããžã§ã¯ããçæããŸãã
auto_attribs=True ã§ããã«ç°¡æœã«
åã¢ãããŒã·ã§ã³ãåžžã«äœ¿çšããå Žåã@attr.define(auto_attribs=True)
ã䜿ããšãattr.field()
ã®åŒã³åºããçç¥ã§ããŸãïŒããã©ã«ãå€ãä»ã®ãªãã·ã§ã³ãå¿
èŠãªãå ŽåïŒã
import attr
@attr.define(auto_attribs=True)
class Vector:
x: float
y: float
z: float = 0.0 # ããã©ã«ãå€ãçŽæ¥æžãã
v = Vector(1.0, 2.0)
print(v) # åºå: Vector(x=1.0, y=2.0, z=0.0)
ãã ããããªããŒã¿ãã³ã³ããŒã¿ãªã©ã®é«åºŠãªæ©èœã䜿ãããå Žåã¯ãäŸç¶ãšã㊠attr.field()
ãæ瀺çã«äœ¿ãå¿
èŠããããŸãã
`attrs` ã®äž»èŠæ©èœè©³è§£ âš
attrs
ã®é
åã¯ãåºæ¬çãªãã€ã©ãŒãã¬ãŒãåæžã ãã§ã¯ãããŸãããããå
ç¢ã§æè»ãªã¯ã©ã¹èšèšãå¯èœã«ãããå€ãã®äŸ¿å©ãªæ©èœãæäŸããŠããŸãã
ããªããŒã¿ (Validators) â
ããªããŒã¿ã䜿ããšãå±æ§ã«ä»£å ¥ãããå€ãç¹å®ã®æ¡ä»¶ãæºãããŠãããæ€èšŒã§ããŸããã€ã³ã¹ã¿ã³ã¹çææããåŸããå±æ§ã«å€ãä»£å ¥ããéã«ïŒèšå®ã«ããïŒæ€èšŒãå®è¡ãããŸãã
attr.field()
ã® validator
åŒæ°ã«ããªããŒã·ã§ã³é¢æ°ïŒãŸãã¯é¢æ°ã®ãªã¹ãïŒãæå®ããŸããããªããŒã·ã§ã³é¢æ°ã¯ãinstance
(ã€ã³ã¹ã¿ã³ã¹èªèº«), attribute
(å±æ§ãªããžã§ã¯ã), value
(代å
¥ãããå€) ã®3ã€ã®åŒæ°ãåããŸããå€ãç¡å¹ãªå Žåã¯äŸå€ïŒé垞㯠ValueError
ã TypeError
ïŒãéåºããŸãã
import attr
def is_positive(instance, attribute, value):
"""å€ãæ£ã®æ°ã§ãããæ€èšŒããããªããŒã¿"""
if value <= 0:
raise ValueError(f"{attribute.name} must be positive, but got {value}")
@attr.define
class PositiveNumber:
value: float = attr.field(validator=is_positive)
# ããã¯OK
num1 = PositiveNumber(10.5)
print(num1) # åºå: PositiveNumber(value=10.5)
# ãã㯠ValueError ãéåº
try:
num2 = PositiveNumber(-5.0)
except ValueError as e:
print(e) # åºå: value must be positive, but got -5.0
# çµã¿èŸŒã¿ããªããŒã¿ã®å©çš
from attr import validators
@attr.define
class User:
name: str = attr.field(validator=validators.instance_of(str))
age: int = attr.field(validator=[validators.instance_of(int), is_positive]) # è€æ°ã®ããªããŒã¿
user = User(name="Alice", age=30)
print(user) # åºå: User(name='Alice', age=30)
try:
invalid_user = User(name="Bob", age=-1)
except ValueError as e:
print(e) # åºå: age must be positive, but got -1
try:
invalid_user2 = User(name=123, age=25)
except TypeError as e:
print(e) # åºå: ("'name' must be <class 'str'> (got 123 that is a <class 'int'>).", ...)
attrs
ã«ã¯ validators.instance_of()
, validators.optional()
, validators.in_()
ãªã©ã䟿å©ãªçµã¿èŸŒã¿ããªããŒã¿ãçšæãããŠããŸãã
泚æ: ããã©ã«ãã§ã¯ãããªããŒã¿ã¯ã€ã³ã¹ã¿ã³ã¹åæåæ (__init__
) ã«ã®ã¿å®è¡ãããŸããã€ã³ã¹ã¿ã³ã¹äœæåŸã«å±æ§ãžä»£å
¥ããå Žåã«ãæ€èšŒãè¡ãããå Žåã¯ã@attr.define(on_setattr=attr.setters.validate)
ã®ããã«èšå®ããå¿
èŠããããŸãã
ã³ã³ããŒã¿ (Converters) ð
ã³ã³ããŒã¿ã¯ãå±æ§ã«å€ãä»£å ¥ãããåã«ããã®å€ãèªåçã«å€æããæ©èœã§ããäŸãã°ãæååã§äžããããæ°å€ãæŽæ°åã«å€æããããç¹å®ã®åœ¢åŒã®ããŒã¿ãå éšè¡šçŸã«é©ãã圢ã«å€æãããããã®ã«åœ¹ç«ã¡ãŸãã
attr.field()
ã® converter
åŒæ°ã«å€æé¢æ°ãæå®ããŸããå€æé¢æ°ã¯å€ (value
) ãå¯äžã®åŒæ°ãšããŠåãåããå€æåŸã®å€ãè¿ããŸãã
import attr
import datetime
def to_datetime(value: str | datetime.datetime) -> datetime.datetime:
"""ISO 8601圢åŒã®æååãdatetimeãªããžã§ã¯ãã«å€æãã"""
if isinstance(value, datetime.datetime):
return value
if isinstance(value, str):
try:
return datetime.datetime.fromisoformat(value)
except ValueError:
raise ValueError(f"Invalid datetime format: {value}")
raise TypeError(f"Expected str or datetime, got {type(value)}")
@attr.define
class Event:
name: str = attr.field()
# æååã§åãåã£ãæ°å€ã float ã«å€æ
value: float = attr.field(converter=float)
# ISO 8601 æååãŸã㯠datetime ãªããžã§ã¯ãã datetime ãªããžã§ã¯ãã«æ£èŠå
timestamp: datetime.datetime = attr.field(converter=to_datetime)
# æååã float ã«å€æããã
ev1 = Event(name="Measurement", value="123.45", timestamp="2025-04-05T13:01:00+00:00")
print(ev1)
# åºå: Event(name='Measurement', value=123.45, timestamp=datetime.datetime(2025, 4, 5, 13, 1, tzinfo=datetime.timezone.utc))
print(type(ev1.value)) # åºå: <class 'float'>
print(type(ev1.timestamp)) # åºå: <class 'datetime.datetime'>
# datetime ãªããžã§ã¯ãã¯ãã®ãŸãŸäœ¿ããã
now = datetime.datetime.now(datetime.timezone.utc)
ev2 = Event(name="Log", value=0.0, timestamp=now)
print(ev2.timestamp is now) # åºå: True
# ç¡å¹ãªåœ¢åŒã¯ãšã©ãŒ
try:
Event(name="Error", value="abc", timestamp="invalid-date")
except ValueError as e:
print(e) # åºå: could not convert string to float: 'abc' (value ã®å€æã§ãšã©ãŒ)
except TypeError as e:
print(e) # converter ã TypeError ãåºãå Žå
ã³ã³ããŒã¿ã¯ããªããŒã¿ãããåã«å®è¡ãããŸããããã«ãããå€æåŸã®å€ã«å¯ŸããŠããªããŒã·ã§ã³ãè¡ãããšãã§ããŸãã
泚æ: ããã©ã«ãã§ã¯ãã³ã³ããŒã¿ã¯ã€ã³ã¹ã¿ã³ã¹åæåæãšãå±æ§ãžã®å代å
¥æã®äž¡æ¹ã§å®è¡ãããŸããããã¯ããªããŒã¿ãšã¯ç°ãªãæåã§ãããã®æåãå€æŽãããå Žåã¯ã@attr.define(on_setattr=...)
ã䜿ã£ãŠå¶åŸ¡ã§ããŸãïŒäŸ: on_setattr=[]
ã§ä»£å
¥æã®ã³ã³ããŒã¿å®è¡ãç¡å¹åïŒã
äžå€ãªããžã§ã¯ã (Frozen Instances) ð§
@attr.define(frozen=True)
ã䜿ããšãã€ã³ã¹ã¿ã³ã¹çæåŸã«å±æ§å€ãå€æŽã§ããªãäžå€ (immutable) ãªã¯ã©ã¹ãäœæã§ããŸããäžå€ãªããžã§ã¯ãã¯ãç¶æ
ãå€åããªãããšãä¿èšŒããããããèŸæžã®ããŒãšããŠäœ¿ãããããã«ãã¹ã¬ããç°å¢ã§å®å
šã«æ±ãããããã¡ãªããããããŸãã
import attr
@attr.define(frozen=True)
class ImmutablePoint:
x: float = attr.field()
y: float = attr.field()
p = ImmutablePoint(1.0, 2.0)
print(p) # åºå: ImmutablePoint(x=1.0, y=2.0)
# å±æ§ãå€æŽããããšãããšãšã©ãŒã«ãªã
try:
p.x = 5.0
except attr.exceptions.FrozenInstanceError as e:
print(e) # åºå: ("can't set attribute 'x'", ...)
# äžå€ãªã®ã§ããã·ã¥å¯èœã§ãããèŸæžã®ããŒãã»ããã®èŠçŽ ã«ã§ãã
point_set = {p, ImmutablePoint(3.0, 4.0)}
print(point_set) # åºåäŸ: {ImmutablePoint(x=1.0, y=2.0), ImmutablePoint(x=3.0, y=4.0)}
äžå€ãªããžã§ã¯ãã®äžéšãå€æŽãããå Žåã¯ãattrs.evolve()
é¢æ°ã䜿ããšãæå®ããå±æ§ã ããç°ãªãæ°ããã€ã³ã¹ã¿ã³ã¹ãå¹ççã«äœæã§ããŸãã
p_new = attr.evolve(p, x=10.0)
print(p_new) # åºå: ImmutablePoint(x=10.0, y=2.0)
print(p) # åºå: ImmutablePoint(x=1.0, y=2.0) (å
ã®ã€ã³ã¹ã¿ã³ã¹ã¯å€æŽãããªã)
Slots ã«ããæé©å ð
@attr.define(slots=True)
ãæå®ãããšãPython ã® __slots__ æ©èœãå©çšããã¯ã©ã¹ãçæãããŸãã__slots__
ã䜿ããšã以äžã®ã¡ãªããããããŸãã
- ã¡ã¢ãªäœ¿çšéã®åæž: ã€ã³ã¹ã¿ã³ã¹ããšã«
__dict__
(å±æ§ãä¿æããèŸæž) ãæããªããªããããã¡ã¢ãªãããããªã³ããå°ãããªããŸãã倧éã®ã€ã³ã¹ã¿ã³ã¹ãçæããå Žåã«ç¹ã«æå¹ã§ãã - å±æ§ã¢ã¯ã»ã¹é床ã®åäž: å±æ§ãžã®ã¢ã¯ã»ã¹ãè¥å¹²éããªãããšããããŸãã
import attr
import sys
@attr.define(slots=True) # slots=True ãæå®
class SlottedPoint:
x: float = attr.field()
y: float = attr.field()
@attr.define(slots=False) # ããã©ã«ã (slots=False)
class NormalPoint:
x: float = attr.field()
y: float = attr.field()
sp = SlottedPoint(1.0, 2.0)
np = NormalPoint(1.0, 2.0)
# ã¡ã¢ãªäœ¿çšéã®æ¯èŒ (ç°¡æçãªæž¬å®)
print(f"SlottedPoint size: {sys.getsizeof(sp)}")
print(f"NormalPoint size: {sys.getsizeof(np)} + dict: {sys.getsizeof(np.__dict__)}")
# åºåäŸ (ç°å¢ã«ããå€å):
# SlottedPoint size: 48
# NormalPoint size: 56 + dict: 104
# slots=True ã®å Žåã__dict__ ã¯ååšããªã
try:
print(sp.__dict__)
except AttributeError as e:
print(e) # åºå: 'SlottedPoint' object has no attribute '__dict__'
# slots=True ã®å Žåãå®çŸ©ãããŠããªãå±æ§ã®è¿œå ã¯ã§ããªã
try:
sp.z = 3.0
except AttributeError as e:
print(e) # åºå: 'SlottedPoint' object has no attribute 'z'
np.z = 3.0 # éåžžã®ã¯ã©ã¹ã§ã¯å¯èœ
print(np.z) # åºå: 3.0
slots=True
ã®æ³šæç¹:
- ã¯ã©ã¹å®çŸ©æã«å®£èšãããŠããªãå±æ§ãåŸããè¿œå ã§ããªããªããŸããããã¯æå³ããªãå±æ§è¿œå ãé²ãå¹æããããŸãããåçãªå±æ§è¿œå ãå¿ èŠãªå Žåã«ã¯äœ¿ããŸããã
- å€éç¶æ¿ãªã©ã§
__slots__
ã®æ±ãã«æ³šæãå¿ èŠãªå ŽåããããŸãã - äžéšã®ã©ã€ãã©ãªïŒç¹ã«å€ããã®ïŒãšã®äºææ§ã«åé¡ãçããå¯èœæ§ããããŸãã
äžè¬çã«ã¯ãããã©ãŒãã³ã¹ãã¡ã¢ãªå¹çãéèŠãªå Žé¢ã§ã¯ slots=True
ã®å©çšãæ€èšãã䟡å€ããããŸãã
ãã®ä»ã®äŸ¿å©ãªæ©èœ
kw_only=True
:@attr.define(kw_only=True)
ãšãããšããã¹ãŠã®å±æ§ãããŒã¯ãŒãå°çšåŒæ°ãšãªããã€ã³ã¹ã¿ã³ã¹åæã«å¿ ãå±æ§åãæå®ããå¿ èŠããããŸã (MyClass(attr1=value1, attr2=value2)
)ãåŒæ°ã®æå³ãæ確ã«ãªããé åºã®ééããé²ããŸããinit=False
,repr=False
ãªã©:attr.field()
ã@attr.define()
ã®åŒæ°ã§ãç¹å®ã®ç¹æ®ã¡ãœããã®èªåçæãæå¶ã§ããŸããäŸãã°attr.field(init=False)
ãšããå±æ§ã¯__init__
ã®åŒæ°ã«å«ãŸããªããªããŸãã- ã¡ã¿ããŒã¿ (
metadata
):attr.field(metadata={'key': 'value'})
ã®ããã«ãå±æ§ã«è¿œå æ å ±ãä»äžã§ããŸããã·ãªã¢ã©ã€ãºã©ã€ãã©ãªãªã©ããã®ã¡ã¿ããŒã¿ãå©çšããããšããããŸãã
`attrs` vs `dataclasses` ð¥
Python 3.7 以éãæšæºã©ã€ãã©ãªã« dataclasses
ã¢ãžã¥ãŒã«ãå°å
¥ãããŸããããã㯠attrs
ã«è§ŠçºãããŠäœããããã®ã§ãåæ§ã«ã¯ã©ã¹ã®ãã€ã©ãŒãã¬ãŒãã³ãŒããåæžããç®çãæã£ãŠããŸããã§ã¯ãã©ã¡ãã䜿ãã¹ãã§ããããïŒ ð€
æŽå²çèæ¯ãšç®ç
attrs
: 2015幎ã«ç»å ŽãããµãŒãããŒãã£ã©ã€ãã©ãªãè±å¯ãªæ©èœãšæè»æ§ãæã¡ãé·å¹Žã«ãããå€ãã®ãããžã§ã¯ãã§äœ¿çšãããŠããŸãããdataclasses
: Python 3.7 (2018幎ãªãªãŒã¹) ã§æšæºã©ã€ãã©ãªå ¥ããattrs
ã®äž»èŠãªã¢ã€ãã¢ãåãå ¥ãã€ã€ãããã·ã³ãã«ã§æšæºçãªæ©èœã»ãããæäŸããããšãç®çãšããŠããŸãã
æ©èœæ¯èŒ
æ©èœ | attrs | dataclasses | åè |
---|---|---|---|
åºæ¬çãªã¡ãœããçæ (__init__ , __repr__ , __eq__ ) | â | â | åºæ¬çãªæ©èœã¯äž¡è ãšãæäŸ |
åã¢ãããŒã·ã§ã³é£æº | â | â | äž¡è ãšãåãã³ããäž»èŠãªå®çŸ©æ¹æ³ãšãã |
ããã©ã«ãå€ (default , factory ) | â | â | dataclasses ã§ã¯ default_factory |
äžå€ãªããžã§ã¯ã (frozen=True ) | â | â | |
é åºä»ãã¡ãœããçæ (order=True ) | â | â | __lt__ , __le__ , __gt__ , __ge__ |
ããªããŒã¿ (Validators) | â (匷å) | â | attrs ã®å€§ããªå©ç¹ã®äžã€ |
ã³ã³ããŒã¿ (Converters) | â | â | attrs ã®å€§ããªå©ç¹ã®äžã€ |
__slots__ ã®ç°¡åãªå©çš | â
(slots=True ) | â ïž (Python 3.10以é㧠slots=True å¯èœ) | attrs ã¯å€ã Python ããŒãžã§ã³ã§ããµããŒã |
ããŒã¯ãŒãå°çšåŒæ° (kw_only ) | â | â (Python 3.10以é) | |
å±æ§ããšã®ã¡ãœããçæå¶åŸ¡ | â (æè») | â | attrs ã®æ¹ããã现ããå¶åŸ¡ãå¯èœ |
äŸåé¢ä¿ | å¿ èŠ (ãµãŒãããŒãã£) | â (æšæºã©ã€ãã©ãª) | |
Python ããŒãžã§ã³äºææ§ | â (åºã) | â ïž (Python 3.7+) | attrs 㯠Python 2.7 ã PyPy ããµããŒã |
ããã©ãŒãã³ã¹ | â¡ïž (äžè¬ã«é«é) | â¡ïž (é«é) | äž¡è
ãšã CPython ã®å®è£
ã«ãã£ãŠã¯ attrs ããããã«éãå Žåããããç¹ã« slots=True 䜿çšæã |
ããã©ãŒãã³ã¹
attrs
ãš dataclasses
ã¯ã©ã¡ãããã¯ã©ã¹å®çŸ©æã«ã³ãŒãçæãè¡ããããå®è¡æã®ãªãŒããŒãããã¯éåžžã«å°ããã§ãããã³ãããŒã¯ã«ãã£ãŠã¯ãç¹ã« slots=True
ã䜿çšããå Žåã« attrs
ããããã«é«éã§ãããšããå ±åããããŸãããå€ãã®å Žåããã®å·®ã¯ç¡èŠã§ããã¬ãã«ã§ãã
Pydantic ã®ãããªããªããŒã·ã§ã³ãã·ãªã¢ã©ã€ãºãäž»ç®çãšããã©ã€ãã©ãªãšæ¯èŒãããšãã€ã³ã¹ã¿ã³ã¹åã®é床ãªã©ã§ã¯ attrs
ã dataclasses
ã®æ¹ãäžè¬çã«é«éã§ãã
䜿ãåãã®æé
ã©ã¡ããéžæãããã¯ããããžã§ã¯ãã®èŠä»¶ãç¶æ³ã«ãã£ãŠç°ãªããŸãã
`dataclasses` ãéžã¶å Žå:
- ãããžã§ã¯ãã®äŸåé¢ä¿ãæå°éã«æãããå ŽåïŒæšæºã©ã€ãã©ãªã®ã¿äœ¿çšïŒã
- Python 3.7 以éã®äœ¿çšãåæãšãããŠããå Žåã
- ããªããŒã¿ãã³ã³ããŒã¿ã®ãããªé«åºŠãªæ©èœãäžèŠãªãã·ã³ãã«ãªããŒã¿ã¯ã©ã¹ãäœæãããå Žåã
- æšæºã©ã€ãã©ãªã§ããããšã«ããããŒã«é£æºïŒéç解æããŒã«ãªã©ïŒã®å®å®æ§ãéèŠããå Žåã
`attrs` ãéžã¶å Žå:
- ããªããŒã¿ãã³ã³ããŒã¿æ©èœãå¿ é ã®å Žåã
slots=True
ã Python 3.9 以åã®ããŒãžã§ã³ã§ãç°¡åã«å©çšãããå Žåã- ãã现ããã«ã¹ã¿ãã€ãºãæè»æ§ãå¿ èŠãªå Žåã
- Python 3.7 ããåã®ããŒãžã§ã³ïŒPython 2.7 ã PyPy ãå«ãïŒããµããŒãããå¿ èŠãããå Žåã
- ãµãŒãããŒãã£ã©ã€ãã©ãªãžã®äŸåã蚱容ãããå Žåã
åºæ¬çãªæ©èœã¯å
±éããŠãããããdataclasses
ãã attrs
ãžã®ç§»è¡ïŒãŸãã¯ãã®éïŒã¯æ¯èŒç容æã§ãããããžã§ã¯ãã®åæ段éã§ã¯ dataclasses
ã䜿ãå§ããåŸããããé«åºŠãªæ©èœãå¿
èŠã«ãªã£ãå Žåã« attrs
ã«ç§»è¡ãããšããã¢ãããŒããèããããŸãã
çºå±çãªäœ¿ãæ¹ ð§âð»
attrs
ã«ã¯ãããã«é«åºŠãªãŠãŒã¹ã±ãŒã¹ã«å¯Ÿå¿ããããã®æ©èœãåãã£ãŠããŸãã
ç¶æ¿
attrs
ã§å®çŸ©ãããã¯ã©ã¹ã¯ãéåžžã® Python ã¯ã©ã¹ãšåæ§ã«ç¶æ¿ã§ããŸãã@attr.define
ã§ãã³ã¬ãŒããããã¯ã©ã¹å士ã§ç¶æ¿ãè¡ããšãå±æ§ã¯é©åã«åŒãç¶ãããŸãã
import attr
@attr.define
class Base:
a: int = attr.field()
@attr.define
class Derived(Base):
b: str = attr.field()
# 芪ã¯ã©ã¹ã®å±æ§ããªãŒããŒã©ã€ãããããšãå¯èœ
# a: float = attr.field(default=0.0)
d = Derived(a=1, b="hello")
print(d) # åºå: Derived(a=1, b='hello')
slots=True
ã䜿çšããŠããå Žåã®ç¶æ¿ã«ã¯æ³šæãå¿
èŠã§ãã芪ã¯ã©ã¹ãšåã¯ã©ã¹ã®äž¡æ¹ã§ slots=True
ãæå®ããã®ãäžè¬çã§ãã
ã·ãªã¢ã©ã€ãº/ãã·ãªã¢ã©ã€ãº: `attrs.asdict()` ãš `cattrs`
attrs
ã€ã³ã¹ã¿ã³ã¹ãèŸæžãã¿ãã«ã«å€æããããã®ãã«ããŒé¢æ°ãçšæãããŠããŸããããã㯠JSON ãä»ã®ãã©ãŒããããžã®ã·ãªã¢ã©ã€ãºã«åœ¹ç«ã¡ãŸãã
attrs.asdict(instance)
: ã€ã³ã¹ã¿ã³ã¹ãèŸæžã«å€æããŸãããã¹ããããattrs
ã€ã³ã¹ã¿ã³ã¹ãååž°çã«èŸæžã«å€æãããŸããattrs.astuple(instance)
: ã€ã³ã¹ã¿ã³ã¹ãã¿ãã«ã«å€æããŸãã
import attr
import json
@attr.define
class Address:
street: str = attr.field()
city: str = attr.field()
@attr.define
class Person:
name: str = attr.field()
age: int = attr.field()
address: Address = attr.field()
addr = Address(street="123 Main St", city="Anytown")
person = Person(name="Alice", age=30, address=addr)
# èŸæžã«å€æ
person_dict = attr.asdict(person)
print(person_dict)
# åºå: {'name': 'Alice', 'age': 30, 'address': {'street': '123 Main St', 'city': 'Anytown'}}
# JSON ã«ã·ãªã¢ã©ã€ãº
person_json = json.dumps(person_dict, indent=2)
print(person_json)
# åºå:
# {
# "name": "Alice",
# "age": 30,
# "address": {
# "street": "123 Main St",
# "city": "Anytown"
# }
# }
# ã¿ãã«ã«å€æ
person_tuple = attr.astuple(person)
print(person_tuple)
# åºå: ('Alice', 30, ('123 Main St', 'Anytown'))
ããè€éãªã·ãªã¢ã©ã€ãº/ãã·ãªã¢ã©ã€ãºïŒæ§é å/éæ§é åïŒã®ããŒãºã«ã¯ãattrs
ãšå¯æ¥ã«é£æºãã `cattrs` ãšããã©ã€ãã©ãªãéåžžã«åŒ·åã§ããcattrs
ã¯ãèŸæžãJSONãã attrs
ã€ã³ã¹ã¿ã³ã¹ãžã®å€æïŒãã·ãªã¢ã©ã€ãºïŒãããã³ãã®éã®å€æïŒã·ãªã¢ã©ã€ãºïŒããåã¢ãããŒã·ã§ã³ã«åºã¥ããŠèªåçã«ããã€å¹ççã«è¡ãããšãã§ããŸãã
# cattrs ã®ã€ã³ã¹ããŒã«: pip install cattrs
import cattrs
import json
converter = cattrs.Converter()
# èŸæžãã Person ã€ã³ã¹ã¿ã³ã¹ãžãã·ãªã¢ã©ã€ãº (éæ§é å)
person_data = {
"name": "Bob",
"age": 25,
"address": {
"street": "456 Oak Ave",
"city": "Otherville"
}
}
bob = converter.structure(person_data, Person)
print(bob) # åºå: Person(name='Bob', age=25, address=Address(street='456 Oak Ave', city='Otherville'))
print(isinstance(bob, Person)) # åºå: True
print(isinstance(bob.address, Address)) # åºå: True
# Person ã€ã³ã¹ã¿ã³ã¹ããèŸæžãžã·ãªã¢ã©ã€ãº (æ§é å)
bob_dict = converter.unstructure(bob)
print(bob_dict == person_data) # åºå: True
# JSON æååããçŽæ¥ãã·ãªã¢ã©ã€ãºãå¯èœ
json_string = '{"name": "Charlie", "age": 42, "address": {"street": "789 Pine Ln", "city": "Somewhere"}}'
charlie = converter.loads(json_string, Person)
print(charlie) # åºå: Person(name='Charlie', age=42, address=Address(street='789 Pine Ln', city='Somewhere'))
cattrs
ã¯ãæ¥ä»æå»ãªããžã§ã¯ããEnumãUnionåããžã§ããªã¯ã¹ãªã©ãæ§ã
ãªåã«å¯Ÿããå€æã«ãŒã«ãèªåãŸãã¯æåã§èšå®ã§ããéåžžã«æè»ãªããŒã¿å€æãå®çŸããŸãã
ãã®ä»ã®é«åºŠãªæ©èœ
__attrs_post_init__(self)
:__init__
ãå®äºããçŽåŸã«åŒã³åºãããã¡ãœãããåæååŸã®è¿œå åŠçïŒä»ã®å±æ§å€ã«åºã¥ããå±æ§ã®èšç®ãªã©ïŒãèšè¿°ã§ããŸããon_setattr
ããã¯:@attr.define(on_setattr=...)
ã§ãå±æ§ãžã®ä»£å ¥æã«å®è¡ãããåŠçïŒããªããŒã·ã§ã³ãã³ã³ããŒãžã§ã³ãã«ã¹ã¿ã ããžãã¯ãªã©ïŒã现ããå¶åŸ¡ã§ããŸãã
ãŸãšã âš
attrs
ã¯ãPython ã§ã¯ã©ã¹ãæ±ãéã®å®åçãªäœæ¥ã倧å¹
ã«åæžããéçºè
ãã¯ã©ã¹ã®æ¬è³ªçãªããžãã¯ã«éäžã§ããããã«ãããéåžžã«åŒ·åã§æçããã©ã€ãã©ãªã§ãã
attrs
ã䜿ãããšã§ã以äžã®ãããªã¡ãªãããåŸãããŸãã
- ð ã³ãŒãã®ç°¡æœå:
__init__
,__repr__
,__eq__
ãªã©ã®èªåçæã - ð å¯èªæ§ã®åäž: ã¯ã©ã¹ã®æå³ãæ確ã«ãªããä¿å®ãããããªããŸãã
- ð¡ïž å ç¢æ§ã®åäž: ããªããŒã¿ãã³ã³ããŒã¿ã«ããããŒã¿æŽåæ§ã®ç¢ºä¿ã
- ð§ äžå€æ§ã®å®¹æãªå®çŸ:
frozen=True
ã§å®å šãªäžå€ã¯ã©ã¹ãäœæã - ð ããã©ãŒãã³ã¹ãšã¡ã¢ãªå¹ç:
slots=True
ã«ããæé©åã - ð§ é«ãæè»æ§ãšã«ã¹ã¿ãã€ãºæ§: è±å¯ãªãªãã·ã§ã³ãšããã¯ã
- ð
dataclasses
ãšã®æ¯èŒ: ããå€ãã®æ©èœãšåºã Python ããŒãžã§ã³äºææ§ãæäŸã
æšæºã©ã€ãã©ãªã® dataclasses
ãçŽ æŽãããéžæè¢ã§ãããããªããŒã·ã§ã³ãã³ã³ããŒãžã§ã³ãå€ã Python ããŒãžã§ã³ãµããŒãããããã¯æ倧éã®æè»æ§ãå¿
èŠãªå Žåã«ã¯ãattrs
ããã匷åãªéžæè¢ãšãªããŸãã
ãŸã attrs
ãè©Šããããšããªãæ¹ã¯ããã²æ¬¡ã®ãããžã§ã¯ãã§å°å
¥ãæ€èšããŠã¿ãŠãã ããããã£ãšãã®äŸ¿å©ããšåŒ·åãã«é©ãã¯ãã§ãïŒ ð
ããã«è©³ããæ å ±ãææ°ã®æ©èœã«ã€ããŠã¯ã attrs å ¬åŒããã¥ã¡ã³ã ãåç §ããŠãã ããã
ã³ã¡ã³ã