The dir1/a.py is a class to be tested. I will need to mock/patch the class.
dir1/a.py
from somemodule import get_c
#dataclass
class A:
x: int
y: object
c: ClassVar[C] = get_c() # get_c() need to be mocked/patched
test_1.py
#pytest.fixture
def sut() -> A:
x = 1
y = Mock()
return A(x, y)
def test_...(sut): # get_c() is called
''' '''
test_2.py
#patch('dir.a.A')
def test_...(): # get_c() is called
How to mock/patch A.c in the tests?
Because c: ClassVar[C] = get_c() is in the declaration of the A dataclass at the top-level of the dir1/a.py file, it gets run when the module is imported. So get_c will be called unless you take extreme measures (implementing a custom import loader, or patching the dataclass decorator before the import dir1.a gets called, ...).
If you don't want get_c to ever be called in your tests, the best and simpler solution is to change the code of dir1/a.py to not do it.
If it is OK for get_c to be called, but that the methods on it should not be used, it becomes simpler : just replace the c default value of your A dataclass with one of your own.
import pytest
import unittest.mock as mocking
from dataclasses import dataclass
from typing import ClassVar, TypeVar
C = TypeVar('C')
def get_c():
print("`get_c` called")
return "THING TO MOCK"
#dataclass
class A:
x: int
y: object
c: ClassVar[C] = get_c()
#pytest.fixture
def sut() -> A:
x = 1
y = mocking.Mock()
a = A(x, y)
a.c = mocking.Mock() # change the value of `c`
return a
def test_a(sut):
assert isinstance(sut.c, mocking.Mock)
Related
How to check type of class instance object passed from another module. Does it require from main import MyClass? Or can I just generally check object is of type "(any) class instance"?
# main.py
#########
import sub
class MyClass():
def __init__(self):
self.z = 1
a = MyClass()
assert type(a) == MyClass
assert isinstance(a, MyClass) == True
b = sub.fun(a)
# sub.py
########
def fun(i):
if isinstance(i, class): # general instead of __main__.MyClass
return i.__dict__
The above yields
NameError: name 'class' is not defined
Maybe it is not a good design and code shall be explicit rather than converting each class to dict?
I want to convert each class instance passed to fun to dictionary
Try the below
class Foo:
def __init__(self, x):
self.x = x
class Bar:
def __init__(self, x):
self.x = x
def fun(instance):
if hasattr(instance, '__dict__'):
return instance.__dict__
print(fun(Foo(56)))
print(fun(Bar("56")))
output
{'x': 56}
{'x': '56'}
Yes, python needs to know which class you are checking because you can have two different classes with the same name in different files.
In file sub.py you have to write:
from main import MyClass
def fun(i):
if isinstance(i, MyClass):
return i.__dict__
But in case you importing fun into main.py you will get circular import, be careful)
# sub.py
########
def fun(i):
if isinstance(i, class): # <---- class is keyword in Python, you cannot use it
# like this. You can use as type(class)
return i.__dict__
I've been playing around with python dataclasses and was wondering: What is the most elegant or most pythonic way to make one or some of the fields descriptors?
In the example below I define a Vector2D class that should be compared on its length.
from dataclasses import dataclass, field
from math import sqrt
#dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(init=False)
def __post_init__(self):
type(self).length = property(lambda s: sqrt(s.x**2+s.y**2))
Vector2D(3,4) > Vector2D(4,1) # True
While this code works, it touches the class every time an instance is created, is there a more readable / less hacky / more intended way to use dataclasses and descriptors together?
Just having length as a property and not a field will work but this means I have to write __lt__, et.al. by myself.
Another solution I found is equally unappealing:
#dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(init=False)
#property
def length(self):
return sqrt(self.x**2+self.y**2)
#length.setter
def length(self, value):
pass
Introducing a no-op setter is necessary as apparently the dataclass-created init method tries to assign to length even though there isn't a default value and it explicitly sets init=False...
Surely there has to be a better way right?
Might not answer your exact question, but you mentioned that the reason that you didnt want to have length as a property and a not field was because you would have to
write __lt__, et.al by myself
While you do have to implement __lt__ by yourself, you can actually get away with implementing just that
from functools import total_ordering
from dataclasses import dataclass, field
from math import sqrt
#total_ordering
#dataclass
class Vector2D:
x: int
y: int
#property
def length(self):
return sqrt(self.x ** 2 + self.y ** 2)
def __lt__(self, other):
if not isinstance(other, Vector2D):
return NotImplemented
return self.length < other.length
def __eq__(self, other):
if not isinstance(other, Vector2D):
return NotImplemented
return self.length == other.length
print(Vector2D(3, 4) > Vector2D(4, 1))
The reason this works is because total_ordering just adds all the other equality methods based on __eq__ and __lt__
I do not think that the example you present is a good use case for what you are trying to do. Nevertheless, for completeness sake, here is a possible solution to your problem:
#dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(default=property(lambda s: sqrt(s.x**2+s.y**2)), init=False)
This works because dataclass sets defaults as values on the class attributes unless the value is a list, dict or set.
Although you could implement the #property and other methods manually, this can make you lose other desirable features like in this case using hash=False if you wanted to use your Vector2D in a dict. Additionally, letting it implement dunder methods for you makes your code less error prone e.g. you can't forget to return NotImplemented which is a common mistake.
The drawback is that implementing the correct type-hint is not easy and that there can be some minor caveats, but once the type-hint is implemented it can be easily used anywhere.
The property (descriptor) type-hint:
import sys
from typing import Any, Optional, Protocol, TypeVar, overload
if sys.version_info < (3, 9):
from typing import Type
else:
from builtins import type as Type
IT = TypeVar("IT", contravariant=True)
CT = TypeVar("CT", covariant=True)
GT = TypeVar("GT", covariant=True)
ST = TypeVar("ST", contravariant=True)
class Property(Protocol[CT, GT, ST]):
# Get default attribute from a class.
#overload
def __get__(self, instance: None, owner: Type[Any]) -> CT:
...
# Get attribute from an instance.
def __get__(self, instance: IT, owner: Optional[Type[IT]] = ...) -> GT:
...
def __get__(self, instance, owner=None):
...
def __set__(self, instance: Any, value: ST) -> None:
...
From here, we can now type-hint our property object when using a dataclass. Use field(default=property(...)) if you need to use the other options in field(...).
import sys
import typing
from dataclasses import dataclass, field
from math import hypot
# Use for read-only property.
if sys.version_info < (3, 11):
from typing import NoReturn as Never
else:
from typing import Never
#dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
# Properties return themselves as their default class variable.
# Read-only properties never allow setting a value.
# If init=True, then it would assign self.length = Vector2D.length for the
# default factory.
# Setting repr=False for consistency with init=False.
length: Property[property, float, Never] = field(
default=property(lambda v: hypot(v.x, v.y)),
init=False,
repr=False,
)
v1 = Vector2D(3, 4)
v2 = Vector2D(6, 8)
if typing.TYPE_CHECKING:
reveal_type(Vector2D.length) # builtins.property
reveal_type(v1.length) # builtins.float
assert v1.length == 5.0
assert v2.length == 10.0
assert v1 < v2
Try it on mypy Playground.
Consider:
from __future__ import annotations
class A:
#classmethod
def get(cls) -> A:
return cls()
class B(A):
pass
def func() -> B: # Line 12
return B.get()
Running mypy on this we get:
$ mypy test.py
test.py:12: error: Incompatible return value type (got "A", expected "B")
Found 1 error in 1 file (checked 1 source file)
Additionally, I have checked to see if old-style recursive annotations work. That is:
# from __future__ import annotations
class A:
#classmethod
def get(cls) -> "A":
# ...
...to no avail.
Of course one could do:
from typing import cast
def func() -> B: # Line 12
return cast(B, B.get())
Every time this case pops up. But I would like to avoid doing that.
How should one go about typing this?
The cls and self parameters are usually inferred by mpyp to avoid a lot of redundant code, but when required they can be specified explicitly by annotations.
In this case the explicit type for the class method would look like the following:
class A:
#classmethod
def get(cls: Type[A]) -> A:
return cls()
So what we really need here is a way to make Type[A] a generic parameter, such that when the class method is called from a child class, you can reference the child class instead. Luckily, we have TypeVar values for this.
Working this into your existing example we will get the following:
from __future__ import annotations
from typing import TypeVar, Type
T = TypeVar('T')
class A:
#classmethod
def get(cls: Type[T]) -> T:
return cls()
class B(A):
pass
def func() -> B:
return B.get()
Now mypy should be your friend again! 😎
I want to populate an attribute of a dataclass using the default_factory method. However, since the factory method is only meaningful in the context of this specific class, I want to keep it inside the class (e.g. as a static or class method). For example:
from dataclasses import dataclass, field
from typing import List
#dataclass
class Deck:
cards: List[str] = field(default_factory=self.create_cards)
#staticmethod
def create_cards():
return ['King', 'Queen']
However, I get this error (as expected) on line 6:
NameError: name 'self' is not defined
How can I overcome this issue? I don't want to move the create_cards() method out of the class.
One possible solution is to move it to __post_init__(self). For example:
#dataclass
class Deck:
cards: List[str] = field(default_factory=list)
def __post_init__(self):
if not self.cards:
self.cards = self.create_cards()
def create_cards(self):
return ['King', 'Queen']
Output:
d1 = Deck()
print(d1) # prints Deck(cards=['King', 'Queen'])
d2 = Deck(["Captain"])
print(d2) # prints Deck(cards=['Captain'])
I adapted momo's answer to be self contained in a class and without thread-safety (since I was using this in asyncio.PriorityQueue context):
from dataclasses import dataclass, field
from typing import Any, ClassVar
#dataclass(order=True)
class FifoPriorityQueueItem:
data: Any=field(default=None, compare=False)
priority: int=10
sequence: int=field(default_factory=lambda: {0})
counter: ClassVar[int] = 0
def get_data(self):
return self.data
def __post_init__(self):
self.sequence = FifoPriorityQueueItem.next_seq()
#staticmethod
def next_seq():
FifoPriorityQueueItem.counter += 1
return FifoPriorityQueueItem.counter
def main():
import asyncio
print('with FifoPriorityQueueItem is FIFO')
q = asyncio.PriorityQueue()
q.put_nowait(FifoPriorityQueueItem('z'))
q.put_nowait(FifoPriorityQueueItem('y'))
q.put_nowait(FifoPriorityQueueItem('b', priority=1))
q.put_nowait(FifoPriorityQueueItem('x'))
q.put_nowait(FifoPriorityQueueItem('a', priority=1))
while not q.empty():
print(q.get_nowait().get_data())
print('without FifoPriorityQueueItem is no longer FIFO')
q.put_nowait((10, 'z'))
q.put_nowait((10, 'y'))
q.put_nowait((1, 'b'))
q.put_nowait((10, 'x'))
q.put_nowait((1, 'a'))
while not q.empty():
print(q.get_nowait()[1])
if __name__ == '__main__':
main()
Results in:
with FifoPriorityQueueItem is FIFO
b
a
z
y
x
without FifoPriorityQueueItem is no longer FIFO
a
b
x
y
z
One option is to wait until after you define the field object to make create_cards a static method. Make it a regular function, use it as such to define the cards field, then replace it with a static method that wraps the function.
from dataclasses import dataclass, field
from typing import List
#dataclass
class Deck:
# Define a regular function first (we'll replace it later,
# so it's not going to be an instance method)
def create_cards():
return ['King', 'Queen']
# Use create_cards as a regular function
cards: List[str] = field(default_factory=create_cards)
# *Now* make it it a static method
create_cards = staticmethod(cards)
This works because the field object is created while the class is being defined, so it doesn't need to be a static method yet.
I want to know a simple way to make a dataclass bar frozen.
#dataclass
class Bar:
foo: int
bar = Bar(foo=1)
In other words, I want a function like the following some_fn_to_freeze
frozen_bar = some_fn_to_freeze(bar)
frozen_bar.foo = 2 # Error
And, an inverse function some_fn_to_unfreeze
bar = som_fn_to_unfrozen(frozen_bar)
bar.foo = 3 # not Error
The standard way to mutate a frozen dataclass is to use dataclasses.replace:
old_bar = Bar(foo=123)
new_bar = dataclasses.replace(old_bar, foo=456)
assert new_bar.foo == 456
For more complex use-cases, you can use the dataclass utils module from: https://github.com/google/etils
It add a my_dataclass = my_dataclass.unfrozen() member, which allow to mutate frozen dataclasses directly
# pip install etils[edc]
from etils import edc
#edc.dataclass(allow_unfrozen=True) # Add the `unfrozen()`/`frozen` method
#dataclasses.dataclass(frozen=True)
class A:
x: Any = None
y: Any = None
old_a = A(x=A(x=A()))
# After a is unfrozen, the updates on nested attributes will be propagated
# to the top-level parent.
a = old_a.unfrozen()
a.x.x.x = 123
a.x.y = 'abc'
a = a.frozen() # `frozen()` recursively call `dataclasses.replace`
# Only the `unfrozen` object is mutated. Not the original one.
assert a == A(x=A(x=A(x = 123), y='abc'))
assert old_a == A(x=A(x=A()))
As seen in the example, you can return unfrozen/frozen copies of the dataclass, which was explicitly designed to mutate nested dataclasses.
#edc.dataclass also add a a.replace(**kwargs) method to the dataclass (alias of dataclasses.dataclass)
a = A()
a = a.replace(x=123, y=456)
assert a == A(x=123, y=456)
dataclass doesn't have built-in support for that. Frozen-ness is tracked on a class-wide basis, not per-instance, and there's no support for automatically generating frozen or unfrozen equivalents of dataclasses.
While you could try to do something to generate new dataclasses on the fly, it'd interact very poorly with isinstance, ==, and other things you'd want to work. It's probably safer to just write two dataclasses and converter methods:
#dataclass
class Bar:
foo: int
def as_frozen(self):
return FrozenBar(self.foo)
#dataclass(frozen=True)
class FrozenBar:
foo: int
def as_unfrozen(self):
return Bar(self.foo)
Python dataclasses are great, but the attrs package is a more flexible alternative, if you are able to use a third-party library. For example:
import attr
# Your class of interest.
#attr.s()
class Bar(object):
val = attr.ib()
# A frozen variant of it.
#attr.s(frozen = True)
class FrozenBar(Bar):
pass
# Three instances:
# - Bar.
# - FrozenBar based on that Bar.
# - Bar based on that FrozenBar.
b1 = Bar(123)
fb = FrozenBar(**attr.asdict(b1))
b2 = Bar(**attr.asdict(fb))
# We can modify the Bar instances.
b1.val = 777
b2.val = 888
# Check current vals.
for x in (b1, fb, b2):
print(x)
# But we cannot modify the FrozenBar instance.
try:
fb.val = 999
except attr.exceptions.FrozenInstanceError:
print(fb, 'unchanged')
Output:
Bar(val=888)
FrozenBar(val=123)
Bar(val=999)
FrozenBar(val=123) unchanged
I'm using the following code to get a frozen copy of a dataclass class or instance:
import dataclasses
from dataclasses import dataclass, fields, asdict
import typing
from typing import TypeVar
FDC_SELF = TypeVar('FDC_SELF', bound='FreezableDataClass')
#dataclass
class FreezableDataClass:
#classmethod
def get_frozen_dataclass(cls: Type[FDC_SELF]) -> Type[FDC_SELF]:
"""
#return: a generated frozen dataclass definition, compatible with the calling class
"""
cls_fields = fields(cls)
frozen_cls_name = 'Frozen' + cls.__name__
frozen_dc_namespace = {
'__name__': frozen_cls_name,
'__module__': __name__,
}
excluded_from_freezing = cls.attrs_excluded_from_freezing()
for attr in dir(cls):
if attr.startswith('__') or attr in excluded_from_freezing:
continue
attr_def = getattr(cls, attr)
if hasattr(attr_def, '__func__'):
attr_def = classmethod(getattr(attr_def, '__func__'))
frozen_dc_namespace[attr] = attr_def
frozen_dc = dataclasses.make_dataclass(
cls_name=frozen_cls_name,
fields=[(f.name, f.type, f) for f in cls_fields],
bases=(),
namespace=frozen_dc_namespace,
frozen=True,
)
globals()[frozen_dc.__name__] = frozen_dc
return frozen_dc
#classmethod
def attrs_excluded_from_freezing(cls) -> typing.Iterable[str]:
return tuple()
def get_frozen_instance(self: FDC_SELF) -> FDC_SELF:
"""
#return: an instance of a generated frozen dataclass, compatible with the current dataclass, with copied values
"""
cls = type(self)
frozen_dc = cls.get_frozen_dataclass()
# noinspection PyArgumentList
return frozen_dc(**asdict(self))
Derived classes could overwrite attrs_excluded_from_freezing to exclude methods which wouldn't work on a frozen dataclass.
Why didn't I prefer other existing answers?
3rd party libraries - etils.edc, If I would use a solution from one of the previous answers, it would be this one. E.g. to get the ability to recursively freeze/unfreeze.
3rd party libraries - attrs
duplicated code