Functions

A function is an organizational structure that lets you group code into a reusable package. Not only do functions allow you to reuse the same code (possible with different inputs) over and over, they can be useful to help organize your code into logically related fragments, which can make your code easier to develop and understand.

Anatomy of a Function

A function definition looks like:

def my_function(inputs...):
    ...
    return expression

Let’s break it down.

Function Name

In the example above, the function created is called my_function. This has the effect of creating a variable named my_function, who’s value is the function itself. In order to use my_function, you call it with the following syntax:

my_function(...)

Where ... in this case is the inputs that you pass to your function.

Note

Because a function’s name becomes a variable, it is conventional for function names to be lowercase, with words separated by underscores. It follows all variable name requirements.

When working with Python in Grasshopper and Rhino 8, it’s not uncommon to need to work with functions that are PascalCased, where the first letter of each word is capitalized (including the first), instead of using underscores. This is a result of the Rhino/Grasshopper-specific functions being created by C# instead of Python.

Function Inputs

The number of inputs a function takes is determined by specifying comma-separated variable names inside the parentheses immediately following def my_function, inputs... in the example above. A function can accept as many inputs as you’d like.

When calling the function, you need to pass in one argument for each input declared in the function definition:

def function0():
    ...
# would need to be called like
return_value = function0()

def function1(arg1):
    ...
# would need to be called like
return_value = function1(True)

def function2(arg1, arg2):
    ...
# would need to be called like
return_value = function2(1, "fizz")

The type of arguments used and the values passed in depend on the function and how it is being used.

Advanced Parameter Shenanigans

During this class, you may come across some parameter lists that aren’t just comma-separated variable names, and you might see some arguments in a function call that look like an assignment.

To understand these, we can look to how Python handles parameters getting passed in. When you provide parameters to a function as shown above, they’re treated as positional arguments. That is, the first value you supply when calling the function is assigned to the first input parameter, the second to the second, etc. Alternatively, you can pass variables by keyword, in which you explicitly say which input variable to bind to which value. Using the function2 from above:

return_value = function2(arg2="fizz", arg1=1)

Important

All positional arguments must be passed in before all keyword arguments.

return_value = function2(arg2="fizz", 1)

is invalid.

Furthermore, attempting to pass an argument to a parameter name that doesn’t exist in the function definition will result in an error.

Additionally, functions in Python can be variadic, which means they can be made to accept any number of arguments you supply to them. To do this, create a parameter, and have it’s name start with an * (the common choice is *args). When called, you can pass in as many comma-separated values as you’d like, and any values in excess of the number of positional input parameters in the function definition will fill a list that gets stored in the args variable. Keyword arguments can also be made to be variadic, starting the variable name with ** (the common choice for variable name is **kwargs). Any keyword arguments that do not match an explicitly defined input parameter will populate a dict that gets stored in the kwargs variable. An example function definition with both variadic positional and keyword arguments:

def my_function(*args, **kwargs):
    print(args)
    print(kwargs)

my_function(1, 2, a=3, b=4)
# Console output:
# [1, 2]
# {'a': 3, 'b': 4}

Note

We will learn about lists and dicts in the next in-person class, during week 2.

Another trick you can apply to a function definition is to make certain parameters either positional-only or keyword-only. Positional-only parameters must be supplied positionally (an error will be raised if they are specified by name), and keyword-only parameters must be supplied by name.

By creating a positional-variadic function, any parameters listed after *args will be keyword only. If you don’t want the function to be positional-variadic but still have keyword-only parameters, make a parameter that is simply *. Extra positional arguments will raise an error, because there’s no variable to save them to, but any arguments listed after the * will be keyword-only. To make positional-only arguments, place a / in the function definition following the arguments you want to be positional-only. An example, non-variadic function definition:

def my_function(pos_only1, pos_only2, /, either_or1, either_or2, *, kw_only1, kw_only2):
    ...

Finally, you can supply default values to function parameters. If these parameters do not receive a value when the function is called, they are assigned the default value provided in the function definition. For example:

def my_function(a, b, c=2):
    print(a, b, c)

my_function(0, 1)
# Console output:
# 0 1 2

my_function(1, 2, 3)
# Console output:
# 1 2 3

Important

All parameters with defaults must be declared at the end of the parameter list. This does mean you have to be mindful if you want to have positional-only or keyword-only arguments with defaults.

Function Output

A function in Python will always return some kind of value. This value is determined with a return statement. As seen in the example above, return is followed by an expression that sets the output value. Once return is called, the function is immediately left, and no more code in the function body is executed.

You can have multiple return statements in one function, but only the first one called will get executed. You can use control statements to make multiple return statements meaningful:

def is_odd(number):
    if number % 2 == 0:
        return False
    else:
        return True

Note

The example above is a little contrived, because it can be reduced to:

def is_odd(number):
    return number % 2 == 1

But it gets the point across.

Some special cases for function outputs exist:

  • If a return is not followed by an expression, the return value is set to None.

  • If a return statement is never called by the time the end of the function has been reached, it will automatically return None.

Useful Functions

The following is a collection of helpful functions that you will probably find useful during this course (copied from the dedicated page).

This is a living list that will expand over the course of the semester.

print

The print() function allows you to output text to the console while a program is running.

In a Grasshopper script node, instead of printing to the console, any printed text gets sent to the out pin, which can be viewed with a Panel.

print(*args, sep=' ', end='\n', file=None, flush=False)

Prints the values to a stream, or to sys.stdout by default.

sep

string inserted between values, default a space.

end

string appended after the last value, default a newline.

file

a file-like object (stream); defaults to the current sys.stdout.

flush

whether to forcibly flush the stream.

Note how print is variadic. You can supply as many arguments as you want to it, and each argument will be converted to a string using that object’s __str__() method. The stringified arguments will then be sent to the output stream, separated by the sep argument, which is keyword-only. In the case of a Grasshopper script node, the default output stream is captured into the out pin.

len

The len() function returns the size of a collection, or any object that has defined the __len__ method.

len(obj, /)

Return the number of items in a container.

max

The max() function accepts items that support “rich” comparison using <, <=, ==, >=, and !=. Multiple versions can be used:

max(iterable, *[, default=obj, key=func]) value
max(arg1, arg2, *args, *[, key=func]) value

With a single iterable argument, return its biggest item. The default keyword-only argument specifies an object to return if the provided iterable is empty. With two or more arguments, return the largest argument.

The second version is variadic and can be provided with as many positional inputs as you’d like. If, instead, an iterable is provided, all items within that iterable must be comparable with the rich comparison operators.

The keyword-only parameter key accepts a function that takes in a single input and returns a value that is richly comparable. With a key, the output of the function is instead the item, x, from the input that has the maximum key(x). This can be used, for example, to compute an “argmax”, the index of the maximal item:

def collection_at_index(idx):
    return collection[idx]
argmax = max(range(len(collection)), key=collection_at_index)

Note how collection_at_index is passed into key without using parentheses to call it. This supplies the function itself to key instead of an output from the function.

min

The min() function accepts items that support “rich” comparison using <, <=, ==, >=, and !=. Multiple versions can be used:

min(iterable, *[, default=obj, key=func]) value
min(arg1, arg2, *args, *[, key=func]) value

With a single iterable argument, return its smallest item. The default keyword-only argument specifies an object to return if the provided iterable is empty. With two or more arguments, return the smallest argument.

The second version is variadic and can be provided with as many positional inputs as you’d like. If, instead, an iterable is provided, all items within that iterable must be comparable with the rich comparison operators.

The keyword-only parameter key accepts a function that takes in a single input and returns a value that is richly comparable. With a key, the output of the function is instead the item, x, from the input that has the minimum key(x). This can be used, for example, to compute an “argmax”, the index of the minimal item:

def collection_at_index(idx):
    return collection[idx]
argmax = min(range(len(collection)), key=collection_at_index)

Note how collection_at_index is passed into key without using parentheses to call it. This supplies the function itself to key instead of an output from the function.

sorted

sorted(iterable, /, *, key=None, reverse=False)

Return a new list containing all items from the iterable in ascending order.

A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order.

abs

abs(x, /)

Return the absolute value of the argument.

round

round(number, ndigits=None)

Round a number to a given precision in decimal digits.

The return value is an integer if ndigits is omitted or None. Otherwise the return value has the same type as the number. ndigits may be negative.

reversed

The reversed() function can be used to reverse a sequence, or any object that has defined the __reversed__ method.

reversed(sequence, /)

Return a reverse iterator over the values of the given sequence.

Warning

reversed() does not return a sequence. It returns something that can be iterated over with a for loop, but it cannot be indexed. If you need a reversed version of your sequence that can be indexed, use a slice, as shown in the Slicing Examples.

range

range(stop) range object
range(start, stop[, step]) range object

Return an object that produces a sequence of integers from start (inclusive) to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, …, j-1. start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3. These are exactly the valid indices for a list of 4 elements. When step is given, it specifies the increment (or decrement).

Useful in for loops.

zip

zip(*iterables, strict=False) zip object
>>> list(zip('abcdefg', range(3), range(4)))
[('a', 0, 0), ('b', 1, 1), ('c', 2, 2)]

The zip object yields n-length tuples, where n is the number of iterables passed as positional arguments to zip(). The i-th element in every tuple comes from the i-th iterable argument to zip(). This continues until the shortest argument is exhausted.

If strict is true and one of the arguments is exhausted before the others, raise a ValueError.

Useful in for loops in conjunction with tuple unpacking:

for item1, item2 in zip(collection1, collection2):
    ...

enumerate

enumerate(iterable, start=0)

Return an enumerate object.

iterable

an object supporting iteration

The enumerate object yields pairs containing a count (from start, which defaults to zero) and a value yielded by the iterable argument.

enumerate is useful for obtaining an indexed list:

(0, seq[0]), (1, seq[1]), (2, seq[2]), …

Useful in for loops in conjunction with tuple unpacking:

for idx, item in enumerate(collection):
    ...

Type Conversions

Many built-in types can be converted to one another by passing them to the new type’s constructor. For example, to convert something to a string, use the str(). To convert something to an integer, use the int(). If the conversion fails, an error will be raised.

class str(object='')
class str(bytes_or_buffer[, encoding[, errors]]) str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.

class int([x])
class int(x, base=10) integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

class float(x=0, /)

Convert a string or number to a floating-point number, if possible.

class bool(x)

Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.