Repository: joke2k/faker

Issue:
pydict(variable_nb_elements=False) returns dicts with varying number of elements
* Faker version: 4.0.1
* OS: OS X 10.15.3

pydict with `variable_nb_elements=False` still has varying number of elements.

### Steps to reproduce

```
from faker import Faker

fake = Faker()

nb = 80
for _ in range(30):  # nb is large and the faker function is called 30 times to increase the probability of reproducing the bug.
    fake_dict = fake.pydict(nb_elements=nb, variable_nb_elements=False)
    nb_reality = len(fake_dict)
    if nb_reality != nb:
        print(nb_reality)
```

### Expected behavior

The length of the dictionary should always be `nb`. 

### Actual behavior

It is often shorter as the keys are generated with [`self.generator.words(nb_elements)`](https://github.com/joke2k/faker/blob/master/faker/providers/python/__init__.py#L166). If the keys are not unique, the element is dropped.


Discussion:

Patch:
diff --git a/faker/providers/internet/__init__.py b/faker/providers/internet/__init__.py
index 60dab824..e42ff0bd 100644
--- a/faker/providers/internet/__init__.py
+++ b/faker/providers/internet/__init__.py
@@ -222,6 +222,33 @@ class Provider(BaseProvider):
         company = self._to_ascii(company_elements.pop(0))
         return company
 
+    def dga(self, year=None, month=None, day=None, tld=None, length=None):
+        """Generates a domain name by given date
+        https://en.wikipedia.org/wiki/Domain_generation_algorithm
+
+        :type year: int
+        :type month: int
+        :type day: int
+        :type tld: str
+        :type length: int
+        :rtype: str
+        """
+
+        domain = ''
+        year = year or self.random_int(min=1, max=9999)
+        month = month or self.random_int(min=1, max=12)
+        day = day or self.random_int(min=1, max=30)
+        tld = tld or self.tld()
+        length = length or self.random_int(min=2, max=63)
+
+        for _ in range(length):
+            year = ((year ^ 8 * year) >> 11) ^ ((year & 0xFFFFFFF0) << 17)
+            month = ((month ^ 4 * month) >> 25) ^ 16 * (month & 0xFFFFFFF8)
+            day = ((day ^ (day << 13)) >> 19) ^ ((day & 0xFFFFFFFE) << 12)
+            domain += chr(((year ^ month ^ day) % 25) + 97)
+
+        return domain + '.' + tld
+
     def tld(self):
         return self.random_element(self.tlds)
 
diff --git a/faker/providers/python/__init__.py b/faker/providers/python/__init__.py
index cb99deae..f439c958 100644
--- a/faker/providers/python/__init__.py
+++ b/faker/providers/python/__init__.py
@@ -163,7 +163,7 @@ class Provider(BaseProvider):
             nb_elements = self.randomize_nb_elements(nb_elements, min=1)
 
         return dict(zip(
-            self.generator.words(nb_elements),
+            self.generator.words(nb_elements, unique=True),
             self._pyiterable(nb_elements, False, *value_types),
         ))
 


Test patch:
diff --git a/tests/test_factory.py b/tests/test_factory.py
index a93df7cf..9ba55204 100644
--- a/tests/test_factory.py
+++ b/tests/test_factory.py
@@ -889,6 +889,14 @@ class FactoryTestCase(unittest.TestCase):
 
         assert expected_methods == sorted(got_methods)
 
+    def test_dga(self):
+        faker = Faker()
+
+        assert faker.dga() != faker.dga()
+
+        expected_domain = 'cqphixmpdfpptskr.com'
+        assert faker.dga(day=1, month=1, year=1000, tld='com', length=16) == expected_domain
+
     def test_random_sample_unique(self):
         from faker.providers import BaseProvider
         provider = BaseProvider(self.generator)