As a beginner, in order to cement my knowledge of functions in Python, I’ve prepared this Jupyter notebook First-Class Functions. The code in it is working, but before moving on to closures and decorators I would like you to comment on my code please.
def cube(x):
return x ** 3
cube_two = cube(2) # Call the function, set cube_two to the value returned
print(cube) # It says cube is a function
print(cube_two) # The value stored in cube_two
# <function cube at 0x010E71E0>
# 8
Returned values as list
elements and dict values
:
nums = [4, cube(4), cube(4) / cube(4) ** 0.5] # returned values as list elements
print(nums)
cube_dict = {'two_cubed': cube(2), 'five_cubed': cube(5)} # as dict values
print(cube_dict)
# [4, 64, 8.0]
# {'two_cubed': 8, 'five_cubed': 125}
Setting a variable to the cube()
function:
make_cube = cube # Take out those parentheses
print(cube)
print(make_cube) # make_cube points to the cube() function
# <function cube at 0x010E71E0>
# <function cube at 0x010E71E0>
the variable make_cube
can be used as a function, like so:
print(cube)
print(make_cube(4))
# <function cube at 0x010E71E0>
# 64
map
taking a function (int
) as an argument:
str_nums = ['1', '2', '3']
one, two, three = map(int, str_nums) # similar to [int(n) for n in str_nums] which is pythonic
print(str_nums)
print([one, two, three])
# ['1', '2', '3']
# [1, 2, 3]
reduce
is another example: it applies a function of two arguments cumulatively to the items of an iterable.
from functools import reduce
cumu_diff = reduce(lambda x, y: x - y, [3, 2, 1]) # cumulative difference
print(cumu_diff)
# 0
Creating a map
-like function (well, kind of).
def map_it(func, seq): # func (without parentheses) as an argument
for x in seq:
yield func(x)
str_nums = ['1', '2', '3']
one, two, three = map_it(int, str_nums) # same as map(int, str_nums)
print([one, two, three])
# [1, 2, 3]
Returning a function from another function:
def greet(name):
def print_message():
print("Hello, " + name + "!") # print_message() gets the name from greet()
return print_message # returning the function, not calling it
greet_et = greet('E.T.') # greet_et points to print_message()
print(greet)
print(greet_et)
print(greet_et())
# <function greet at 0x01090C90>
# <function greet.<locals>.print_message at 0x010B36A8>
# Hello, E.T.!
# None
Here's another example.
def make_paragraph(tag):
def wrapper(text):
return '<{0}>{1}</{0}>'.format(tag, text)
return wrapper
para_one = make_paragraph('p')
print(para_one('This is a small paragraph.')) # returns '<p>This is a small paragraph.</p>'
#<p>This is a small paragraph.</p>
-
2\$\begingroup\$ You need to include the code you want reviewed in the body of your question for it to be on-topic for this site. \$\endgroup\$Phrancis– Phrancis2017年10月10日 21:31:35 +00:00Commented Oct 10, 2017 at 21:31
-
\$\begingroup\$ @Phrancis, sorry for not including it. Just edited my post. \$\endgroup\$SKN– SKN2017年10月10日 22:06:51 +00:00Commented Oct 10, 2017 at 22:06
-
1\$\begingroup\$ Thanks. I have edited your code to comment out console output. \$\endgroup\$Phrancis– Phrancis2017年10月10日 22:33:27 +00:00Commented Oct 10, 2017 at 22:33
1 Answer 1
There are a few general improvements I would suggest for readability and clarity and apply throughout.
Use docstring to document your functions, modules and classes.
Since Python 3, you can now use type hints for your function and method signatures, and use static code analysis tools. This also makes the code easier to read for humans.
Applying these to your cube
function, you would get something like this:
def cube(number: int) -> int:
"""Return the cube of a number,
i.e. the number to the power of 3."""
return number ** 3
Note that I renamed the x
argument to a more descriptive number
argument. What the above means is that the function expects number
to be an integer, and that the function should return an integer.
Another problem I see with this function is that it does not check for correct input. If you type cube("hello")
then the program just crashes with an error TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
. It would be better to handle the error. There are at least two ways to do this.
Using isinstance
:
def cube(number: int) -> int:
if not isinstance(number, int):
return TypeError("{0} is not an integer".format(number))
return number ** 3
Using try
and except
:
def cube(number: int) -> int:
try:
return number ** 3
except TypeError as err:
return TypeErrorTypeError("{0} is not an integer".format(number))
Using the latter, you could also just except TypeError as err
and return err
which will return the normal error listed above.
You can also use assert
which will instead raise an AssertionError
if the type is incorrect, for instance assert(isinstance(number, int))
. This is particularly useful when setting up tests for your code.
I personally find that the first method is simpler to use, and you can use it to check multiple types at once, but the latter is the standard way to handle something that may cause an error. Choose whichever is more appropriate for the situation.
Applying the same ideas to map_it
, this is slightly more complicated since the type hints have to be imported from typing
library. Again, I renamed x
to a more descriptive name elem
.
from typing import Callable, Sequence, Iterable
def map_it(func: Callable, seq: Sequence) -> Iterable:
"""Applies a given function to a sequence and yields the result."""
for elem in seq:
yield func(elem)
You could also test using instanceof
here if you liked, which may or may not be necessary depending on your use cases.
Your greet
function can also have type errors, if you call it with a non-string type, e.g., greet(42)
. The simple fix for this (and I recommend to use it any time you need to put variables/arguments into strings) is to use .format()
instead of +
'ing string and values.
from typing import Callable
def greet(name: str) -> Callable:
"""Returns a callable greeting with given name."""
def print_message() -> None:
"""Callable to print a greeting to stdout."""
print("Hello, {0}!".format(name))
return print_message
One more thing, I noticed your code is peppered with # line comments
, in general comments like that should only be used to clarify or explain why something is done, in most cases they are not needed as the code speaks for itself.
-
1\$\begingroup\$ I use
docstrings
but forgot to write them before submitting this post. I read abouttype hints
, but have never used thetyping
module so far. Thanks for it. I’m using it right away. I’m also in favor of descriptive names for variables, parameters, functions etc. I’m taking note of the advice on validating input. I agree that sometimes formatting strings is better than concatenating them. I used the line comments to help keep track of what I was trying to achieve, but as you said most of them are not necessary as the code speaks for itself :) \$\endgroup\$SKN– SKN2017年10月11日 07:49:37 +00:00Commented Oct 11, 2017 at 7:49 -
\$\begingroup\$ I've never met a case where
.format()
was detrimental, I'd say just always use it for formatting strings. \$\endgroup\$Phrancis– Phrancis2017年10月16日 04:22:22 +00:00Commented Oct 16, 2017 at 4:22
Explore related questions
See similar questions with these tags.