diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj
index 1a155e9..4920133 100644
--- a/src/Fable.Python.fsproj
+++ b/src/Fable.Python.fsproj
@@ -25,6 +25,7 @@
+
diff --git a/src/stdlib/Functools.fs b/src/stdlib/Functools.fs
new file mode 100644
index 0000000..f8b8db0
--- /dev/null
+++ b/src/stdlib/Functools.fs
@@ -0,0 +1,44 @@
+/// Type bindings for Python functools module: https://docs.python.org/3/library/functools.html
+module Fable.Python.Functools
+
+open Fable.Core
+
+// fsharplint:disable MemberNames
+
+[]
+type IExports =
+ // ========================================================================
+ // Higher-order functions
+ // ========================================================================
+
+ /// Apply a function of two arguments cumulatively to the items of an iterable,
+ /// reducing it to a single value (fold-left without a seed).
+ /// See https://docs.python.org/3/library/functools.html#functools.reduce
+ abstract reduce: func: System.Func<'T, 'T, 'T> * iterable: 'T seq -> 'T
+
+ /// Apply a function of two arguments cumulatively to the items of an iterable,
+ /// starting with the initializer as the seed value (fold-left with a seed).
+ /// See https://docs.python.org/3/library/functools.html#functools.reduce
+ []
+ abstract reduce: func: System.Func<'State, 'T, 'State> * iterable: 'T seq * initializer: 'State -> 'State
+
+ // ========================================================================
+ // Caching decorators
+ // ========================================================================
+
+ /// Wrap func with an LRU (least-recently-used) cache of at most maxsize entries.
+ /// Returns a memoised callable with the same signature as func.
+ /// Requires Python 3.8+.
+ /// See https://docs.python.org/3/library/functools.html#functools.lru_cache
+ []
+ abstract lruCache: maxsize: int * func: ('T -> 'R) -> ('T -> 'R)
+
+ /// Wrap func with an unbounded cache (equivalent to lru_cache(maxsize=None)).
+ /// Requires Python 3.9+.
+ /// See https://docs.python.org/3/library/functools.html#functools.cache
+ []
+ abstract cache: func: ('T -> 'R) -> ('T -> 'R)
+
+/// Higher-order functions and operations on callable objects
+[]
+let functools: IExports = nativeOnly
diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj
index 3c439a7..f37dca6 100644
--- a/test/Fable.Python.Test.fsproj
+++ b/test/Fable.Python.Test.fsproj
@@ -18,6 +18,7 @@
+
diff --git a/test/TestFunctools.fs b/test/TestFunctools.fs
new file mode 100644
index 0000000..a0f9bb1
--- /dev/null
+++ b/test/TestFunctools.fs
@@ -0,0 +1,48 @@
+module Fable.Python.Tests.Functools
+
+open Fable.Python.Testing
+open Fable.Python.Functools
+
+[]
+let ``test reduce sum works`` () =
+ functools.reduce ((fun a b -> a + b), [ 1; 2; 3; 4; 5 ])
+ |> equal 15
+
+[]
+let ``test reduce product works`` () =
+ functools.reduce ((fun a b -> a * b), [ 1; 2; 3; 4; 5 ])
+ |> equal 120
+
+[]
+let ``test reduce with initializer works`` () =
+ functools.reduce ((fun acc x -> acc + x), [ 1; 2; 3 ], 10)
+ |> equal 16
+
+[]
+let ``test reduce string fold with initializer works`` () =
+ functools.reduce ((fun acc s -> acc + s), [ "b"; "c"; "d" ], "a")
+ |> equal "abcd"
+
+[]
+let ``test lruCache memoises results`` () =
+ let callCount = ResizeArray()
+ let expensive (x: int) =
+ callCount.Add x
+ x * x
+ let cached = functools.lruCache (128, expensive)
+ cached 5 |> equal 25
+ cached 5 |> equal 25
+ cached 3 |> equal 9
+ callCount.Count |> equal 2
+
+[]
+let ``test cache memoises results`` () =
+ let callCount = ResizeArray()
+ let expensive (x: int) =
+ callCount.Add x
+ x * 2
+ let cached = functools.cache expensive
+ cached 7 |> equal 14
+ cached 7 |> equal 14
+ cached 4 |> equal 8
+ callCount.Count |> equal 2