diff --git a/cmake/modules/DunePerftoolMacros.cmake b/cmake/modules/DunePerftoolMacros.cmake
index 0b9e63eb51ed57166004e16501fece7411c997ed..071901bbbbf327d50665085512ad5435438596fd 100644
--- a/cmake/modules/DunePerftoolMacros.cmake
+++ b/cmake/modules/DunePerftoolMacros.cmake
@@ -114,7 +114,7 @@ function(add_generated_executable)
                              --operator-file ${GEN_OPERATOR}
                              --driver-file ${GEN_DRIVER}
                              ${GEN_FORM_COMPILER_ARGS}
-                             ${GEN_UFLFILE}
+                             --uflfile ${GEN_UFLFILE}
                      DEPENDS ${GEN_UFLFILE} ${UFL2PDELAB_SOURCES} ${GEN_DEPENDS}
                      COMMENT "Running ufl2pdelab for the target ${GEN_TARGET}"
                     )
diff --git a/python/dune/perftool/compile.py b/python/dune/perftool/compile.py
index d5427fa6fe15f98d10ab0e3e491abc0ae7697432..d2639ac6934b10a936711744c515890eba2d0a02 100644
--- a/python/dune/perftool/compile.py
+++ b/python/dune/perftool/compile.py
@@ -14,7 +14,7 @@ from dune.perftool.generation import (delete_cache_items,
                                       global_context,
                                       )
 from dune.perftool.interactive import start_interactive_session
-from dune.perftool.options import get_option
+from dune.perftool.options import get_option, initialize_options
 from dune.perftool.pdelab.driver import generate_driver
 from dune.perftool.pdelab.localoperator import (generate_localoperator_basefile,
                                                 generate_localoperator_file,
@@ -102,6 +102,7 @@ def read_ufl(uflfile):
 
 # This function is the entrypoint of the ufl2pdelab executable
 def compile_form():
+    initialize_options()
     formdatas, data = read_ufl(get_option("uflfile"))
 
     with global_context(data=data, formdatas=formdatas):
diff --git a/python/dune/perftool/options.py b/python/dune/perftool/options.py
index ded7826e994cd51a017415825775f7a5d5d8ebcd..a98082347a45edafb2edd5fa09f4cb4a9c3c7ae3 100644
--- a/python/dune/perftool/options.py
+++ b/python/dune/perftool/options.py
@@ -1,96 +1,98 @@
 """ Manage the command line options to the form compiler executable """
 
 from argparse import ArgumentParser
-from os import path
-from pytools import memoize
+from os.path import abspath
+from pytools import ImmutableRecord
 
 from dune.common.parametertree.parser import parse_ini_file
 
 
-@memoize
-def get_form_compiler_arguments():
-    # define an argument parser.
-    parser = ArgumentParser(description="Compile UFL files to PDELab C++ code", epilog="Please report bugs to dominic.kempf@iwr.uni-heidelberg.de")
-    parser.add_argument("uflfile", type=str, nargs=1, help="the UFL file to compile")
-    parser.add_argument("--driver-file", type=str, help="The filename for the generated driver header")
-    parser.add_argument("--operator-file", type=str, help="The filename for the generated local operator header")
+class PerftoolOption(ImmutableRecord):
+    """ Data structure representing a single formcompiler option """
+    def __init__(self,
+                 default=None,
+                 helpstr="Undocumented feature!",
+                 process=lambda x: x,
+                 type=type(None),
+                 ):
+        ImmutableRecord.__init__(self,
+                                 helpstr=helpstr,
+                                 default=default,
+                                 process=process,
+                                 type=type,
+                                 )
+
+
+class PerftoolOptionsArray(ImmutableRecord):
+    """ A collection of form compiler arguments """
+    def __init__(self, **kwargs):
+        opts = {k: v.default for k, v in PerftoolOptionsArray.__dict__.items() if isinstance(v, PerftoolOption)}
+        opts.update(kwargs)
+        ImmutableRecord.__init__(self, **opts)
+
+    uflfile = PerftoolOption(helpstr="the UFL file to compile", process=abspath)
+    driver_file = PerftoolOption(helpstr="The filename for the generated driver header", process=abspath)
+    operator_file = PerftoolOption(helpstr="The filename for the generated local operator header", process=abspath)
+    numerical_jacobian = PerftoolOption(default=False, helpstr="use numerical jacobians (only makes sense, if uflpdelab for some reason fails to generate analytic jacobians)")
+    matrix_free = PerftoolOption(default=False, helpstr="Use iterative solver with matrix free jacobian application")
+    explicit_time_stepping = PerftoolOption(default=False, helpstr="use explicit time stepping")
+    exact_solution_expression = PerftoolOption(helpstr="name of the exact solution expression in the ufl file")
+    compare_dofs = PerftoolOption(helpstr="maximal allowed maximum error of difference between degrees of freedom vectors of numerical solution and interpolation of exact solution (NOTE: requires --exact-solution-expression)")
+    compare_l2errorsquared = PerftoolOption(helpstr="maximal allowed l2 error squared of difference between numerical solution and interpolation of exact solution (NOTE: requires --exact-solution-expression)")
+    interactive = PerftoolOption(default=False, helpstr="whether the optimization process should be guided interactively (also useful for debugging)")
+    print_transformations = PerftoolOption(default=False, helpstr="print out dot files after ufl tree transformations")
+    print_transformations_dir = PerftoolOption(default=".", helpstr="place where to put dot files (can be omitted)", process=abspath)
+    quadrature_order = PerftoolOption(type=int, helpstr="Quadrature order used for all integrals.")
+    diagonal_transformation_matrix = PerftoolOption(default=False, helpstr="set option if the jacobian of the transformation is diagonal (axiparallel grids)")
+    constant_transformation_matrix = PerftoolOption(default=False, helpstr="set option if the jacobian of the transformation is constant on a cell")
+    ini_file = PerftoolOption(helpstr="An inifile to use. A generated driver will be hard-coded to it, a [formcompiler] section will be used as default values to form compiler arguments (use snake case)", process=abspath)
+    opcounter = PerftoolOption(default=False, helpstr="Count operations. Note: In this case only oparor applications are generated since solving and operator counting does not work. You probably want to set instrumentation level>0.")
+    time_opcounter = PerftoolOption(default=False, helpstr="Generate opcounter codepath. Can be used for timing opcounter programs without setting the opcounter option.")
+    instrumentation_level = PerftoolOption(default=0, helpstr="Control time/opcounter measurements. 0-do nothing, 1-measure program as a whole, 2-operator applications, 3-measure kernel (eg. alpha-volume, ...), 4-parts of kernel (eg. stage 1-3 of SF)")
+    project_basedir = PerftoolOption(helpstr="The base (build) directory of the dune-perftool project", process=abspath)
+    fastdg = PerftoolOption(default=False, helpstr="Use FastDGGridOperator from PDELab.")
+    sumfact = PerftoolOption(default=False, helpstr="Use sumfactorization")
+    vectorize_quad = PerftoolOption(default=False, helpstr="whether to generate code with explicit vectorization")
+    vectorize_grads = PerftoolOption(default=False, helpstr="whether to generate code with explicit vectorization")
+    turn_off_diagonal_jacobian = PerftoolOption(default=False, helpstr="Do not use diagonal_jacobian transformation on the ufl tree and cast result of jacobianInverseTransposed into a FieldMatrix.")
+
+
+# Until more sophisticated logic is needed, we keep the actual option data in this module
+_options = PerftoolOptionsArray()
+
+
+def initialize_options():
+    """ This should be called in entrypoint methods to correctly set up an options array """
+    global _options
+    _options = update_options_from_commandline(_options)
+    _options = update_options_from_inifile(_options)
+
+
+def update_options_from_commandline(opt):
+    """ Return an options array object with updated values from the commandline """
+    assert isinstance(opt, PerftoolOptionsArray)
+    parser = ArgumentParser(description="Compile UFL files to PDELab C++ code",
+                            epilog="Please report bugs to dominic.kempf@iwr.uni-heidelberg.de",
+                            )
     parser.add_argument('--version', action='version', version='%(prog)s 0.1')
-    parser.add_argument("--numerical-jacobian", action="store_true", help="use numerical jacobians (only makes sense, if uflpdelab for some reason fails to generate analytic jacobians)")
-    parser.add_argument("--matrix-free", action="store_true", help="use iterative solver with matrix free jacobian application")
-    parser.add_argument("--explicit-time-stepping", action="store_true", help="use explicit time stepping")
-    parser.add_argument(
-        "--exact-solution-expression",
-        type=str,
-        help="name of the exact solution expression in the ufl file")
-    parser.add_argument(
-        "--compare-dofs",
-        type=str,
-        help="maximal allowed maximum error of difference between degrees of freedom vectors of numerical solution and interpolation of exact solution (NOTE: requires --exact-solution-expression)"
-    )
-    parser.add_argument(
-        "--compare-l2errorsquared",
-        type=str,
-        help="maximal allowed l2 error squared of difference between numerical solution and interpolation of exact solution (NOTE: requires --exact-solution-expression)"
-    )
-    parser.add_argument("--interactive", action="store_true", help="whether the optimization process should be guided interactively (also useful for debugging)")
-    parser.add_argument("--print-transformations", action="store_true", help="print out dot files after ufl tree transformations")
-    parser.add_argument("--print-transformations-dir", type=str, help="place where to put dot files (can be omitted)")
-    parser.add_argument("--quadrature-order", type=int, help="Quadrature order used for all integrals.")
-    parser.add_argument("--diagonal-transformation-matrix", action="store_true", help="set option if the jacobian of the transformation is diagonal (axiparallel grids)")
-    parser.add_argument("--constant-transformation-matrix", action="store_true", help="set option if the jacobian of the transformation is constant on a cell")
-    parser.add_argument("--ini-file", type=str, help="An inifile to use. A generated driver will be hard-coded to it, a [formcompiler] section will be used as default values to form compiler arguments (use snake case)")
-    parser.add_argument("--opcounter", action="store_true", help="Count operations. Note: In this case only oparor applications are generated since solving and operator counting does not work. You probably want to set instrumentation level>0.")
-    parser.add_argument("--time-opcounter", action="store_true", help="Generate opcounter codepath. Can be used for timing opcounter programs without setting the opcounter option.")
-    parser.add_argument("--instrumentation-level", type=int, default=0, help="Control time/opcounter measurements. 0-do nothing, 1-measure program as a whole, 2-operator applications, 3-measure kernel (eg. alpha-volume, ...), 4-parts of kernel (eg. stage 1-3 of SF)")
-    parser.add_argument("--project-basedir", type=str, help="The base (build) directory of the dune-perftool project")
-    parser.add_argument("--fastdg", action="store_true", help="Use FastDGGridOperator from PDELab.")
-    # TODO at some point this help description should be updated
-    parser.add_argument("--sumfact", action="store_true", help="Use sumfactorization")
-    parser.add_argument("--vectorize-quad", action="store_true", help="whether to generate code with explicit vectorization")
-    parser.add_argument("--vectorize-grads", action="store_true", help="whether to generate code with explicit vectorization")
-    parser.add_argument("--turn-off-diagonal-jacobian", action="store_true", help="Do not use diagonal_jacobian transformation on the ufl tree and cast result of jacobianInverseTransposed into a FieldMatrix.")
-
-    # Modify the positional argument to not be a list
-    args = vars(parser.parse_args())
-
-    # Delistify the uflfile parameter
-    args["uflfile"] = args["uflfile"][0]
-
-    # Turn any relative paths into absolute ones for consistency
-    if args["driver_file"]:
-        args["driver_file"] = path.abspath(args["driver_file"])
-    if args["operator_file"]:
-        args["operator_file"] = path.abspath(args["operator_file"])
-    if args["ini_file"]:
-        args["ini_file"] = path.abspath(args["ini_file"])
-
-    # Return the argument dict. This result is memoized to turn all get_option calls into simple dict lookups.
-    return args
-
-
-_option_dict = {}
-_arguments_read = False
-
-
-def init_option_dict():
-    """Add form compile arguments to options dict"""
-    global _arguments_read
-    if not _arguments_read:
-        _option_dict.update(get_form_compiler_arguments())
-        _arguments_read = True
-
-        # Read arguments from the given inifile
-        if _option_dict["ini_file"]:
-            inifile = parse_ini_file(_option_dict["ini_file"])
-            for key, value in inifile.get("formcompiler", {}).items():
-                if key not in _option_dict or _option_dict[key] is None:
-                    _option_dict[key] = value
-                elif not _option_dict[key]:
-                    # As `bool("0")` is True, we need to eval bools
-                    if isinstance(_option_dict[key], bool):
-                        _option_dict[key] = eval(value)
-                    else:
-                        _option_dict[key] = type(_option_dict[key])(value)
+    for k, v in type(opt).__dict__.items():
+        if isinstance(v, PerftoolOption):
+            cmdopt = "--{}".format(k.replace('_', '-'))
+            _type = v.type
+            if _type is type(None):
+                _type = type(v.default)
+            if _type is type(None):
+                _type = str
+            parser.add_argument(cmdopt, help=v.helpstr, type=_type)
+    parsedargs = {k: v for k, v in vars(parser.parse_args()).items() if v is not None}
+    return opt.copy(**parsedargs)
+
+
+def update_options_from_inifile(opt):
+    """ Return an options array object with updated values from an inifile """
+    if opt.ini_file:
+        opt = opt.copy(**parse_ini_file(opt.ini_file).get("formcompiler", {}))
+    return opt
 
 
 def set_option(key, value):
@@ -101,22 +103,12 @@ def set_option(key, value):
     any other options.
 
     """
-    # Make sure form compile arguments were read
-    init_option_dict()
+    global _options
+    _options = _options.copy(key=value)
 
-    _option_dict.update({key: value})
 
-
-def get_option(key, default=None):
-    try:
-        __IPYTHON__
-        return default
-    except:
-        """Return the value corresponding to key from option dictionary"""
-        # Make sure form compile arguments were read
-        init_option_dict()
-
-        return _option_dict.get(key, default)
+def get_option(key):
+    return getattr(_options, key)
 
 
 def option_switch(opt):
diff --git a/python/dune/perftool/ufl/transformations/__init__.py b/python/dune/perftool/ufl/transformations/__init__.py
index d4bd87f862e7ca12611daea6a6cdb4523b0f287f..3ffb9b7ff8af4efec7032568d5379ea689cc094d 100644
--- a/python/dune/perftool/ufl/transformations/__init__.py
+++ b/python/dune/perftool/ufl/transformations/__init__.py
@@ -20,9 +20,9 @@ class UFLTransformationWrapper(object):
 
         # Write out a dot file
         from dune.perftool.options import get_option
-        if get_option("print_transformations", False):
+        if get_option("print_transformations"):
             import os
-            dir = get_option("print_transformations_dir", os.getcwd())
+            dir = get_option("print_transformations_dir")
 
             for i, exprtowrite in enumerate(expr):
                 filename = "trafo_{}_{}_{}{}.dot".format(self.name, str(self.counter).zfill(4), "in" if before else "out", "_{}".format(i) if len(expr) > 1 else "")