惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
大猫的无限游戏
大猫的无限游戏
爱范儿
爱范儿
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Register - Security
The Register - Security
IT之家
IT之家
博客园_首页
Microsoft Security Blog
Microsoft Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
I
InfoQ
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
M
MIT News - Artificial intelligence
博客园 - Franky
C
Check Point Blog
T
The Blog of Author Tim Ferriss
V
Visual Studio Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
美团技术团队
The Cloudflare Blog
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
The GitHub Blog
The GitHub Blog
D
DataBreaches.Net
Google DeepMind News
Google DeepMind News
V
V2EX
aimingoo的专栏
aimingoo的专栏
GbyAI
GbyAI
G
Google Developers Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42
罗磊的独立博客
量子位
MongoDB | Blog
MongoDB | Blog
Last Week in AI
Last Week in AI
Stack Overflow Blog
Stack Overflow Blog
小众软件
小众软件
D
Docker
人人都是产品经理
人人都是产品经理

DigitalOcean Community Tutorials

It's Time to Break Up with Your Cloud: Why AI Teams are Switching We Built a Private-Document AI App to Test Platform Security. Here Is What We Could Actually Verify. PostgreSQL Explained: A Complete Beginner-to-Advanced Guide How To Install and Configure Postfix on Ubuntu How To Build a Web Application Using Flask in Python 3 Build AI Reading List with DigitalOcean Functions and Mistral How To Concatenate Strings in Python How to Allow MySQL Remote Access Securely How To Install and Use Docker on Rocky Linux How To Build a Multi-Agent AI System with Docker Agent DSPy Use Cases: Build Optimized LLM Pipelines How To Submit AJAX Forms with jQuery Build an AI-Powered GPU Fleet Optimizer with the DigitalOcean AI Platform ADK Monitor GPU Utilization in Real Time: A Complete Guide Reduce File Size of Images in Linux - CLI and GUI methods Reduce PDF File Size in Linux: Tools and Methods How To Set Up a Private Docker Registry on Ubuntu How To Troubleshoot Terraform: Errors and Fixes How to Use Go Modules Python Multiprocessing Example: Process, Pool & Queue Convert Class Components to Functional Components with React Hooks How To Install and Configure Ansible on Ubuntu LLM Tokenizers Simplified: BPE, SentencePiece, and More How To Monitor System Authentication Logs on Ubuntu How to Use Traceroute and MTR to Diagnose Network Issues How to Deploy Postgres to Kubernetes Cluster Importing Packages in Go: A Complete Guide Create RAID Arrays with mdadm on Ubuntu How To Make an HTTP Server in Go How To Set Up Time Synchronization on Ubuntu How To Use Struct Tags in Go apt-key Deprecation: Add Repositories with GPG on Ubuntu Linux ps Command: 20 Real-World Examples Python struct.pack and struct.unpack for Binary Data Deadlock in Java: Examples, Detection, and Prevention How To Use Find and Locate to Search for Files on Linux Structured Resume Skill Extraction Using Mistral-7B Inference How to Use the Python Main Function How to Set Up NemoClaw on a DigitalOcean Droplet with 1-Click Build an End-to-End RAG Pipeline for LLM Applications From Single to Multi-Agent Systems: Key Infrastructure Needs Back Up Data to Object Storage Using Restic How to Generate Videos with LTX-2.3 on DigitalOcean GPU Droplets How To Install LAMP Stack (Apache, MySQL, PHP) on Ubuntu How to Download Files with cURL How To Use Variadic Functions in Go Generate UUIDs with uuidgen on Linux How To Use EJS to Template Your Node Application How to Install Node.js on Ubuntu (Step-by-Step Guide) MongoDB Indexes: Improve Query Performance with Node.js LLM Tool Calling with DigitalOcean AI Platform and Databases What are Text Diffusion Models? - An Overview Crafting a Game from Scratch with GPT-5.4 Building Long-Term Memory in AI Agents with LangGraph and Mem0 How To Install PHP 7.4 and Set Up a Local Development Environment on Ubuntu 20.04 Build a GraphQL API in Go to Upload Files to Spaces How To Lint and Format Code with ESLint in Visual Studio Code Train YOLO26 for Retail Object Detection on DigitalOcean GPUs How To Work with JSON in MySQL How to Use the JavaScript .map() Method Building a Scalable App with MongoDB Using DigitalOcean's MCP Server How to Create an SSH Key in Linux: Easy Step-by-Step Guide Measure MySQL Query Performance with mysqlslap How To Use *args and **kwargs in Python 3 Nemotron 3 helped me find the perfect dish rack? A2A vs MCP - How These AI Agent Protocols Actually Differ How To Install and Manage Supervisor Docker Container Images with Watchtower on Ubuntu Getting Started with Qwen3.5 Vision-Language Models How To Create a New Sudo-Enabled User on Ubuntu How to Use Ansible to Install and Set Up Docker on Ubuntu How To Enable Remote Desktop Protocol Using xrdp on Ubuntu 22.04 How To Convert a String to a List in Python How To Check If a String Contains Another String in Python How to Read a Properties File in Python Python Command Line Arguments: sys.argv, argparse, getopt Mastering Grep command in Linux/Unix: A Beginner's Tutorial Understanding Python Data Types How to Implement a Stack in C With Code Examples Python os.system() vs subprocess: Run System Commands How To Install and Use Docker Compose on Ubuntu How to Add and Delete Users on Ubuntu How To Order Query Results in Laravel Eloquent How To Define and Use Handlers in Ansible Playbooks How To Install and Use SQLite on Ubuntu How To Install and Use Homebrew on macOS How To Manage DateTime with Carbon in Laravel and PHP How To Install Git on Ubuntu How To Install and Secure Redis on Ubuntu How To Build and Install Go Programs on Linux Using ldflags to Set Version Information for Go Applications How To Build a Node.js Application with Docker How To Add JavaScript to HTML How To Reset Your MySQL or MariaDB Root Password How To Add Images in Markdown How To Set Up a Production Elasticsearch Cluster with Ansible How To Set Up a Firewall Using firewalld on CentOS Understanding Systemd Units and Unit Files How To Set Up Replication in MySQL How To Use the .htaccess File
Python Find String in List: Methods and Examples
Pankaj Kumar · 2026-06-16 · via DigitalOcean Community Tutorials

Introduction

Finding a string in a list is one of the most common tasks in Python. You might do it to filter log data, check user input, look up configuration values, or work through text. The quickest way to check if a string is in a list is the in operator. It looks through the list one item at a time and returns True or False:

my_list = ["apple", "banana", "cherry"]
if "banana" in my_list:
    print("Found!")

Running this prints the following output:

Output

Found!

That one line answers the basic question, but real code often needs more. You might need the index of a match, all the positions where it matches, a substring match instead of an exact one, a case-insensitive search, or a pattern match using regular expressions.

This tutorial covers each approach with examples you can run, compares their speed and return types in one table, and shows three real-world cases (filtering logs, checking input, and matching file paths) so you can pick the right method for what you need. Every example runs in a standard Python 3.6+ environment (tested on the latest stable release, Python 3.14) and uses only the standard library, with no extra packages.

Key Takeaways

  • Use the in operator ("target" in my_list) for a fast, easy-to-read check; it returns True or False.
  • On a list, in checks for an exact match, not a partial one. So "app" in ["apple"] is False.
  • Use list.index("target") to get the position of the first match. Wrap it in a try/except ValueError block, since it raises an error when the string is not there.
  • To find all matching positions, use a list comprehension with enumerate(): [i for i, val in enumerate(my_list) if val == "target"].
  • To check if any element contains a substring, use any("sub" in item for item in my_list). To collect those elements, use a list comprehension.
  • Use next((item for item in my_list if "sub" in item), None) to get the first matching element (or a default) without checking the rest.
  • For case-insensitive matching, convert both sides with .lower(), or use re.search(pattern, item, re.IGNORECASE) for pattern searches.
  • Checking membership in a list is O(n) (it scans every item). If you search the same data many times, convert it to a set for O(1) average lookups, as noted in the Python Wiki time-complexity reference.
  • Use regular expressions only when you are matching a pattern (dates, extensions, IDs). For fixed text, plain in or str.find() is faster and easier to read.

Using the in operator to check if a string exists in a list

The in operator is the simplest and most common way to check whether a string is in a list, and it is the fastest choice for a one-time check. It returns True when a matching element exists and False when it does not. The matching not in operator checks that a string is missing.

l1 = ['A', 'B', 'C', 'D', 'A', 'A', 'C']

# Check whether a string is present in the list
if 'A' in l1:
    print('A is present in the list')

# Check whether a string is absent from the list
if 'X' not in l1:
    print('X is not present in the list')

This produces the following output:

Output

A is present in the list
X is not present in the list

The same pattern works well for checking user input. The following example asks the user for a value and reports whether it appears in the list:

l1 = ['A', 'B', 'C', 'D', 'A', 'A', 'C']
s = input('Please enter a character A-Z:\n')

if s in l1:
    print(f'{s} is present in the list')
else:
    print(f'{s} is not present in the list')

If the user types A, the program prints:

Output

Please enter a character A-Z:
A
A is present in the list

One thing to watch out for: on a list, in checks for an exact match, not a partial one. "app" in ["apple", "banana"] returns False because no element equals "app", even though "apple" starts with it. If you need to match a substring inside list elements, see the section on Searching for a substring within list elements. For more on the string formatting used here, see f-strings in Python.

Finding the index of a string with index()

When you need the position of a string instead of a yes/no answer, use the list index() method. It returns the index of the first match, counting from zero.

my_list = ["apple", "banana", "cherry"]
index = my_list.index("banana")
print(index)

This returns the position of the first match:

Output

1

The result 1 means "banana" is the second element, since list indexes start at 0. The key thing to remember is that index() raises a ValueError if the value is not there, so production code should wrap the call in a try/except block:

my_list = ["apple", "banana", "cherry"]
try:
    index = my_list.index("mango")
    print(f"Found at index {index}")
except ValueError:
    print("Not found")

Because "mango" is absent, the except branch runs and prints:

Output

Not found

Finding all indexes of a string in a list

The index() method only returns the first match. To get every position where a string appears, combine enumerate() with a list comprehension. This is a short, clean replacement for a manual while loop:

my_list = ["apple", "banana", "cherry", "banana"]
indexes = [i for i, val in enumerate(my_list) if val == "banana"]
print(indexes)

This returns a list of all matching indexes:

Output

[1, 3]

The comprehension checks each element and keeps the index when it matches the target. Learn more in the guides on list comprehensions and enumerate().

Searching for a substring within list elements

A common point of confusion is that in on a list checks for an exact match, when you may just want to know whether any element contains a target substring. The following patterns solve this and let you choose the result you need: the matching elements, a True/False answer, or just the first match.

Returning all elements that contain the substring

Use a list comprehension with the in operator (which does check for substrings inside individual strings) to collect every element that contains the target text:

my_list = ["apple", "banana", "cherry"]
matches = [item for item in my_list if "an" in item]
print(matches)

This keeps only the elements that contain "an":

Output

['banana']

Returning a boolean with any()

When you only need to know whether at least one element contains the substring, any() with a generator expression is the most efficient choice, since it stops at the first match:

my_list = ["apple.log", "banana.txt", "cherry.csv"]
has_log = any(".log" in item for item in my_list)
print(has_log)

This reports whether any element contains ".log":

Output

True

Returning the first match with next()

If you want only the first matching element and nothing else, next() over a generator expression returns it right away. It also takes a default value, which avoids a StopIteration error when there is no match:

my_list = ["app.log", "error.log", "data.csv"]
first_error = next((item for item in my_list if "error" in item), None)
print(first_error)

This returns the first element containing "error":

Output

error.log

Case-insensitive string search in a list

By default, every method above is case-sensitive, so "BANANA" will not match "banana". There are two reliable ways to make a search case-insensitive.

Normalizing with .lower()

The simplest approach is to convert both the target and each element to the same case with .lower() (or .upper()) before you compare them:

names = ["Alice", "BOB", "carol"]
target = "bob"
matches = [n for n in names if target.lower() in n.lower()]
print(matches)

This matches "BOB" regardless of case:

Output

['BOB']

Using re.IGNORECASE for pattern searches

When your search uses a pattern rather than a fixed string, pass the re.IGNORECASE flag to re.search() inside a comprehension. This is the cleaner choice when you also need regular-expression features:

import re

names = ["Alice", "BOB", "carol"]
matches = [n for n in names if re.search("bob", n, re.IGNORECASE)]
print(matches)

This returns the same case-insensitive result:

Output

['BOB']

Using regex to find strings in a list

Regular expressions are the right tool when you are searching for a pattern (a date, a file extension, an ID format) rather than a fixed string. The re module from the standard library handles these searches. For an introduction to the syntax, see An Introduction to Regular Expressions.

Filtering with re.search() and a list comprehension

re.search() returns a match object (which counts as True) when the pattern is found anywhere in the string, and None otherwise. Combine it with a comprehension to filter a list:

import re

logs = ["2024-01-01 OK", "2024-01-02 ERROR", "no date here"]
dated = [line for line in logs if re.search(r"\d{4}-\d{2}-\d{2}", line)]
print(dated)

This keeps only the lines that contain an ISO-style date:

Output

['2024-01-01 OK', '2024-01-02 ERROR']

Extracting matches with re.findall()

When you want the matched text itself rather than the whole element, re.findall() returns every match of the pattern in a string. Use a comprehension to combine the results across the list into one flat list:

import re

logs = ["2024-01-01 OK", "2024-01-02 ERROR", "no date here"]
dates = [match for line in logs for match in re.findall(r"\d{4}-\d{2}-\d{2}", line)]
print(dates)

This collects the date strings themselves:

Output

['2024-01-01', '2024-01-02']

When to use regex versus simple string methods

Use regex only when you truly need pattern matching. For an exact match, use the in operator or index(). For a fixed substring, use in on the element or str.find(). These plain methods are faster and much easier to read than a regular expression, and they avoid hard-to-spot bugs caused by unescaped special characters. Use regex when the target follows a flexible structure (for example, matching any .log or .txt extension, checking a date format, or pulling out numeric IDs) and a plain string cannot describe the rule.

Filtering a list by string pattern with list comprehension and filter()

Filtering a list down to the elements that match a string condition is so common that Python gives you two standard ways to do it. Both give the same result; the difference is mostly style.

Using a list comprehension

A list comprehension reads naturally and is the most common choice among Python developers for filtering a list:

words = ["cat", "dog", "caterpillar", "cobra"]
starts_with_cat = [w for w in words if w.startswith("cat")]
print(starts_with_cat)

This keeps the words that start with "cat":

Output

['cat', 'caterpillar']

Using filter() with a lambda

The built-in filter() function checks each element and returns an iterator, which you then turn into a list:

words = ["cat", "dog", "caterpillar", "cobra"]
starts_with_cat = list(filter(lambda w: w.startswith("cat"), words))
print(starts_with_cat)

This produces the identical result:

Output

['cat', 'caterpillar']

As a rule of thumb, use the list comprehension in most cases because it is easier to read. filter() can use slightly less memory for very large inputs because it processes items only as needed, and it reads cleanly when you already have a named function to pass instead of a lambda.

Counting occurrences with count()

When you need to know how many times a string appears rather than where it is, the list count() method returns the number of matches. A result of 0 means the string is not in the list.

l1 = ['A', 'B', 'C', 'D', 'A', 'A', 'C']
s = 'A'
count = l1.count(s)
if count > 0:
    print(f'{s} is present in the list {count} times.')

This reports the frequency of 'A':

Output

A is present in the list 3 times.

Using a loop (alternative approach)

The built-in methods cover almost every case, but a plain loop is worth knowing when you need to do extra work on each element during the search or want more control over how you loop through the list.

my_list = ["apple", "banana", "cherry", "banana"]
found = False
for item in my_list:
    if item == "banana":
        found = True
        break
print(found)

This stops at the first match and prints:

Output

True

This approach is longer and usually slower than the in operator for simple checks, so use the built-in methods unless the loop body does something a comprehension cannot express cleanly.

Method comparison: which approach should you use?

Each method fits a different goal. The following table matches common use cases to the recommended approach, what it returns, and its time complexity. The complexity figures follow the official Python Wiki time-complexity reference, where n is the number of elements in the list.

Method Use case Returns Time complexity Notes
in operator Check if an exact element exists bool O(n) Most readable membership test; exact match only
not in operator Check that an element is absent bool O(n) Negative membership test
list.index() Position of the first exact match int (or ValueError) O(n) Wrap in try/except ValueError
enumerate() + comprehension All positions of an exact match list[int] O(n) Returns every matching index
List comprehension with in All elements containing a substring list[str] O(n) Substring match on each element
any() Whether any element contains a substring bool O(n) Short-circuits at the first match
next() First element containing a substring str (or default) O(n) Returns one match, then stops
count() Number of occurrences int O(n) 0 means not present
re.search() in comprehension Pattern-based filtering list[str] O(n * m) m is pattern cost; use only for patterns
set membership (in) Repeated exact-match lookups bool O(1) average No substring matching; build the set once

Handling large datasets efficiently

When you work with large datasets, the cost of an O(n) scan adds up if you search the same data many times. A few changes to how you store the data can cut that cost a lot.

The most effective change is to use a set for repeated checks. Converting a list to a set with set(my_list) gives O(1) average lookups instead of O(n), because sets use hash tables instead of arrays. The trade-off is that sets do not keep order, drop duplicates, and cannot do substring matching, so they fit exact-match checks rather than pattern searches.

allowed = ["admin", "editor", "viewer", "guest"]
allowed_set = set(allowed)
print("editor" in allowed_set)

Because the lookup uses a hash table, this returns instantly even for very large collections:

Output

True

Two more options are worth knowing. Dictionaries give the same O(1) average lookup as sets, but they also map each key to a value, which is ideal when you need extra data and not just a yes/no answer. For data that is already sorted, the standard library bisect module finds elements in O(log n) time using binary search, which is faster than a full scan when you can keep the list in order.

Real-world examples

The patterns above are common in real Python code. Here are three practical examples of how you might use these string search methods in everyday tasks.

Filtering log lines that contain an error string

Scanning logs for lines that mention an error is a classic substring-filtering task, and a list comprehension solves it cleanly:

log_lines = [
    "INFO startup complete",
    "ERROR database connection failed",
    "WARNING disk almost full",
    "ERROR timeout while reading socket",
]
errors = [line for line in log_lines if "ERROR" in line]
print(errors)

This isolates the two error lines:

Output

['ERROR database connection failed', 'ERROR timeout while reading socket']

Checking user input against an allowed values list

Checking that a submitted value is one of a known set is an exact-match check. Building a set first keeps the check fast even when you validate many inputs:

allowed_roles = {"admin", "editor", "viewer"}
submitted = "editor"
if submitted in allowed_roles:
    print(f"{submitted} is a valid role")
else:
    print(f"{submitted} is not allowed")

Because "editor" is in the set, this prints:

Output

editor is a valid role

Extracting file paths that match a pattern

Picking files by extension or naming convention is a job for regular expressions, since the target is a pattern rather than a fixed string:

import re

paths = ["report.csv", "image.png", "data.csv", "notes.txt"]
csv_files = [p for p in paths if re.search(r"\.csv$", p)]
print(csv_files)

This keeps only the paths ending in .csv:

Output

['report.csv', 'data.csv']

FAQs

1. How to search a string in a list in Python?

The fastest way to search for an exact string is the in operator, which returns True or False. Use it whenever you only need to know whether the string is there:

my_list = ["apple", "banana", "cherry"]
if "banana" in my_list:
    print("Found!")

If you need the position instead of a True/False answer, use index() inside a try/except block so a missing value does not crash your program:

my_list = ["apple", "banana", "cherry"]
try:
    index = my_list.index("banana")
    print(f"Found at index {index}")
except ValueError:
    print("Not found")

2. How to check if a string exists in a list in Python?

Use the in operator, which is the standard way to check membership and returns True or False directly. It reads almost like plain English and is the best default for a yes/no check:

my_list = ["apple", "banana", "cherry"]
if "banana" in my_list:
    print("Exists")
else:
    print("Does not exist")

3. How to find part of a string in a list?

To find elements that contain a substring (rather than equal it exactly), use a list comprehension with the in operator, because in checks for substrings inside individual strings. This returns every element that contains the target text:

my_list = ["apple", "banana", "cherry"]
part = "an"
filtered_list = [item for item in my_list if part in item]
print(filtered_list)  # ['banana']

For a simple yes/no answer instead of the matching items, use any(part in item for item in my_list).

4. How do I find a specific string in Python?

To find a specific string and get its position, use the index() method, which returns the index of the first match and raises ValueError if the string is missing. Always wrap it in try/except so a missing value is handled gracefully:

my_list = ["apple", "banana", "cherry"]
try:
    index = my_list.index("banana")
    print(f"Found at index {index}")
except ValueError:
    print("Not found")

5. How to count occurrences of a string in a list?

Use the list count() method, which returns how many times a value appears. A return value of 0 means the string is not in the list:

my_list = ["apple", "banana", "cherry", "banana"]
count = my_list.count("banana")
print(f"Count: {count}")  # 2

6. How to find all indexes of a string in a list?

Use a list comprehension with enumerate() to collect every index where the string appears, since index() returns only the first one. This gives you a list of all matching positions:

my_list = ["apple", "banana", "cherry", "banana"]
indexes = [i for i, val in enumerate(my_list) if val == "banana"]
print(indexes)  # [1, 3]

7. How do I perform a case-insensitive string search in a Python list?

The simplest approach is to convert both the target and each element to the same case with .lower() (or .upper()) before comparing them. This matches no matter how the text is capitalized:

items = ["Apple", "BANANA", "Cherry"]
target = "banana"
matches = [item for item in items if target.lower() in item.lower()]
print(matches)  # ['BANANA']

When the search uses a pattern, use re.search("target", item, re.IGNORECASE) inside a comprehension instead.

8. When should I use regex to find strings in a list?

Use regular expressions when the target is a pattern rather than fixed text, such as a date format, a file extension, or a structured ID. For exact or simple substring matching, the plain in operator or str.find() is faster and easier to read:

import re
files = ["a.log", "b.txt", "c.log"]
logs = [f for f in files if re.search(r"\.log$", f)]
print(logs)  # ['a.log', 'c.log']

9. What is the time complexity of finding a string in a Python list?

Methods that scan the whole list, such as the in operator, index(), count(), and list comprehensions, are O(n), which means the cost grows with the length of the list. If you need to run many checks on the same data, convert the list to a set once for O(1) average lookups, as documented in the Python Wiki time-complexity reference. Keep in mind that sets support only exact matching, not substring searches.

10. What is the difference between list.index() and list.find() in Python?

Python lists do not have a find() method, so calling my_list.find(...) raises an AttributeError. The find() method belongs to strings and returns -1 when the substring is missing. For lists, use index() (which raises ValueError if the value is missing) or a list comprehension when you need to find elements:

text = "banana"
print(text.find("na"))   # 2  (string method)

fruits = ["apple", "banana"]
print(fruits.index("banana"))  # 1  (list method)

Conclusion

In this tutorial, you learned how to find a string in a Python list using the right tool for each goal: the in operator for fast membership checks, index() and enumerate() for positions, list comprehensions and any()/next() for substring searches, .lower() and re.IGNORECASE for case-insensitive matching, regular expressions for pattern matching, and filter() as an alternative to comprehensions. You also saw how a set turns repeated O(n) scans into O(1) lookups, and how these patterns apply to log filtering, input validation, and file-path matching.

For most situations, the in operator is the best default. Use index() when you need a position, comprehensions when you need substring matches, and regular expressions only when you are matching a pattern.

Further reading

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.