<jupyter_start><jupyter_text>Functions as Objects Functions in Python are **first-class objects**. Programming language theorists define a **first-class object** as a program entity that can be:- Created at runtime- Assigned to a variable or element in a data structure- Passed as an argument to a function- Returned as the result of a function Integers, strings, and dictionaries are other examples of first-class objects in Python — nothing fancy here. But if you came to Python from a language where functions are **not** first-class citizens, this notbook and the rest focuses on the implications and practical applications of treating functions as objects.  Treating a Function like an Object<jupyter_code>def factorial(n):
    '''returns n!'''
    return 1 if n < 2 else n * factorial(n-1)
factorial(42)
factorial.__doc__
type(factorial)<jupyter_output><empty_output><jupyter_text>Introspection<jupyter_code>dir(factorial)
factorial.__class__<jupyter_output><empty_output><jupyter_text>Use function through a different name, and pass function as argument<jupyter_code>fact = factorial
fact
fact(5)<jupyter_output><empty_output><jupyter_text>**Note**: Having first-class functions enables programming in a **functional style**. One of the hallmarks of functional programming is the use of **higher-order functions**.  Higher-Order Functions >A function that takes a function as argument or returns a function as the result is a higher-order function. One example is `map`. Another is the built-in function `sorted`: an optional `key` argument lets you provide a function to be applied to each item for sorting, as seen in `list.sort` and the `sorted` functions.For example, to sort a list of words by length, simply pass the `len` function as the key:.<jupyter_code>fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=len)<jupyter_output><empty_output><jupyter_text>Anonymous Functions The `lambda` keyword creates an anonymous function within a Python expression.However, the simple syntax of Python limits the body of `lambda` functions to be pure expressions. In other words, the body of a lambda cannot make assignments or use any other Python statement such as `while`, etc.The best use of anonymous functions is in the context of an argument list.<jupyter_code>fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=lambda word: word[::-1])<jupyter_output><empty_output><jupyter_text>Outside the limited context of arguments to higher-order functions, anonymous functions are rarely useful in Python. The syntactic restrictions tend to make nontriv‐ ial lambdas either unreadable or unworkable. **Lundh’s lambda Refactoring Recipe**If you find a piece of code hard to understand because of a lambda, <NAME> suggests this refactoring procedure:> 1. Write a comment explaining what the heck that lambda does. > 2. Study the comment for a while, and think of a name that captures the essence ofthe comment.> 3. Convert the lambda to a def statement, using that name.> 4. Remove the comment.These steps are quoted from the [Functional Programming HOWTO](https://docs.python.org/3/howto/functional.html), a **must read**. The `lambda` syntax is just syntactic sugar: a lambda expression creates a function object just like the def statement.   Function Annotations **Python 3** provides syntax to attach _metadata_ to the parameters of a function declaration and its return value.<jupyter_code>def clip(text:str, max_len:'int > 0'=80) -> str:
    """Return text clipped at the last space before or after max_len
    """
    end = None
    if len(text) > max_len:
        space_before = text.rfind(' ', 0, max_len)
        if space_before >= 0:
            end = space_before
        else:
            space_after = text.rfind(' ', max_len)
            if space_after >= 0:
                end = space_after
    if end is None:  # no spaces were found
        end = len(text)
    return text[:end].rstrip()
clip.__annotations__<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08170339192869522, 'fraction_numerical': 0.004704134686803664, 'mean_word_length': 4.763195435092725, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 36, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '234954', 'license': 'MIT', 'n_tokens_mistral': 1120, 'n_tokens_neox': 1075, 'n_words': 502, 'path': 'python_extras/functions-objects.ipynb', 'repo_name': 'AnnaMilov/programming-for-data-science', 'size': '4046'}