Custom Types

All examples are included in the PyXLL download.

Plain text version

"""
PyXLL Examples: Custom types

Worksheet functions can use a number of standard types
as shown in the worksheetfuncs example.

It's also possible to define custom types that
can be used in the PyXLL function signatures
as shown by these examples.

For a more complicated custom type example see the
object cache example.
"""

from pyxll import xl_func

import sys
if sys.version_info[0] > 2:
    from functools import reduce

#
# xl_arg_type and xl_return type are decorators that can
# be used to declare types that our excel functions
# can use in addition to the standard types
#
from pyxll import xl_arg_type, xl_return_type

#
# 1) Custom types
#

#
# All variables are passed to and from excel as the basic types,
# but it's possible to register conversion functions that will
# convert those basic types to whatever types you like before
# they reach your function, (or after you function returns them
# in the case of returned values).
#

#
# CustomType1 is a very simple class used to demonstrate
# custom types.
#
class CustomType1:

    def __init__(self, name):
        self.name = name

    def greeting(self):
        return "Hello, my name is %s" % self.name

#
# To use CustomType1 as an argument to a pyxll function you have to
# register a function to convert from a basic type to our custom type.
#
# xl_arg_type takes two arguments, the new custom type name, and the
# base type.
#

@xl_arg_type("custom1", "string")
def string_to_custom1(name):
    return CustomType1(name)

#
# now the type 'custom1' can be used as an argument type 
# in a function signature.
#

@xl_func("custom1 x: string")
def customtype_pyxll_function_1(x):
    """returns x.greeting()"""
    return x.greeting()

#
# To use CustomType1 as a return type for a pyxll function you have
# to register a function to convert from the custom type to a basic type.
#
# xl_return_type takes two arguments, the new custom type name, and
# the base type.
#

@xl_return_type("custom1", "string")
def custom1_to_string(x):
    return x.name

#
# now the type 'custom1' can be used as the return type.
#

@xl_func("custom1 x: custom1")
def customtype_pyxll_function_2(x):
    """check the type and return the same object"""
    assert isinstance(x, CustomType1), "expected an CustomType1 object"""
    return x

#
# CustomType2 is another example that caches its instances
# so they can be referred to from excel functions.
#

class CustomType2:

    __instances__ = {}

    def __init__(self, name, value):
        self.value = value
        self.id = "%s-%d" % (name, id(self))
        
        # overwrite any existing instance with self
        self.__instances__[name] = self
      
    def getValue(self):
        return self.value

    @classmethod
    def getInstance(cls, id):
        name, unused = id.split("-")
        return cls.__instances__[name]

    def getId(self):
        return self.id


@xl_arg_type("custom2", "string")
def string_to_custom2(x):
    return CustomType2.getInstance(x)


@xl_return_type("custom2", "string")
def custom2_to_string(x):
    return x.getId()


@xl_func("string name, float value: custom2")
def customtype_pyxll_function_3(name, value):
    """returns a new CustomType2 object"""
    return CustomType2(name, value)


@xl_func("custom2 x: float")
def customtype_pyxll_function_4(x):
    """returns x.getValue()"""
    return x.getValue()

#
# custom types may be base types of other custom types, as
# long as the ultimate base type is a basic type.
#
# This means you can chain conversion functions together.
#

class CustomType3:

    def __init__(self, custom2):
        self.custom2 = custom2

    def getValue(self):
        return self.custom2.getValue() * 2


@xl_arg_type("custom3", "custom2")
def custom2_to_custom3(x):
    return CustomType3(x)


@xl_return_type("custom3", "custom2")
def custom3_to_custom2(x):
    return x.custom2


#
# when converting from an excel cell to a CustomType3 object,
# the string will first be used to get a CustomType2 object
# via the registed function string_to_custom2, and then
# custom2_to_custom3 will be called to get the final 
# CustomType3 object.
#

@xl_func("custom3 x: float")
def customtype_pyxll_function_5(x):
    """return x.getValue()"""
    return x.getValue()


#
# 2) Custom types and arrays
#

#
# Array types may be used as the base types for custom types
# in the same way as any other type.
#

#
# This example shows how to reduce a range of data (list of
# lists) to a single list for use by a function.
#
# It also shows how it's possible to use multiple xl_arg_type
# decorators for the same function without duplicating code.
#

@xl_arg_type("int_list", "int[]")
@xl_arg_type("float_list", "float[]")
@xl_arg_type("custom1_list", "custom1[]")
def flatten(x):
    return reduce(lambda a,b: a + b, x, [])


@xl_func("float_list x: string")
def customarray_pyxll_function_1(x):
    # x is list of floats
    total = sum(x, 0)
    return "sum=%f : %s" % (total, x)


@xl_func("custom1_list x: string")
def customarray_pyxll_function_2(x):
    # x is a list of CustomType1 objects
    return "Hello %s" % (", ".join([c.name for c in x]))