Is function an object in python

You are looking for the __call__ method. Function objects have that method:

>>> def foo(): pass
... 
>>> foo.__call__

Not that the Python interpreter loop actually makes use of that method when encountering a Python function object; optimisations in the implementation jump straight to the contained bytecode in most cases.

But you can use that on your own custom class:

class Callable(object):
    def __init__(self, name):
        self.name = name

    def __call__(self, greeting):
        return '{}, {}!'.format(greeting, self.name)

Demo:

>>> class Callable(object):
...     def __init__(self, name):
...         self.name = name
...     def __call__(self, greeting):
...         return '{}, {}!'.format(greeting, self.name)
... 
>>> Callable('World')('Hello')
'Hello, World!'

Python creates function objects for you when you use a def statement, or you use a lambda expression:

>>> def foo(): pass
... 
>>> foo

>>> lambda: None
 at 0x106d90668>

You can compare this to creating a string or an integer or a list using literal syntax:

listobject = [1, 'two']

The above creates 3 objects without ever calling a type, Python did that all for you based on the syntax used. The same applies to functions.

Creating one yourself can be a little more complex; you need to have a code object and reference to a global namespace, at the very least:

>>> function_type = type(lambda: None)
>>> function_type

>>> function_type(foo.__code__, globals(), 'bar')

Here I created a function object by reusing the function type, taking the code object from the foo function; the function type is not a built-in name but the type really does exist and can be obtained by calling type() on an existing function instance.

I also passed in the global namespace of my interpreter, and a name; the latter is an optional argument; the name is otherwise taken from the code object.

One of the most powerful features of Python is that everything is an object, including functions. Functions in Python are first-class objects.

Is function an object in python

This broadly means, that functions in Python:

  • have types
  • can be sent as arguments to another function
  • can be used in expression
  • can become part of various data…

import os
import sys
import glob
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline
%precision 4

References:

Functional Programming HOWTO

In Python, functions behave like any other object, such as an int or a list. That means that you can use functions as arguments to other functions, store functions as dictionary values, or return a function from another function. This leads to many powerful ways to use functions.

def square(x):
    """Square of x."""
    return x*x

def cube(x):
    """Cube of x."""
    return x*x*x

# create a dictionary of functions

funcs = {
    'square': square,
    'cube': cube,
}

x = 2

print square(x)
print cube(x)

for func in sorted(funcs):
    print func, funcs[func](x)

Function argumnents¶

This is caution to be careful of how Python treats function arguments.

Call by “object reference”¶

Some data types, such as strings and tuples, cannot be directly modified and are called immutable. Atomic variables such as integers or floats are always immutable. Other datatypes, such as lists and dictionaries, can be directly modified and are called mutable. Passing mutable variables as function arguments can have different outcomes, depedning on what is done to the variable inside the function. When we call

x = [1,2,3] # mutable
f(x)

what is passsed to the function is a copy of the name x that refers to the content (a list) [1, 2, 3]. If we use this copy of the name to change the content directly (e.g. x[0] = 999) within the function, then x chanes outside the funciton as well. However, if we reassgne x within the function to a new object (e.g. another list), then the copy of the name x now points to the new object, but x outside the function is unhcanged.

def transmogrify(x):
    x[0] = 999
    return x

x = [1,2,3]
print x
print transmogrify(x)
print x

[1, 2, 3]
[999, 2, 3]
[999, 2, 3]

def no_mogrify(x):
    x = [4,5,6]
    return x

x = [1,2,3]
print x
print no_mogrify(x)
print x

[1, 2, 3]
[4, 5, 6]
[1, 2, 3]

Binding of default arguments occurs at function definition¶

def f(x = []):
    x.append(1)
    return x

print f()
print f()
print f()
print f(x = [9,9,9])
print f()
print f()

[1]
[1, 1]
[1, 1, 1]
[9, 9, 9, 1]
[1, 1, 1, 1]
[1, 1, 1, 1, 1]

# Usually, this behavior is not desired and we would write

def f(x = None):
    if x is None:
        x = []
    x.append(1)
    return x

print f()
print f()
print f()
print f(x = [9,9,9])
print f()
print f()

[1]
[1]
[1]
[9, 9, 9, 1]
[1]
[1]

However, sometimes in advanced usage, the behavior is intetnional. See http://effbot.org/zone/default-values.htm for details.

Higher-order functions¶

A function that uses another function as an input argument or returns a function (HOF) is known as a higher-order function. The most familiar examples are map and filter.

# The map function applies a function to each member of a collection

map(square, range(5))

# The filter function applies a predicate to each memmber of a collection,
# retaining only those members where the predicate is True

def is_even(x):
    return x%2 == 0

filter(is_even, range(5))

# It is common to combine map and filter

map(square, filter(is_even, range(5)))

# The reduce function reduces a collection using a binary operator to combine items two at a time

def my_add(x, y):
    return x + y

# another implementation of the sum function
reduce(my_add, [1,2,3,4,5])

# Custom functions can of couse, also be HOFs

def custom_sum(xs, transform):
    """Returns the sum of xs after a user specified transform."""
    return sum(map(transform, xs))

xs = range(5)
print custom_sum(xs, square)
print custom_sum(xs, cube)

# Returning a function is also useful

# A closure
def make_logger(target):
    def logger(data):
        with open(target, 'a') as f:
            f.write(data + '\n')
    return logger

foo_logger = make_logger('foo.txt')
foo_logger('Hello')
foo_logger('World')

Hello
World
Hello
World
Hello
World
Hello
World

Anonymous functions¶

When using functional style, there is often the need to create small specific functions that perform a limited task as input to a HOF such as map or filter. In such cases, these functions are often written as anonymous or lambda functions. If you find it hard to understand what a lambda function is doing, it should probably be rewritten as a regular function.

# Using standard functions

def square(x):
    return x*x

print map(square, range(5))

# Using an anonymous function

print map(lambda x: x*x, range(5))

# what does this function do?
s1 = reduce(lambda x, y: x+y, map(lambda x: x**2, range(1,10)))
print(s1)
print

# functional expressions and lambdas are cool
# but can be difficult to read when over-used
# Here is a more comprehensible version
s2 = sum(x**2 for x in range(1, 10))
print(s2)

# we will revisit map-reduce when we look at high-performance computing
# where map is used to distribute jobs to multiple processors
# and reduce is used to calculate some aggreate function of the results
# returned by map

Pure functions¶

Functions are pure if they do not have any side effects and do not depend on global variables. Pure functions are similar to mathematical functions - each time the same input is given, the same output will be returned. This is useful for reducing bugs and in parallel programming since each function call is independent of any other function call and hence trivially parallelizable.

def pure(xs):
    """Make a new list and return that."""
    xs = [x*2 for x in xs]
    return xs

xs = range(5)
print "xs =", xs
print pure(xs)
print "xs =", xs

xs = [0, 1, 2, 3, 4]
[0, 2, 4, 6, 8]
xs = [0, 1, 2, 3, 4]

def impure(xs):
    for i, x in enumerate(xs):
        xs[i] = x*2
    return xs

xs = range(5)
print "xs =", xs
print impure(xs)
print "xs =", xs

xs = [0, 1, 2, 3, 4]
[0, 2, 4, 6, 8]
xs = [0, 2, 4, 6, 8]

# Note that mutable functions are created upon function declaration, not use.
# This gives rise to a common source of beginner errors.

def f1(x, y=[]):
    """Never give an empty list or other mutable structure as a default."""
    y.append(x)
    return sum(y)

print f1(10)
print f1(10)
print f1(10, y =[1,2])

# Here is the correct Python idiom

def f2(x, y=None):
    """Check if y is None - if so make it a list."""
    if y is None:
        y = []
    y.append(x)
    return sum(y)

print f1(10)
print f1(10)
print f1(10, y =[1,2])

Recursion¶

A recursive function is one that calls itself. Recursive functions are extremely useful examples of the divide-and-conquer paradigm in algorithm development and are a direct expression of finite diffference equations. However, they can be computationally inefficient and their use in Python is quite rare in practice.

Recursive functions generally have a set of base cases where the answer is obvious and can be returned immediately, and a set of recursive cases which are split into smaller pieces, each of which is given to the same function called recursively. A few examples will make this clearer.

# The factorial function is perhaps the simplest classic example of recursion.

def fact(n):
    """Returns the factorial of n."""
    # base case
    if n==0:
        return 1
    # recursive case
    else:
        return n * fact(n-1)

print [fact(n) for n in range(10)]

[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]

# The Fibonacci sequence is another classic recursion example

def fib1(n):
    """Fib with recursion."""

    # base case
    if n==0 or n==1:
        return 1
    # recurssive caae
    else:
        return fib1(n-1) + fib1(n-2)

print [fib1(i) for i in range(10)]

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

# In Python, a more efficient version that does not use recursion is

def fib2(n):
    """Fib without recursion."""
    a, b = 0, 1
    for i in range(1, n+1):
        a, b = b, a+b
    return b

print [fib2(i) for i in range(10)]

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

# Note that the recursive version is much slower than the non-recursive version

%timeit fib1(20)
%timeit fib2(20)

# this is because it makes many duplicate function calls
# Note duplicate calls to fib(2) and fib(1) below
# fib(4) -> fib(3), fib(2)
# fib(3) -> fib(2), fib(1)
# fib(2) -> fib(1), fib(0)
# fib(1) -> 1
# fib(0) -> 1

100 loops, best of 3: 5.64 ms per loop
100000 loops, best of 3: 2.87 µs per loop

# Use of cache to speed up the recursive version.
# Note biding of the (mutable) dictionary as a default at run-time.

def fib3(n, cache={0: 1, 1: 1}):
    """Fib with recursion and caching."""

    try:
        return cache[n]
    except KeyError:
        cache[n] = fib3(n-1) + fib3(n-2)
        return cache[n]

print [fib3(i) for i in range(10)]

%timeit fib1(20)
%timeit fib2(20)
%timeit fib3(20)

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
100 loops, best of 3: 5.64 ms per loop
100000 loops, best of 3: 2.92 µs per loop
1000000 loops, best of 3: 262 ns per loop

# Recursion is used to show off the divide-and-conquer paradigm

def almost_quick_sort(xs):
    """Almost a quick sort."""

    # base case
    if xs == []:
        return xs
    # recursive case
    else:
        pivot = xs[0]
        less_than = [x for x in xs[1:] if x <= pivot]
        more_than = [x for x in xs[1:] if x > pivot]
        return almost_quick_sort(less_than) + [pivot] + almost_quick_sort(more_than)

xs = [3,1,4,1,5,9,2,6,5,3,5,9]
print almost_quick_sort(xs)

[1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9, 9]

Iterators¶

Iterators represent streams of values. Because only one value is consumed at a time, they use very little memory. Use of iterators is very helpful for working with data sets too large to fit into RAM.

# Iterators can be created from sequences with the built-in function iter()

xs = [1,2,3]
x_iter = iter(xs)

print x_iter.next()
print x_iter.next()
print x_iter.next()
print x_iter.next()

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)

 in ()
      7 print x_iter.next()
      8 print x_iter.next()
----> 9 print x_iter.next()


StopIteration:

# Most commonly, iterators are used (automatically) within a for loop
# which terminates when it encouters a StopIteration exception

x_iter = iter(xs)
for x in x_iter:
    print x

Generators¶

Generators create iterator streams.

# Functions containing the 'yield' keyword return iterators
# After yielding, the function retains its previous state

def count_down(n):
    for i in range(n, 0, -1):
        yield i

counter = count_down(10)
print counter.next()
print counter.next()
for count in counter:
    print count,

# Iterators can also be created with 'generator expressions'
# which can be coded similar to list generators but with parenthesis
# in place of square brackets

xs1 = [x*x for x in range(5)]
print xs1

xs2 = (x*x for x in range(5))
print xs2

for x in xs2:
    print x,
print

[0, 1, 4, 9, 16]
 at 0x1130d09b0>
0 1 4 9 16

# Iterators can be used for infinte functions

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a+b

for i in fib():
    # We must have a stopping condiiton since the generator returns an infinite stream
    if i > 1000:
        break
    print i,

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

# Many built-in Python functions return iterators
# including file handlers
# so with the idiom below, you can process a 1 terabyte file line by line
# on your laptop without any problem
# Inn Pyhton 3, map and filter return itnrators, not lists

for line in open('foo.txt'):
    print line,

Hello
World
Hello
World
Hello
World
Hello
World

Generators and comprehensions¶

# A geneeratorr expression

print (x for x in range(10))

# A list comprehesnnion

print [x for x in range(10)]

# A set comprehension

print {x for x in range(10)}

# A dictionary comprehension

print {x: x for x in range(10)}

 at 0x1130d0960>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}

Utilites - enumerate, zip and the ternary if-else operator¶

Two useful functions and an unusual operator.

# In many programming languages, loops use an index.
# This is possible in Python, but it is more
# idiomatic to use the enumerate function.

# using and index in a loop
xs = [1,2,3,4]
for i in range(len(xs)):
    print i, xs[i]
print

# using enumerate
for i, x in enumerate(xs):
    print i, x

0 1
1 2
2 3
3 4

0 1
1 2
2 3
3 4

# zip is useful when you need to iterate over matched elements of
# multiple lists

xs = [1, 2, 3, 4]
ys = [10, 20, 30, 40]
zs = ['a', 'b', 'c', 'd', 'e']

for x, y, z in zip(xs, ys, zs):
    print x, y, z

# Note that zip stops when the shortest list is exhausted

1 10 a
2 20 b
3 30 c
4 40 d

# For list comprehensions, the ternary if-else operator is sometimes very useful

[x**2 if x%2 == 0 else x**3 for x in range(10)]

[0, 1, 4, 27, 16, 125, 36, 343, 64, 729]

Decorators¶

Decorators are a type of HOF that take a function and return a wrapped function that provides additional useful properties.

Examples:

  • logging
  • profiling
  • Just-In-Time (JIT) compilation

# Here is a simple decorator to time an arbitrary function

def func_timer(func):
    """Times how long the function took."""

    def f(*args, **kwargs):
        import time
        start = time.time()
        results = func(*args, **kwargs)
        print "Elapsed: %.2fs" % (time.time() - start)
        return results

    return f

# There is a special shorthand notation for decorating functions

@func_timer
def sleepy(msg, sleep=1.0):
    """Delays a while before answering."""
    import time
    time.sleep(sleep)
    print msg

sleepy("Hello", 1.5)

The operator module¶

The operator module provides “function” versions of common Python operators (+, *, [] etc) that can be easily used where a function argument is expected.

import operator as op

# Here is another way to express the sum function
print reduce(op.add, range(10))

# The pattern can be generalized
print reduce(op.mul, range(1, 10))

my_list = [('a', 1), ('bb', 4), ('ccc', 2), ('dddd', 3)]

# standard sort
print sorted(my_list)

# return list sorted by element at position 1 (remember Python counts from 0)
print sorted(my_list, key=op.itemgetter(1))

# the key argument is quite flexible
print sorted(my_list, key=lambda x: len(x[0]), reverse=True)

[('a', 1), ('bb', 4), ('ccc', 2), ('dddd', 3)]
[('a', 1), ('ccc', 2), ('dddd', 3), ('bb', 4)]
[('dddd', 3), ('ccc', 2), ('bb', 4), ('a', 1)]

Exercises¶

1. Rewrite the following nested loop as a list comprehension

ans = []
for i in range(3):
    for j in range(4):
        ans.append((i, j))
print ans

ans = []
for i in range(3):
    for j in range(4):
        ans.append((i, j))
print ans

[(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)]

# YOUR CODE HERE

ans = [(i,j) for i in range(3) for j in range(4)]
print ans

[(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)]

2. Rewrite the following as a list comprehension

ans = map(lambda x: x*x, filter(lambda x: x%2 == 0, range(5)))
print ans

ans = map(lambda x: x*x, filter(lambda x: x%2 == 0, range(5)))
print ans

# YOUR CODE HERE

ans = [x*x for x in range(5) if x%2 == 0]
print ans

3. Convert the function below into a pure function with no global variables or side effects

x = 5
def f(alist):
    for i in range(x):
        alist.append(i)
    return alist

alist = [1,2,3]
ans = f(alist)
print ans
print alist # alist has been changed!

x = 5
def f(alist):
    for i in range(x):
        alist.append(i)
    return alist

alist = [1,2,3]
ans = f(alist)
print ans
print alist # alist has been changed!

[1, 2, 3, 0, 1, 2, 3, 4]
[1, 2, 3, 0, 1, 2, 3, 4]

# YOUR CODE HERE

def f(alist, x=5):
    """Append range(x) to alist."""
    return alist + range(x)

alist = [1,2,3]
ans = f(alist)
print ans
print alist

[1, 2, 3, 0, 1, 2, 3, 4]
[1, 2, 3]

4. Write a decorator hello that makes every wrapped function print “Hello!”

For example

@hello
def square(x):
    return x*x

when called will give the following result

[In]
square(2)
[Out]
Hello!
4

# YOUR CODE HERE

def hello(f):
    """Decorator that prints Hello!"""
    print 'Hello!'
    def func(*args, **kwargs):
        return f(*args, **kwargs)
    return func

@hello
def square(x):
    return x*x

print square(2)

5. Rewrite the factorial function so that it does not use recursion.

def fact(n):
    """Returns the factorial of n."""
    # base case
    if n==0:
        return 1
    # recursive case
    else:
        return n * fact(n-1)

def fact(n):
    """Returns the factorial of n."""
    # base case
    if n==0:
        return 1
    # recursive case
    else:
        return n * fact(n-1)

for i in range(1,11):
    print fact1(i),

1 2 6 24 120 720 5040 40320 362880 3628800

# YOUR CODE HERE

def fact1(n):
    """Returns the factorial of n."""
    return reduce(lambda x, y: x*y, range(1, n+1))

for i in range(1,11):
    print fact1(i),

1 2 6 24 120 720 5040 40320 362880 3628800

Exercise 6. Rewrite the same factorail funciotn so that it uses a cache to speed up calculations

# YOUR CODE HERE

def fact2(n, cache={0: 1}):
    """Returns the factorial of n."""
    if n in cache:
        return cache[n]
    else:
        cache[n] = n * fact2(n-1)
        return cache[n]

for i in range(1,11):
    print fact2(i),

1 2 6 24 120 720 5040 40320 362880 3628800

%timeit -n3 fact(20)
%timeit -n3 fact1(20)
%timeit -n3 fact2(20)

3 loops, best of 3: 6.6 µs per loop
3 loops, best of 3: 6.99 µs per loop
3 loops, best of 3: 318 ns per loop

7. Rewrite the following anonymous functiona as a regular named fucntion.

# YOUR CODE HERE

def f(x, y):
    return x**2 + y**2

8. Find an efficient way to extrac a subset of dict1 into a a new dictionary dict2 that only contains entrires with the keys given in the set good_keys. Note that good_keys may include keys not found in dict1 - these must be excluded when building dict2.

import numpy as np
import cPickle

try:
    dict1 = cPickle.load(open('dict1.pic'))
except:
    numbers = np.arange(1e6).astype('int') # 1 million entries
    dict1 = dict(zip(numbers, numbers))
    cPickle.dump(dict1, open('dict1.pic', 'w'), protocol=2)

good_keys = set(np.random.randint(1, 1e7, 1000))

# YOUR CODE HEREß

# dictionary comprehension
dict2 = {key: dict1[key] for key in good_keys if key in dict1}
dict2

{3798: 3798,
 38065: 38065,
 60534: 60534,
 62860: 62860,
 65901: 65901,
 69807: 69807,
 88291: 88291,
 93037: 93037,
 121629: 121629,
 141402: 141402,
 145747: 145747,
 148527: 148527,
 150344: 150344,
 152908: 152908,
 153980: 153980,
 159115: 159115,
 159816: 159816,
 166245: 166245,
 166775: 166775,
 204056: 204056,
 215282: 215282,
 217453: 217453,
 220327: 220327,
 234622: 234622,
 238067: 238067,
 240478: 240478,
 246595: 246595,
 257871: 257871,
 283049: 283049,
 291229: 291229,
 298025: 298025,
 303411: 303411,
 308318: 308318,
 314338: 314338,
 315854: 315854,
 326904: 326904,
 342248: 342248,
 351085: 351085,
 351709: 351709,
 368128: 368128,
 373994: 373994,
 382529: 382529,
 383056: 383056,
 385263: 385263,
 397214: 397214,
 402105: 402105,
 407302: 407302,
 410937: 410937,
 415658: 415658,
 419413: 419413,
 425844: 425844,
 427857: 427857,
 444312: 444312,
 452078: 452078,
 459387: 459387,
 463491: 463491,
 465533: 465533,
 476420: 476420,
 494457: 494457,
 505772: 505772,
 513386: 513386,
 533868: 533868,
 542111: 542111,
 549781: 549781,
 552654: 552654,
 554927: 554927,
 578321: 578321,
 585696: 585696,
 595181: 595181,
 598361: 598361,
 606851: 606851,
 616495: 616495,
 623269: 623269,
 623740: 623740,
 632592: 632592,
 635041: 635041,
 637283: 637283,
 649087: 649087,
 658653: 658653,
 670079: 670079,
 679081: 679081,
 687831: 687831,
 688321: 688321,
 696673: 696673,
 717431: 717431,
 740355: 740355,
 745659: 745659,
 746251: 746251,
 752638: 752638,
 759721: 759721,
 791255: 791255,
 791732: 791732,
 808228: 808228,
 809121: 809121,
 834173: 834173,
 844773: 844773,
 850271: 850271,
 851370: 851370,
 855436: 855436,
 857481: 857481,
 864807: 864807,
 870028: 870028,
 885796: 885796,
 898787: 898787,
 904119: 904119,
 906198: 906198,
 909435: 909435,
 942835: 942835,
 965580: 965580,
 974342: 974342,
 997183: 997183}

Is a function an object?

In JavaScript, functions are first-class objects, because they can have properties and methods just like any other object. What distinguishes them from other objects is that functions can be called. In brief, they are Function objects. For more examples and explanations, see the JavaScript guide about functions.

Is function same as object?

An object is a collection of functions and data. A function is a collection of commands and data.

What is the difference between function and object in Python?

Key differences between method and function in python Unlike a function, methods are called on an object. Like in our example above we call our method . i.e. “my_method” on the object “cat” whereas the function “sum” is called without any object.

Is every function an object?

Every function is an object. Objects can contain functions (methods) but an object is not necessary a function.