Q:

Python - UnboundLocalError: local variable 'd' referenced before assignment

This function:
def weighted_choice(self, dna):

    """
    Chooses a random element from tags.
    Weight determines the probability 
    of choosing its respective item. 
    """

    weight_total = sum((d[1] for d in dna))

    n = random.uniform(0, weight_total)

    for d, weight in dna:
        if n < weight:
            return d
        n = n - weight

    return d

is returning the following error:
Traceback (most recent call last):
  File "/usr/src/app/project/api/classifiers/metadata/bio/replicate.py", line 39, in brandio_genetics
    client_and_product_genes = bio.fitness(place_dna, client_dna)
  File "/usr/src/app/project/api/classifiers/metadata/bio/dna.py", line 553, in fitness
    # print each offspring
  File "/usr/src/app/project/api/classifiers/metadata/bio/dna.py", line 508, in crossover
    # genre 2
  File "/usr/src/app/project/api/classifiers/metadata/bio/dna.py", line 440, in weighted_choice
UnboundLocalError: local variable 'd' referenced before assignment

data structure:
dna1 = {'patalanov': {'indie pop': 100, 'indie': 97, 'australian': 90, '80s': 52, 'alternative': 52, 'new wave': 27, 'jangle pop': 26, 'seen live': 25, 'rock': 23, 'indie rock': 23, 'pop': 21, 'australia': 16, 'post-punk': 15, 'alternative rock': 11, 'Brisbane': 10, 'twee': 6, 'college rock': 5, 'singer-songwriter': 5, 'Post punk': 5, 'Aussie': 4, 'The Go-Betweens': 4, 'punk': 3, 'power pop': 3, 'elegant': 3, "80's": 3, 'alternative pop': 3, 'Favourites': 3, 'indie-pop': 3, 'guitar pop': 3, 'All': 3, 'twee pop': 2, 'romantic': 2, '90s': 2, 'Passionate': 2, 'literate': 2, 'melancholy': 2, 'Favorite Artists': 2, 'soft': 2, 'male vocalists': 2, 'oz': 2, 'emusic': 2, 'neo acoustic': 2, '80s indie': 2, 'postcard': 2, 'Grant McLennan': 2, 'Robert Forster': 2, '70s': 2, 'emo': 2, 'folk': 2, 'favorites': 2, 'indiepop': 2, 'americana': 2, 'Reflective': 2, 'genius': 2, 'Alt-country': 2, 'Bittersweet': 2, '00s': 2, 'sophisticated': 2, '1980s': 2, 'wistful': 2, 'jangle': 2, 'c86': 2, 'Left of the Dial': 2, 'Very Good': 2, 'Godlike': 2, 'old favorites': 2, 'wrongly tagged as twee': 2, 'hi fidelity': 2, 'jangle-pop': 2, 'records and tapes': 2}}

dna2 = {'Suplicy': {'electronica ': 288.0, 'art rock ': 290.0, 'chillout ': 288.0, 'genius ': 290.0, 'trip-hop ': 287.0, 'psychedelic ': 288.0, 'indie pop ': 287.0, 'ambient ': 288.0, 'indie rock ': 291.0, 'post-rock ': 287.0, 'alternative rock ': 291.0, 'seen live ': 288.0, 'melancholic ': 290.0, 'Awesome ': 291.0, 'radiohead ': 295.0, 'emo ': 286.0, 'rock ': 292.0, 'indie ': 289.0, '90s ': 5.0, 'pop ': 288.0, 'britpop ': 289.0, 'british ': 293.0, 'classic rock ': 288.0, 'better than radiohead ': 288.0, 'overrated ': 288.0, 'alternative ': 290.0, 'Progressive ': 289.0, 's ': 283.0, 'Favorite ': 288.0, 'electronic ': 289.0, 'Experimental Rock ': 289.0, 'beautiful ': 290.0, 'melancholy ': 290.0, 'idm ': 288.0, 'Progressive rock ': 288.0, 'favorites ': 288.0, 'english ': 288.0, 'male vocalists ': 289.0, 'experimental ': 288.0, 'UK ': 290.0}}

following the traceback:
def crossover(self, dna1, dna2):

        """
        Mixes dna1 and dna2.
        """

        global breed
        # get alleles for gene 1
        A = self.alleles(dna1)[1]
        a = self.alleles(dna1)[2]
        # get alleles for gene 2
        B = self.alleles(dna2)[1]
        b = self.alleles(dna2)[2]
        # format alleles as dictionaries
        AB, Ab, Ba, ab = ({} for i in range(4))
        # genre 1
        AB[str(self.weighted_choice(A))]='A'
        AB[str(self.weighted_choice(B))]='B'
        # genre 2
        Ab[str(self.weighted_choice(A))]='A'
        Ab[str(self.weighted_choice(b))]='b'
        # genre 3
        Ba[str(self.weighted_choice(B))]='B'
        Ba[str(self.weighted_choice(a))]='a'
        # genre 4
        ab[str(self.weighted_choice(a))]='a' 
        ab[str(self.weighted_choice(b))]='b'

        # pick a random number
        x = random.random()
        # set percentage
        if x < 0.25:
            # if low, generate recessive genotypes
            genotype = random.choice([ab])
            breed = ' '.join([k for k in genotype])
            #return genotype
        else:
            # if low, generate dominant genotypes
            genotype = random.choice([AB, Ab, Ba])
            breed = ' '.join([k for k in genotype])

        return (genotype, breed)

def fitness(self, dna1, dna2, generations=600):

        '''
        Most adapted phenotype after many generations.
        '''

        # container for unique breeds
        offspring = set()
        # fetch million song dataset 
        pool = self.genome()
        # test for many generations
        for generation in range(generations):
            print ("Generation %s..." % (generation))
            # mix phenotype
            breed = self.crossover(dna1,dna2)[1]
            # print each offspring
            print (breed)
            # build genotype
            offspring.add(breed)

        # container for sequences
        matches = []
        # lookup offspring in genetic pool
        for o in sorted(offspring):
            for p in pool:
                # compare genetic sequences
                seq = difflib.SequenceMatcher(None, a=o.lower(), b=p.lower())
                print (o, p, round(seq.ratio(), 4))
                # sort matches by similarity ratio
                matches.append([o, p, round(seq.ratio(), 4)])

        # order fittest by descending order
        # similarity = sorted(matches, key=lambda x: x[2], reverse=True)
        similarity = sorted(matches, key=lambda x: x[2], reverse=True)

        # containers for optimized genetic values
        fit = []
        # add
        for m in matches:
            # if m[2] == 1.0:
            #   fit.append(m)
            if 0.8880 <= m[2] <= 1.0:
                fit.append(m)

        return sorted(fit, key=lambda x: x[2], reverse=True)

def alleles(self, dna):

        global allele
        # each user is one
        individual = dna.keys()
        # containers for data
        total = []
        dominant = []
        recessive = []

        for k, v in dna.items():
            # track gene strength # python 3 requires list for indexing
            weight = list(v.values())
            # add all weights 
            total.append(weight[0])
            # each tag is a 'gene'
            gene = v.keys()
            # format in allele fashion
            allele = zip(gene, weight)
            # get the average weight for tags
            mean = sum(total)/len(total)
            # print allel
            for a in allele:
                # above avg is a dominant tag
                if a[1] > mean:
                    dominant.append(a)
                elif a[1] == 1.0:
                    dominant.append(a)
                    recessive.append(a)
                # below avg is a recessive tag
                else:
                    recessive.append(a)

        return (individual, dominant, recessive)

what is wrong?

A:

Take this as an example:
>>> def f(a):
...     for d in a:
...         if d % 2 == 0:
...             return d
...     return d  # You're returning d, but if the list is empty d is expected to be undefined
... 
>>> f([5])
5
>>> f([5, 5, 2])
2
>>> f([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in f
UnboundLocalError: local variable 'd' referenced before assignment

If you pass an empty list (in this case f([])), the for loop will never happen, and the local d variable will never be created.
Which means that you're getting that error because your dna iterable does not have any elements. Two options: 

Return a default value when dna is empty

def weighted_choice(self, dna):

    """
    Chooses a random element from tags.
    Weight determines the probability 
    of choosing its respective item. 
    """

    weight_total = sum((d[1] for d in dna))

    n = random.uniform(0, weight_total)

    for d, weight in dna:
        if n < weight:
            return d
        n = n - weight

    return 0 if not dna else d  # return 0 if dna is empty (not dna). otherwise return d

Avoid calling weighted_choice if dna is empty

# Wherever you're calling the weighted_choice method.
if dna:
    self.weighted_choice(dna)

EDIT:
First of all you're calling twice the alleles method for each dna1 and dna2
I'd change this:
A = self.alleles(dna1)[1]
a = self.alleles(dna1)[2]

B = self.alleles(dna2)[1]   
b = self.alleles(dna2)[2]

to:
# It seems like you only care about 2nd and 3rd elements being
# returned, so _ will silently take the `individual` value
_, A, a = self.alleles(dna1)
_, B, b = self.alleles(dna2)

Notes:

Only two calls and the tuple expansion makes the trick. It assigns the values returned with return (individual, dominant, recessive) to the right variables.

Once said that, either dominant or recessive are empty. The error itself must be inside your alleles method.
    def alleles(self, dna):
        # Your code goes here. Make sure it does what you're expecting.
        # And if dominant and/or recessive are not expected to be empty
        # with the given input (aka. dna) you have to fix this method.

        print('dominant:', dominant)    
        print('recessive:', recessive)

        return (individual, dominant, recessive)

