Calling Wikifunctions for der, die, das
One of the least favorite parts for learners of German are the grammatical genders. At times it can feel like a tiny bureaucratic prank designed to test a learner’s patience. Articles seem to obey secret laws: der Tisch, die Tür, das Mädchen. While there are patterns, there are also enough exceptions to make confidence wobble. For learners, the frustration is not just remembering vocabulary, but remembering each noun with its gender attached. There’s no shortcut but learning them by heart.
You have to look up each word. These days, you don’t need to flip through the pages of a paper dictionary, you can make use of lexicographical data on Wikidata, also know as Wikidata Lexemes. Wikidata Lexemes are entries in Wikidata used to represent lexical information in a structured form. They record data about words, including the language to which a word belongs, its part of speech, its meanings, and its inflected forms. For example, a noun entry may include information about grammatical gender and plural forms, while a verb entry may include conjugated forms. Lexemes provide a way of organising linguistic data for use in dictionaries, language research, and computational applications. Wikidata Lexemes are entries in Wikidata used to represent lexical information in a structured form. They record data about words, including the language to which a word belongs, its part of speech, its meanings, and its inflected forms. For example, a noun entry may include information about grammatical gender and plural forms, while a verb entry may include conjugated forms. Lexemes provide a way of organizing linguistic data for use in dictionaries, language research, and computational linguistics applications.
To extract the information on the grammatical gender, we use Wikifunctions. Wikifunctions is a Wikimedia project that aims to create a collaboratively maintained library of functions. A function is a procedure that takes one or more inputs and produces an output. Examples include converting units, transforming singular words into plural forms, or carrying out simple calculations. Wikifunctions is intended to support the reuse of such functions across Wikimedia projects and other software environments. It’s also the foundation of Abstract Wikipedia. And, last, but not least, we can use these functions in remote calls from the command line.
With these building blocks in place we can have a program that answers the question of der, die, das for a given German noun. The code is available on Codeberg, but let’s go through the interesting parts of the program.
Please note that you have to edit the code and give your contact email address before running the program. We’ll be doing some calls to Wikimedia APIs, and these days that means that clients must provide a contact email address, typically as part of the User-Agent header. The address is generally not used for authentication or authorization, but for operational purposes, such as contacting the developer if an application causes excessive load, malfunctions, or otherwise affects the service. That’s a recent development. The reason why Wikimedia APIs became a tiny bit less open than a few years ago is of course the excessive data extractivism from AI companies. In 2025, 65% of Wikimedia’s most expensive traffic came from crawler bots. That’s why you have to give a contact email address for your API calls.
Now, let’s have a look at the code. First, let’s define some constants, including the API endpoints for Wikidata and Wikifunctions:
WIKIDATA_API = "https://www.wikidata.org/w/api.php"
WIKIFUNCTIONS_API = "https://www.wikifunctions.org/w/api.php"
# Wikifunction used to determine grammatical genders from a Wikidata lexeme.
FUNCTION_GENDERLIST_ID = "Z20616"
FUNCTION_GENDERLIST_PARAMETER = "Z20616K1"
# Wikifunction used to fetch a lexeme object by lexeme ID.
FUNCTION_FETCH_LEXEME_ID = "Z6825"
FUNCTION_FETCH_LEXEME_PARAMETER = "Z6825K1"
# Wikidata Lexeme Reference type
LEXEME_TYPE = "Z6095"
LEXEME_TYPE_PARAMETER = "Z6095K1"
# Wikidata item IDs for grammatical genders.
M = "Q499327" # masculine
F = "Q1775415" # feminine
N = "Q1775461" # neuter
What’s with the Q and Z? These are identifiers. Both Wikidata and Wikifunctions are multilingual projects and don’t contain articles with titles in e.g. English, but entities that are named with a letter followed by a number. Wikidata identifiers beginning with Q represent items, which describe entities such as people, places, concepts, or works. Identifiers beginning with L represent lexemes, which describe words and their linguistic properties, including forms and senses. Wikifunctions identifiers beginning with Z represent functions, types, implementations, and other objects defined in the Wikifunctions data model. To put it very simply: Something with a Q is a thing or concept, something with an L is a word, and something with a Z is a function we can call.
Let’s search for the lexemes now:
def find_lexemes(noun: str) -> list:
params = {
"action": "wbsearchentities",
"search": noun,
"type": "lexeme",
"language": "de",
"format": "json",
}
r = requests.get(WIKIDATA_API, params=params, headers=HEADERS)
r.raise_for_status()
data = r.json()
if not data["search"]:
raise RuntimeError(f"No lexeme found for '{noun}'")
items = []
for item in data["search"]:
if item["display"]["label"]["value"].lower() != noun.lower():
continue
if item["display"]["label"]["language"] != "de":
continue
items.append(item)
if items == []:
raise RuntimeError(f"No German lexeme found for '{noun}'")
else:
return(items)
The find_lexemes() function searches Wikidata for German lexemes matching a given noun using the wbsearchentities Action API. As much as I would love to use the Wikidata REST API, it doesn’t offer a way to search for lexemes, so using the older and slightly weird MediaWiki Action API it is. Our function sends an HTTP GET request with the search term, requests lexeme results in JSON format, and raises an error if the request fails or no results are returned. Since the API may return partial matches and lexemes from multiple languages, the function filters the results to keep only exact, case-insensitive matches whose display label is in German. If no entries remain after filtering, it raises a RuntimeError; otherwise, it returns the list of matching lexeme search result objects.
Once we have found the right lexemes (the thingies starting with a L) on Wikidata, we can run a function that gives us grammatical genders from a Wikidata lexeme, known on Wikifunctions as Z20616. Here’s the code for that:
def build_call(lexeme_id: str) -> dict:
function_call = {
"Z1K1":"Z7",
"Z7K1": FUNCTION_GENDERLIST_ID,
FUNCTION_GENDERLIST_PARAMETER:{
"Z1K1":"Z7",
"Z7K1":FUNCTION_FETCH_LEXEME_ID,
FUNCTION_FETCH_LEXEME_PARAMETER:{
"Z1K1": LEXEME_TYPE,
LEXEME_TYPE_PARAMETER:lexeme_id
}
}
}
return function_call
def run_function(call: dict) -> dict:
response = requests.get(
WIKIFUNCTIONS_API,
params={
"action": "wikifunctions_run",
"format": "json",
"formatversion": "2",
"function_call": json.dumps(call),
},
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
result = response.json()
if "error" in result:
raise RuntimeError(result["error"])
return result
Woah, there’s quite a lot going on in these two Python functions! First, there’s the build_call() function. It constructs a JSON-serializable Wikifunctions Z7 function call that represents the nested expression Z20616(Z6825(lexeme_id)). What’s a Z7 function call? Well, remember that entities in Wikifunctions have identifiers starting with Z? Z7 is the identifier for “call a function”. Calling a function is quite a basic thing you can do to a function, and thus it’s no wonder that Z7 is such a low (and lucky!) number. Our Z7 function call is applied to Z6825 (fetch Wikidata lexeme) which retrieves the Wikidata lexeme corresponding to the supplied lexeme identifier. Then the resulting lexeme object is passed to Z20616 (grammatical genders from Wikidata lexeme), which extracts its grammatical genders. What a beautifully nested function call!
What we’re essentially doing here, is constructing a dictionary (the Python data structure, not the book where you look up words). It’s a bit like filling in the blanks in a text template for a Python dictionary. The generated dictionary is designed to match the Wikifunctions data model and serves as the request payload for the API.
How to do that API call now that we have the payload? With the following function in the code snippet. The run_function() function executes our payload by sending it to the public Wikifunctions API as a wikifunctions_run request, serializing the function call to JSON and requesting the result in JSON format. It checks for HTTP errors, parses the returned JSON, raises a RuntimeError if the API reports an execution error, and otherwise returns the parsed response dictionary.
And that’s it! The complete program has a bit more stuff to make it a true command line tool and some more comments to make the code more readable, but that’s what you need to look up your der, die, das for your German nouns. Viel Spaß dabei!