{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## CS 200: Regular Expressions in Python\n", "\n", "This notebook mirrors the Google Python Course: Regular Expressions\n", "\n", "\n", "\n", "Regular expressions comprise a pattern matching language. They also are a formal\n", "grammar that is a proper subset of context free grammars. In addition, \n", "regular expressions are provably equivalent to deterministic finite state automata, aka deterministic finite \n", "state acceptors or DFA's. \n", "\n", "The functions defined in this notebook are found in retest.py.\n", "\n", "Python implements regular expression pattern matching in the re module." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import re" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['A',\n", " 'ASCII',\n", " 'DEBUG',\n", " 'DOTALL',\n", " 'I',\n", " 'IGNORECASE',\n", " 'L',\n", " 'LOCALE',\n", " 'M',\n", " 'MULTILINE',\n", " 'Match',\n", " 'Pattern',\n", " 'RegexFlag',\n", " 'S',\n", " 'Scanner',\n", " 'T',\n", " 'TEMPLATE',\n", " 'U',\n", " 'UNICODE',\n", " 'VERBOSE',\n", " 'X',\n", " '_MAXCACHE',\n", " '__all__',\n", " '__builtins__',\n", " '__cached__',\n", " '__doc__',\n", " '__file__',\n", " '__loader__',\n", " '__name__',\n", " '__package__',\n", " '__spec__',\n", " '__version__',\n", " '_cache',\n", " '_compile',\n", " '_compile_repl',\n", " '_expand',\n", " '_locale',\n", " '_pickle',\n", " '_special_chars_map',\n", " '_subx',\n", " 'compile',\n", " 'copyreg',\n", " 'enum',\n", " 'error',\n", " 'escape',\n", " 'findall',\n", " 'finditer',\n", " 'fullmatch',\n", " 'functools',\n", " 'match',\n", " 'purge',\n", " 'search',\n", " 'split',\n", " 'sre_compile',\n", " 'sre_parse',\n", " 'sub',\n", " 'subn',\n", " 'template']" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dir(re)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A pattern is a string containing either characters or meta-characters.\n", "\n", "The re method search(pattern, string) performs a pattern match." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "pat = 'xxx'" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "r = re.search(pat, ' x ')" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "r ### there is no match" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "r2 = re.search(pat, 'xxxyyyxxx')" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "r2 ### there is a match!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The power of regular expressions is that they can specify patterns, not just fixed characters. Here are the most basic patterns which match single characters:\n", "\n", "\n", "#### Examples" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('ab.','abxddd')" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "re.search('ab..', 'abc') ## no match" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\w\\w\\w','ab345')" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "re.search('\\w\\w\\w','ab dc') ## no match" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\w\\w\\W\\w','ab dc')" ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [], "source": [ "re.search(r'\\w\\w\\b\\w\\w','ab dc')" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search(r'\\b\\w\\w\\b','ab dc')" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search(r'\\bfoo\\b', 'foo')" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search(r'\\bfoo\\b', 'foo.')" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search(r'\\bfoo\\b', '(foo)')" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search(r'\\bfoo\\b', 'bar foo baz')" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "re.search(r'\\bfoo\\b', 'foobar')" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "re.search(r'\\bfoo\\b', 'foo3')" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\w\\w\\s\\w\\w','ab dc')" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\S\\S\\s\\S\\S', 'ab dc')" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\d\\d\\d','12345')" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\d\\d\\d\\D\\d\\d\\d','123 456')" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\d\\d\\d','x123456')" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "re.search('^\\d\\d\\d','x123456') # no match" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('^\\d\\d\\d','123456')" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\d\\d\\d$','123456')" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "re.search('\\d\\d\\d$','123456 ') ## no match" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\d\\d\\d$','123456 '.strip())" ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [], "source": [ "re.search('ab\\.','abc') ## no match" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('ab\\.', 'ab...')" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('(cat|dog)','my cat')" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('(cat|dog)', 'your dog')" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('^(a|e|i|o|u)123', 'e123')" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('^[aeiou]123', 'o123456')" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('^[^aeiou]123','x123456')" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('^[a-z]123','x123456')" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('^[^a-z]123','X123456')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Repetition\n", "\n", "Things get more interesting when you use + and * to specify repetition in the pattern\n", "
    \n", "
  • + -- 1 or more occurrences of the pattern to its left, e.g. 'i+' = one or more i's\n", "\n", "
  • * -- 0 or more occurrences of the pattern to its left\n", "\n", "
  • ? -- match 0 or 1 occurrences of the pattern to its left\n", "
\n", "\n", "### Leftmost & Largest\n", "\n", "First the search finds the leftmost match for the pattern, and second it tries to use up as much of the string as possible -- i.e. + and * go as far as possible (the + and \\* are said to be \"greedy\").\n", "\n", "### Examples" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.match('pi+g', 'piiiig') # one or more i's, as many as possible" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finds the first/leftmost solution, and within it drives the +\n", "as far as possible (aka 'leftmost and largest').\n", "\n", "In this example, note that it does not get to the second set of i's." ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [], "source": [ "match = re.search(r'i+', 'piigiiii')" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "match" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\\s* = zero or more whitespace chars\n", "\n", "Here look for 3 digits, possibly separated by whitespace." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search('\\d\\s*\\d\\s*\\d', 'xx1 2 3xx')" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search(r'\\d\\s*\\d\\s*\\d', 'xx12 3xx')" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search(r'\\d\\s*\\d\\s*\\d', 'xx12 3xx')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "^ = matches the start of string, so the first case fails:" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "re.search(r'^b\\w+', 'foobar') " ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.search(r'b\\w+', 'foobar') " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Square brackets indicate a character class. e.g. [aeiou] matches any vowel" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.match('^[aeiou]+$','aaaaeee')" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "re.match('^[aeiou]+$','aaaaxxxeee')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Testing regular expressions" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "def retest(str = 'an example word:cat!!'):\n", " pat = 'word:\\w\\w\\w'\n", " match = re.search(pat, str)\n", " # If-statement after search() tests if it succeeded\n", " if match:\n", " print ('found: {}'.format( match.group()))\n", " else:\n", " print ('Did not find {} in {}'.format(pat, str))" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "found: word:cat\n" ] } ], "source": [ "retest()" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Did not find word:\\w\\w\\w in hello world\n" ] } ], "source": [ "retest('hello world')" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "found: word:123\n" ] } ], "source": [ "retest('this is word:123456')" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [], "source": [ "patterns = ['aaa', # contains aaa\n", " 'abc', # contains abc\n", " '...', # contains three characters\n", " '^...$', # starts with three characters\n", " '\\.\\.\\.', # contains three periods\n", " 'abd', # contains abd\n", " '^[aeiou]*$', # contains only vowels\n", " '^[^aeiou]*$', # contains only NON vowels\n", " '\\w\\W\\w', # two word characters separated by a non-word char\n", " '\\w\\w\\w', # three word characters \n", " '^\\d+$', # contains only decimal digits\n", " '^[0-7]+$', # contains only octal digits\n", " '^[0-9A-Fa-f]+$', # contains only hexadecimal digits\n", " '^[a-z]*$', # contains only lower case letters\n", " '^\\s+', # starts with a whitespace char\n", " '^\\d\\s?', # starts with a digit followed by zero or one space\n", " '\\w+@\\w+', # match email address\n", " '(b)*(a)*(c)',\n", " '^b*a*c$',\n", " '^(b|c)*(a|b)*$',\n", " '^bb*(ab|ba)*|(bbc|cbc)*$',\n", " '^(ab|ba|cb|bc|ca|ac)*$',\n", " '(bc|bcc)(bac|cba)(cba|aa)',\n", " '\\AThe', # \\A is beginning of string\n", "]" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "def retest2(patlist = patterns):\n", " while (True):\n", " str = input(\"\\nEnter string: \")\n", " if str == \"quit\": break\n", " for pat in patlist:\n", " match = re.search(pat, str)\n", " # match = re.match(pat, str)\n", " if match:\n", " print ('Matched: {} with: {}'.format(pat, match.group(0)))\n", " else:\n", " print ('Did not find {} in {}'.format(pat, str))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Enter string: abc\n", "Did not find aaa in abc\n", "Matched: abc with: abc\n", "Matched: ... with: abc\n", "Matched: ^...$ with: abc\n", "Did not find \\.\\.\\. in abc\n", "Did not find abd in abc\n", "Did not find ^[aeiou]*$ in abc\n", "Did not find ^[^aeiou]*$ in abc\n", "Did not find \\w\\W\\w in abc\n", "Matched: \\w\\w\\w with: abc\n", "Did not find ^\\d+$ in abc\n", "Did not find ^[0-7]+$ in abc\n", "Matched: ^[0-9A-Fa-f]+$ with: abc\n", "Matched: ^[a-z]*$ with: abc\n", "Did not find ^\\s+ in abc\n", "Did not find ^\\d\\s? in abc\n", "Did not find \\w+@\\w+ in abc\n", "Matched: (b)*(a)*(c) with: bc\n", "Did not find ^b*a*c$ in abc\n", "Did not find ^(b|c)*(a|b)*$ in abc\n", "Matched: ^bb*(ab|ba)*|(bbc|cbc)*$ with: \n", "Did not find ^(ab|ba|cb|bc|ca|ac)*$ in abc\n", "Did not find (bc|bcc)(bac|cba)(cba|aa) in abc\n", "Did not find \\AThe in abc\n", "\n", "Enter string: joe@yale\n", "Did not find aaa in joe@yale\n", "Did not find abc in joe@yale\n", "Matched: ... with: joe\n", "Did not find ^...$ in joe@yale\n", "Did not find \\.\\.\\. in joe@yale\n", "Did not find abd in joe@yale\n", "Did not find ^[aeiou]*$ in joe@yale\n", "Did not find ^[^aeiou]*$ in joe@yale\n", "Matched: \\w\\W\\w with: e@y\n", "Matched: \\w\\w\\w with: joe\n", "Did not find ^\\d+$ in joe@yale\n", "Did not find ^[0-7]+$ in joe@yale\n", "Did not find ^[0-9A-Fa-f]+$ in joe@yale\n", "Did not find ^[a-z]*$ in joe@yale\n", "Did not find ^\\s+ in joe@yale\n", "Did not find ^\\d\\s? in joe@yale\n", "Matched: \\w+@\\w+ with: joe@yale\n", "Did not find (b)*(a)*(c) in joe@yale\n", "Did not find ^b*a*c$ in joe@yale\n", "Did not find ^(b|c)*(a|b)*$ in joe@yale\n", "Matched: ^bb*(ab|ba)*|(bbc|cbc)*$ with: \n", "Did not find ^(ab|ba|cb|bc|ca|ac)*$ in joe@yale\n", "Did not find (bc|bcc)(bac|cba)(cba|aa) in joe@yale\n", "Did not find \\AThe in joe@yale\n" ] } ], "source": [ "retest2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

Group Matching

" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def retest3(patlist = ['(\\w+)@(\\w+)', '^(\\d\\d\\d).(\\d\\d\\d)']):\n", " while (True):\n", " str = input(\"\\nEnter string: \")\n", " if str == \"quit\": break\n", " for pat in patlist:\n", " match = re.search(pat, str)\n", " if match:\n", " print ('Matched: {} group 1: {} group 2: {}'.format(pat, match.group(1), match.group(2)))\n", " else:\n", " print ('Did not find {} in {}'.format(pat, str))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "retest3()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Findall" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "def retest4(patlist = patterns):\n", " while (True):\n", " str = input(\"\\nEnter string: \")\n", " if str == \"quit\": break\n", " for pat in patlist:\n", " match = re.findall(pat, str)\n", " if match:\n", " print ('Total matches for {}: {}'.format(pat, len(match)))\n", " else:\n", " print ('Did not find {} in {}'.format(pat, str))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Enter string: aaaaaa\n", "Total matches for aaa: 2\n", "Did not find abc in aaaaaa\n", "Total matches for ...: 2\n", "Did not find ^...$ in aaaaaa\n", "Did not find \\.\\.\\. in aaaaaa\n", "Did not find abd in aaaaaa\n", "Total matches for ^[aeiou]*$: 1\n", "Did not find ^[^aeiou]*$ in aaaaaa\n", "Did not find \\w\\W\\w in aaaaaa\n", "Total matches for \\w\\w\\w: 2\n", "Did not find ^\\d+$ in aaaaaa\n", "Did not find ^[0-7]+$ in aaaaaa\n", "Total matches for ^[0-9A-Fa-f]+$: 1\n", "Total matches for ^[a-z]*$: 1\n", "Did not find ^\\s+ in aaaaaa\n", "Did not find ^\\d\\s? in aaaaaa\n", "Did not find \\w+@\\w+ in aaaaaa\n", "Did not find (b)*(a)*(c) in aaaaaa\n", "Did not find ^b*a*c$ in aaaaaa\n", "Total matches for ^(b|c)*(a|b)*$: 1\n", "Total matches for ^bb*(ab|ba)*|(bbc|cbc)*$: 1\n", "Did not find ^(ab|ba|cb|bc|ca|ac)*$ in aaaaaa\n", "Did not find (bc|bcc)(bac|cba)(cba|aa) in aaaaaa\n", "Did not find \\AThe in aaaaaa\n" ] } ], "source": [ "retest4()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "End of regular expressions notebook." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.7" } }, "nbformat": 4, "nbformat_minor": 4 }