<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Custom generators
Step1:  Custom generator without __init__ method
Step2:  Explicitly setting the name of generated items
Let's repeat the previous example, but explicitly set the name of generated items by setting the __tohu_items_name__ attribute inside the custom generator.
Step3:  The generated sequence is the same as above, but the name of the items has changed from Quux to Foobar.
Step4:  Custom generator with __init__ method
Step5:  Custom generator containing derived generators
Step6:  Example
Step7:  Example
Step8:  Example
<ASSISTANT_TASK:>
Python Code:
import tohu
from tohu.v4.primitive_generators import *
from tohu.v4.derived_generators import *
from tohu.v4.dispatch_generators import *
from tohu.v4.custom_generator import *
from tohu.v4.utils import print_generated_sequence, make_dummy_tuples
print(f'Tohu version: {tohu.__version__}')
Explanation: Custom generators
End of explanation
class QuuxGenerator(CustomGenerator):
    aa = Integer(100, 200)
    bb = HashDigest(length=6)
    cc = FakerGenerator(method='name')
g = QuuxGenerator()
print_generated_sequence(g, num=10, sep='\n', seed=12345)
Explanation: Custom generator without __init__ method
End of explanation
class SomeGeneratorWithExplicitItemsName(CustomGenerator):
    __tohu_items_name__ = 'Foobar'
    aa = Integer(100, 200)
    bb = HashDigest(length=6)
    cc = FakerGenerator(method='name')
g = SomeGeneratorWithExplicitItemsName()
Explanation: Explicitly setting the name of generated items
Let's repeat the previous example, but explicitly set the name of generated items by setting the __tohu_items_name__ attribute inside the custom generator.
End of explanation
print_generated_sequence(g, num=10, sep='\n', seed=12345)
Explanation: The generated sequence is the same as above, but the name of the items has changed from Quux to Foobar.
End of explanation
class QuuxGenerator(CustomGenerator):
    aa = Integer(100, 200)
    
    def __init__(self, faker_method):
        self.bb = FakerGenerator(method=faker_method)
        # Note: the call to super().__init__() needs to be at the end,
        # and it needs to be passed the same arguments as the __init__()
        # method from which it is called (here: `faker_method`).
        super().__init__(faker_method)
g1 = QuuxGenerator(faker_method='first_name')
g2 = QuuxGenerator(faker_method='city')
print_generated_sequence(g1, num=10, sep='\n', seed=12345); print()
print_generated_sequence(g2, num=10, sep='\n', seed=12345)
Explanation: Custom generator with __init__ method
End of explanation
some_tuples = make_dummy_tuples('abcdefghijklmnopqrstuvwxyz')
#some_tuples[:5]
Explanation: Custom generator containing derived generators
End of explanation
class QuuxGenerator(CustomGenerator):
    aa = SelectOne(some_tuples)
    bb = GetAttribute(aa, 'x')
    cc = GetAttribute(aa, 'y')
g = QuuxGenerator()
print_generated_sequence(g, num=10, sep='\n', seed=12345)
Explanation: Example: extracting attributes
End of explanation
def square(x):
    return x * x
def add(x, y):
    return x + y
class QuuxGenerator(CustomGenerator):
    aa = Integer(0, 20)
    bb = Integer(0, 20)
    cc = Apply(add, aa, Apply(square, bb))
g = QuuxGenerator()
print_generated_sequence(g, num=10, sep='\n', seed=12345)
df = g.generate(num=100, seed=12345).to_df()
print(list(df['aa'][:20]))
print(list(df['bb'][:20]))
print(list(df['cc'][:20]))
all(df['aa'] + df['bb']**2 == df['cc'])
Explanation: Example: arithmetic
End of explanation
class QuuxGenerator(CustomGenerator):
    name = FakerGenerator(method="name")
    tag = SelectOne(['a', 'bb', 'ccc'])
g = QuuxGenerator()
quux_items = g.generate(num=100, seed=12345)
quux_items.to_df().head(5)
tag_lookup = {
    'a': [1, 2, 3, 4, 5],
    'bb': [10, 20, 30, 40, 50],
    'ccc': [100, 200, 300, 400, 500],
}
class FoobarGenerator(CustomGenerator):
    some_quux = SelectOne(quux_items)
    number = SelectOneDerived(Lookup(GetAttribute(some_quux, 'tag'), tag_lookup))
h = FoobarGenerator()
h_items = h.generate(10000, seed=12345)
df = h_items.to_df(fields={'name': 'some_quux.name', 'tag': 'some_quux.tag', 'number': 'number'})
df.head()
print(df.query('tag == "a"')['number'].isin([1, 2, 3, 4, 5]).all())
print(df.query('tag == "bb"')['number'].isin([10, 20, 30, 40, 50]).all())
print(df.query('tag == "ccc"')['number'].isin([100, 200, 300, 400, 500]).all())
df.query('tag == "a"').head(5)
df.query('tag == "bb"').head(5)
df.query('tag == "ccc"').head(5)
Explanation: Example: multi-stage dependencies
End of explanation
<END_TASK>