Random item generation system to be used in a roguelike
This isn't bad code for someone learning the language, but there are a few things I'd change:

At the top you have:

i_quality = ["Good", "Bad"]
i_quality = random.choice(i_quality)


And other such lines. The main problem here is you're overwriting i_quality. What if you wanted to generate a second quality later? You overwrote your master list of qualities. Have them as separate variables:

all_qualities = ["Good", "Bad"]
all_bases = ["Sword", "Gun"]
all_elements = ["Fire", "Water"]

i_quality = random.choice(all_qualities)
i_base = random.choice(all_bases)
i_element = random.choice(all_elements)




Be careful how you name things and what the responsibilities of functions are. create_Item does create an Item, but it also prints it out then discards it. Such a function isn't very useful. The caller could have just called the Item constructor themselves and then printed the object if they wanted to. Your function does some formatting, but that would be better off in the __str__ method of Item.

What would make for a useful factory function though would be a create_random_item function that returns a random Item. Taking all that together, I'd change this code to something closer to:

import random

all_qualities = ["Good", "Bad"]
all_bases = ["Sword", "Gun"]
all_elements = ["Fire", "Water"]

class Item(object):
    def __init__ (self, quality, base, element):
        self.quality = quality
        self.base = base
        self.element = element

    # What Python will call when you ask it to
    #  display an Item using str
    def __str__(self):
        return self.quality + " " + self.base + " of " + self.element

def create_random_item():
    quality = random.choice(all_qualities)
    base = random.choice(all_bases)
    element = random.choice(all_elements)

    return Item(quality, base, element)

for _ in range(10): # We'll create 10 random weapons and print them out
    item = create_random_item()
    print(str(item)) # str calls item's __str__

Good Gun of Fire
Good Sword of Fire
Bad Sword of Water
Good Sword of Fire
Good Sword of Water
Good Sword of Fire
Bad Gun of Water
Bad Sword of Water
Bad Gun of Water
Bad Sword of Water





This could be further improved though. Any time you have a closed set of members of a set (like how "fire" and "water" are members of the "elements" set), you should likely being using an Enum. They help avoid errors in many cases, and allow IDEs to assist you in auto-completing names.
In __str__, you could also make use of f-strings to avoid needing to manually concat using +. It would be neater to write:

def __str__(self):
    return f"{self.quality} {self.base} of {self.element}"


That allows for much less noise and allows you to focus more on what you what printed, and less on dealing with opening and closing quotes and +s.
For the sake of context, create_random_item may make more sense as a static method. It makes it clearer what class it's associated with, since then you'd call the method as:

Item.create_random_item()