import typing
from dataclasses import dataclass
from typing import ClassVar
@dataclass
class Point:
x: int
y: int
p = Point(1, 2)
assert p.x == 1 and p.y == 2, 'positional construction sets fields'
assert Point(10, y=20).y == 20
assert Point(x=5, y=6).x == 5
@dataclass
class WithDefault:
a: int
b: int = 5
c: str = 'hi'
assert WithDefault(1).b == 5
assert WithDefault(1).c == 'hi'
assert WithDefault(1, 2).b == 2
assert WithDefault(1, c='x').c == 'x'
assert WithDefault(1, b=9).b == 9
@dataclass
class WithClassVar:
x: int
count: ClassVar[int] = 0
assert WithClassVar(7).x == 7
assert WithClassVar.count == 0
@dataclass
class ClassVarSpellings:
x: int
bare: ClassVar[int] = 1
dotted: typing.ClassVar[int] = 3
dotted_arg: ClassVar[typing.Dict[str, int]] = {}
assert repr(ClassVarSpellings(7)) == 'ClassVarSpellings(x=7)'
assert ClassVarSpellings.dotted == 3
assert ClassVarSpellings.dotted_arg == {}
def expect_type_error(fn, message):
try:
fn()
assert False, f'expected TypeError: {message}'
except TypeError as e:
assert str(e) == message, f'wrong message: {e!r}'
expect_type_error(lambda: Point(), "Point.__init__() missing 2 required positional arguments: 'x' and 'y'")
expect_type_error(lambda: Point(1), "Point.__init__() missing 1 required positional argument: 'y'")
expect_type_error(lambda: Point(1, 2, 3), 'Point.__init__() takes 3 positional arguments but 4 were given')
expect_type_error(lambda: Point(1, x=2), "Point.__init__() got multiple values for argument 'x'")
expect_type_error(lambda: Point(1, 2, z=3), "Point.__init__() got an unexpected keyword argument 'z'")
expect_type_error(lambda: WithClassVar(7, 8), 'WithClassVar.__init__() takes 2 positional arguments but 3 were given')
expect_type_error(lambda: Point(**{1: 2}), 'keywords must be strings')
expect_type_error(lambda: Point(**{1: 2, 'x': 5}), 'keywords must be strings')
expect_type_error(lambda: Point(**{'z': 1, 1: 2}), 'keywords must be strings')
expect_type_error(lambda: Point(1, **{'x': 2, 1: 3}), 'keywords must be strings')
expect_type_error(lambda: Point(1, 2, 3, **{1: 2}), 'keywords must be strings')
def expect_error(fn, exc_type, message):
try:
fn()
assert False, f'expected an exception: {message}'
except exc_type as e:
assert str(e) == message, f'wrong message: {e!r}'
def mutable_default():
@dataclass
class BadList:
xs: list[int] = []
def mutable_default_dict():
@dataclass
class BadDict:
d: dict[str, int] = {}
def non_default_after_default():
@dataclass
class BadOrder:
a: int = 1
b: int
expect_error(
mutable_default, ValueError, "mutable default <class 'list'> for field xs is not allowed: use default_factory"
)
expect_error(
mutable_default_dict, ValueError, "mutable default <class 'dict'> for field d is not allowed: use default_factory"
)
expect_error(non_default_after_default, TypeError, "non-default argument 'b' follows default argument 'a'")
expect_type_error(lambda: Point(1, 2, 3, z=4), "Point.__init__() got an unexpected keyword argument 'z'")
expect_type_error(lambda: Point(1, 2, 3, x=4), "Point.__init__() got multiple values for argument 'x'")
expect_type_error(lambda: WithDefault(1, 2, 3, 4, b=9), "WithDefault.__init__() got multiple values for argument 'b'")
expect_type_error(
lambda: WithDefault(1, 2, 3, 4), 'WithDefault.__init__() takes from 2 to 4 positional arguments but 5 were given'
)
@dataclass
class Rebind:
a: int
b: int = 5
Rebind.b = 99
assert Rebind(1).b == 5
assert Rebind.b == 99
assert repr(Rebind(1)) == 'Rebind(a=1, b=5)'