Deepcopy recurses indefinitely
Hello! 👋
>>> import pycountry
>>> from copy import deepcopy
>>> deepcopy(pycountry.countries.get(alpha_2='US'))
Raises error:
RecursionError: maximum recursion depth exceeded
I'm a bit confused by the behavior of the library overall, because it always returns the same instance when querying a country, which is why I guess it doesn't bother to implement __eq__ on Country , yet the country instances are mutable. Shouldn't they be frozen? In that case I would resolve this by overriding the deep copy behavior so that it returns the same instance.
I've encountered this as well when trying to store country object in some of my dataclasses (dataclasses.dataclass).
This is probably caused by missing __deepcopy__ dunder in the pycountry.db.Country class.
I've solved it for my case by subclassing Country.
@MartinDevi if you want to use my code as a workaround, here it is:
class CopyableCountry(pycountry.db.Country):
"""
Overriding of `pycountry.db.Country` in order to be used by `copy.deepcopy()` for making deep copies.
This is because we use country class in `dataclasses.dataclass` which calls deepcopy on non-builtin data types
when serializing the object.
Thus, we can have a country object as an attribute of our dataclasses which can be serialized into a dictionary.
"""
def __init__(self, country: pycountry.db.Country):
if not isinstance(country, pycountry.db.Country):
raise TypeError("Country argument must be an instance of `pycountry.db.Country`!")
elif not country:
raise ValueError("Country argument is empty!")
super().__init__(
alpha_2=country.alpha_2,
alpha_3=country.alpha_3,
name=country.name,
numeric=country.numeric,
official_name=country.official_name,
common_name=country.common_name,
flag=country.flag,
)
def __deepcopy__(self, memo):
"""Create a new instance of `CopyableCountry()` with the same attributes."""
new_country = pycountry.db.Country(
alpha_2=self.alpha_2,
alpha_3=self.alpha_3,
name=self.name,
numeric=self.numeric,
official_name=self.official_name,
common_name=self.common_name,
flag=self.flag,
)
new_country = CopyableCountry(new_country)
memo[id(self)] = new_country
return new_country
Usage:
>>> import pycountry
>>> from copy import deepcopy
>>> _country = pycountry.countries.get(alpha_2='US')
>>> id(_country)
140548732301472
>>> country = CopyableCountry(_country)
>>> id(country)
140548732806144
>>> copied = deepcopy(country)
>>> id(copied)
140548733014176
If you want to use shallow copy, instead of deepcopy, you must implement __copy__ dunder.
Also, if you want to use the latest version 24.6.1, be sure to get rid of common_name lines in the code, because in that release, this attribute has been removed from the Country class.