diff --git a/examples/plural2.py b/examples/plural2.py index 1852804..82d8e21 100644 --- a/examples/plural2.py +++ b/examples/plural2.py @@ -31,11 +31,11 @@ def match_default(noun): def apply_default(noun): return noun + 's' -rules = [[match_sxz, apply_sxz], - [match_h, apply_h], - [match_y, apply_y], - [match_default, apply_default] - ] +rules = ((match_sxz, apply_sxz), + (match_h, apply_h), + (match_y, apply_y), + (match_default, apply_default) + ) def plural(noun): for matches_rule, apply_rule in rules: diff --git a/examples/plural3.py b/examples/plural3.py index 4029495..80c38b7 100644 --- a/examples/plural3.py +++ b/examples/plural3.py @@ -12,15 +12,15 @@ def build_match_and_apply_functions(pattern, search, replace): return re.search(pattern, word) def apply_rule(word): return re.sub(search, replace, word) - return [matches_rule, apply_rule] + return (matches_rule, apply_rule) patterns = \ - [ - ['[sxz]$', '$', 'es'], - ['[^aeioudgkprt]h$', '$', 'es'], - ['(qu|[^aeiou])y$', 'y$', 'ies'], - ['$', '$', 's'] - ] + ( + ('[sxz]$', '$', 'es'), + ('[^aeioudgkprt]h$', '$', 'es'), + ('(qu|[^aeiou])y$', 'y$', 'ies'), + ('$', '$', 's') + ) rules = [build_match_and_apply_functions(pattern, search, replace) for (pattern, search, replace) in patterns] diff --git a/generators.html b/generators.html index 65e3bad..0cad642 100644 --- a/generators.html +++ b/generators.html @@ -152,11 +152,11 @@ def match_default(noun): def apply_default(noun): return noun + 's' -rules = [[match_sxz, apply_sxz], ③ - [match_h, apply_h], - [match_y, apply_y], - [match_default, apply_default] - ] +rules = ((match_sxz, apply_sxz), ③ + (match_h, apply_h), + (match_y, apply_y), + (match_default, apply_default) + ) def plural(noun): for matches_rule, apply_rule in rules: ④ @@ -169,7 +169,7 @@ def plural(noun):
plural() function can be reduced to a few lines of code. Using a for loop, you can pull out the match and apply rules two at a time (one match, one apply) from the rules structure. On the first iteration of the for loop, matches_rule will get match_sxz, and apply_rule will get apply_sxz. On the second iteration (assuming you get that far), matches_rule will be assigned match_h, and apply_rule will be assigned apply_h. The function is guaranteed to return something eventually, because the final match rule (match_default) simply returns True, meaning the corresponding apply rule (apply_default) will always be applied.
-
+
The reason this technique works is that everything in Python is an object, including functions. The rules data structure contains functions — not names of functions, but actual function objects. When they get assigned in the for loop, then matches_rule and apply_rule are actual functions that you can call. On the first iteration of the for loop, this is equivalent to calling matches_sxz(noun), and if it returns a match, calling apply_sxz(noun).
If this additional level of abstraction is confusing, try unrolling the function to see the equivalence. The entire for loop is equivalent to the following:
@@ -185,7 +185,7 @@ def plural(noun):
if match_default(noun):
return apply_default(noun)
-
The benefit here is that the plural() function is now simplified. It takes a list of rules, defined elsewhere, and iterates through them in a generic fashion.
+
The benefit here is that the plural() function is now simplified. It takes a sequence of rules, defined elsewhere, and iterates through them in a generic fashion.
The rules could be defined anywhere, in any way. The plural() function doesn’t care.
-
Now, was adding this level of abstraction worth it? Well, not yet. Let’s consider what it would take to add a new rule to the function. In the first example, it would require adding an if statement to the plural() function. In this second example, it would require adding two functions, match_foo() and apply_foo(), and then updating the rules list to specify where in the order the new match and apply functions should be called relative to the other rules.
+
Now, was adding this level of abstraction worth it? Well, not yet. Let’s consider what it would take to add a new rule to the function. In the first example, it would require adding an if statement to the plural() function. In this second example, it would require adding two functions, match_foo() and apply_foo(), and then updating the rules sequence to specify where in the order the new match and apply functions should be called relative to the other rules.
But this is really just a stepping stone to the next section. Let’s move on… @@ -203,7 +203,7 @@ def plural(noun):
Defining separate named functions for each match and apply rule isn’t really necessary. You never call them directly; you add them to the rules list and call them through there. Furthermore, each function follows one of two patterns. All the match functions call re.search(), and all the apply functions call re.sub(). Let’s factor out the patterns so that defining new rules can be easier.
+
Defining separate named functions for each match and apply rule isn’t really necessary. You never call them directly; you add them to the rules sequence and call them through there. Furthermore, each function follows one of two patterns. All the match functions call re.search(), and all the apply functions call re.sub(). Let’s factor out the patterns so that defining new rules can be easier.
import re
@@ -213,27 +213,27 @@ def build_match_and_apply_functions(pattern, search, replace):
return re.search(pattern, word)
def apply_rule(word): ②
return re.sub(search, replace, word)
- return [matches_rule, apply_rule] ③
+ return (matches_rule, apply_rule) ③
build_match_and_apply_functions() is a function that builds other functions dynamically. It takes pattern, search and replace, then defines a matches_rule() function which calls re.search() with the pattern that was passed to the build_match_and_apply_functions() function, and the word that was passed to the matches_rule() function you’re building. Whoa.
re.sub() with the search and replace parameters that were passed to the build_match_and_apply_functions() function, and the word that was passed to the apply_rule() function you’re building. This technique of using the values of outside parameters within a dynamic function is called closures. You’re essentially defining constants within the apply function you’re building: it takes one parameter (word), but it then acts on that plus two other values (search and replace) which were set when you defined the apply function.
-build_match_and_apply_functions() function returns a list of two values: the two functions you just created. The constants you defined within those functions (pattern within the match_rule() function, and search and replace within the apply_rule() function) stay with those functions, even after you return from build_match_and_apply_functions(). That’s insanely cool.
+build_match_and_apply_functions() function returns a tuple of two values: the two functions you just created. The constants you defined within those functions (pattern within the match_rule() function, and search and replace within the apply_rule() function) stay with those functions, even after you return from build_match_and_apply_functions(). That’s insanely cool.
If this is incredibly confusing (and it should be, this is weird stuff), it may become clearer when you see how to use it.
patterns = \ ①
- [
- ['[sxz]$', '$', 'es'],
- ['[^aeioudgkprt]h$', '$', 'es'],
- ['(qu|[^aeiou])y$', 'y$', 'ies'],
- ['$', '$', 's']
- ]
+ (
+ ('[sxz]$', '$', 'es'),
+ ('[^aeioudgkprt]h$', '$', 'es'),
+ ('(qu|[^aeiou])y$', 'y$', 'ies'),
+ ('$', '$', 's')
+ )
rules = [build_match_and_apply_functions(pattern, search, replace) ②
for (pattern, search, replace) in patterns]
re.search() to see if this rule matches. The second and third strings in each group are the search and replace expressions you would use in re.sub() to actually apply the rule to turn a noun into its plural.
-build_match_and_apply_functions() function, which just happens to take three strings as parameters and return a list of two functions. This means that rules ends up being exactly the same as the previous example: a list of lists, where each (inner) list is a pair of functions. The first function is the match function that calls re.search(), and the second function is the apply function that calls re.sub().
+re.search() to see if this rule matches. The second and third strings in each group are the search and replace expressions you would use in re.sub() to actually apply the rule to turn a noun into its plural.
+build_match_and_apply_functions() function, which just happens to take three strings as parameters and return a tuple of two functions. This means that rules ends up being functionally equivalent to the previous example: a list of tuples, where each inner tuple is a pair of functions. The first function is the match function that calls re.search(), and the second function is the apply function that calls re.sub().
Rounding out this version of the script is the main entry point, the plural() function.