开源日报 每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,坚持阅读《开源日报》,保持每日学习的好习惯。
今日推荐开源项目:《世界地图 Fantasy-Map-Generator》
今日推荐英文原文:《Are You Confused by These Python Functions?》

今日推荐开源项目:《世界地图 Fantasy-Map-Generator》传送门:项目链接
推荐理由:不管是画漫画的还是做游戏的,但凡是用到幻想世界背景的,基本都会在档案馆这样的地方贴一张世界地图。这个项目可以让你生成属于自己的幻想世界地图,并提供各种各样的配置选项,如果需要更细致的调整的话,这个项目也提供了笔刷功能便于自己动手绘画。
今日推荐英文原文:《Are You Confused by These Python Functions?》作者:Yong Cui, Ph.D.
原文链接:https://medium.com/better-programming/are-you-confused-by-these-python-functions-8e9e7f3d7605
推荐理由:在 Python 里这些看起来相似的函数并不能随意换用

Are You Confused by These Python Functions?

Sort vs. sorted, reverse vs. reversed. Understand their nuances to avoid unexpected results

It’s often said that one of the hardest things in programming is to name variables, which include functions as well. When some functions perform similar jobs, they naturally should have similar names, and they will inevitably bring some confusion to beginners. In this article, I’d like to review functions that have similar names but work differently.

1. sorted() vs. sort()

Both functions can be used to sort a list object. However, the sorted() function is a built-in function that can work with any iterable. The sort() function is actually a list’s method, which means that it can only be used with a list object. They also have different syntax.
>>> # sorted() for any iterable
>>> sorted([7, 4, 3, 2], reverse=True)
[7, 4, 3, 2]
>>> sorted({7: 'seven', 2: 'two'})
[2, 7]
>>> sorted([('nine', 9), ('one', 1)], key=lambda x: x[-1])
[('one', 1), ('nine', 9)]
>>> sorted('hello')
['e', 'h', 'l', 'l', 'o']
>>> 
>>> # sort() for a list
>>> grades = [{'name': 'John', 'grade': 99},
...           {'name': 'Mary', 'grade': 95},
...           {'name': 'Zack', 'grade': 97}]
>>> grades.sort(key=lambda x: x['grade'], reverse=True)
>>> grades
[{'name': 'John', 'grade': 99}, {'name': 'Zack', 'grade': 97}, {'name': 'Mary', 'grade': 95}]
  • Both functions have the arguments reverse and key. The reverse argument is to request the sorting in reverse order, while the key argument is to specify the sorting algorithm beyond the default order. It can be set as a lambda function or a regular function.
  • The sorted() function can work with any iterable. In the case of the dictionary (Line 4), the iterable from a dictionary object is the keys, and thus the sorted() function returns a list object of the keys.
  • In a similar fashion, when you pass a string to the sorted() function, it will return a list of characters because a string is treated as an iterable consisting of individual characters.
  • The sorted() function returns a list object in the sorted order, while the sort() function doesn’t return anything (or returns None, to be precise). In other words, the list object calling the sort() function is to be sorted in place.

2. reversed() vs. reverse()

The use scenarios for these two are similar to sorted() vs. sort(). The reversed() function works with any sequence data, such as lists and strings, while the reverse() function is a list’s method.
>>> # reversed() for any sequence data
>>> reversed([1, 2, 3])
<list_reverseiterator object at 0x11b035490>
>>> list(reversed((1, 2, 3)))
[3, 2, 1]
>>> tuple(reversed('hello'))
('o', 'l', 'l', 'e', 'h')
>>> 
>>> # reverse() for a list
>>> numbers = [1, 2, 3, 4]
>>> numbers.reverse()
>>> numbers
[4, 3, 2, 1]
  • Unlike the sorted() function, which returns a list, the reversed() function returns a reverse iterator, which is essentially an iterable but can be directly used in the for loop.
  • To construct a list or a tuple, you can utilize the returned iterator to construct a sequence data in the reverse order from the original one.
  • As with the sort() method on a list, the reverse() method is to reverse the order of the list’s elements in place, and thus it returns None.

3. append() vs. extend()

Both of these functions are list objects’ methods. Both are used to add items to an exiting list object. The following code shows you how to use them and is followed by some explanations.
>>> # Create a list object to begin with
>>> integers = [1, 2, 3]
>>> 
>>> # append()
>>> integers.append(4)
>>> integers.append([5, 6])
>>> integers
[1, 2, 3, 4, [5, 6]]
>>> 
>>> # extend()
>>> integers.extend({7, 8, 9})
>>> integers.extend('hello')
>>> integers
[1, 2, 3, 4, [5, 6], 8, 9, 7, 'h', 'e', 'l', 'l', 'o']
  • Both functions modify the list object in place and return None.
  • The append() function is to append a single item to the end of the list. If you want to add an item to a specific location, you should use the insert() method.
  • The extend() function is to append all the elements in an iterable to the end of the list object. In the case of the string (Line 12), the characters are appended. For the set object, you notice that the order of the elements inserted doesn’t reflect the items that we use to create the set object, which is the expected behavior of a set object that holds unordered items.

4. is vs. ==

Both functions are used to compare objects. However, they have some nuances that you should be aware of. We refer to is as the identity comparison and == as the value equality comparison. One thing to note is that when we say the identity of an object, we can simply refer to the memory address of a particular object using the id() function. Let’s see some examples.
>>> # Create a function for comparison
>>> def compare_two_objects(obj0, obj1):
...     print("obj0 id:", id(obj0), "obj1 id:", id(obj1))
...     print("Compare Identity:", obj0 is obj1)
...     print("Compare Value:", obj0 == obj1)
... 
>>> compare_two_objects([1, 2, 3], [1, 2, 3])
obj0 id: 4749717568 obj1 id: 4748799936
Compare Identity: False
Compare Value: True
>>> compare_two_objects([1, 2].reverse(), None)
obj0 id: 4465453816 obj1 id: 4465453816
Compare Identity: True
Compare Value: True
  • When the objects have the same memory address, they’re the same objects and have the same identity and value. Thus, is and == produce the same boolean value.
  • In most cases, even objects can have the same values, but they can be different objects in the memory.
  • Some special objects, such as None and small integers (e.g., 1, 2) always have the same identity because they’re used so much and have already been instantiated when Python was loaded. We share these objects between different modules.
  • In most cases, we use == to compare objects because we’re mostly interested in using the values of the objects. However, we do prefer using is when we examine if an object is None or not (i.e., if obj is None).

5. remove(), pop() vs. clear()

These three functions are list objects’ methods, which is confusing. Let’s see their usages first, and we’ll discuss their nuances next.
>>> # Create a list of integers
>>> integers = [1, 2, 3, 4, 5]
>>> 
>>> # remove()
>>> integers.remove(1)
>>> integers
[2, 3, 4, 5]
>>> 
>>> # pop()
>>> integers.pop()
5
>>> integers
[2, 3, 4]
>>> integers.pop(0)
2
>>> integers
[3, 4]
>>> 
>>> # clear()
>>> integers.clear()
>>> integers
[]
  • To remove a particular item, you can specify it in the remove() function. But be cautious. If the item that is to be removed isn’t in the list, you’ll encounter a ValueError exception, as shown below.
>>> [1, 2, 3, 4].remove(5)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ValueError: list.remove(x): x not in list
  • The pop() method is removing the last item by default. If you want to remove an element at a specific index, you can specify it in the function, as shown in Line 14. Importantly, this method will return the popped item, and thus it’s particularly useful if you want to work with the removed item. One thing to note is that this method will raise an IndexError if the list has become empty.
>>> [].pop()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
IndexError: pop from empty list
  • The clear() method is to remove all items in the list, which should be straightforward.

6. any() vs. all()

Both functions are used to check conditions using iterables. The returned value for both functions are boolean values — True or False. The following code shows you some usages.
>>> # Create a function to check iterables
>>> def check_any_all(iterable):
...     print(f"any({iterable!r}): {any(iterable)}")
...     print(f"all({iterable!r}): {all(iterable)}")
... 
>>> 
>>> check_any_all([1, False, 2])
any([1, False, 2]): True
all([1, False, 2]): False
>>> check_any_all([True, True, True])
any([True, True, True]): True
all([True, True, True]): True
>>> check_any_all(tuple())
any(()): False
all(()): True
  • When any item in the iterable is True, any() returns True. Otherwise, it returns False.
  • Only when all the items in the iterable are True does all() return True. Otherwise, it returns False.
  • Special consideration is given when the iterable is empty. As you can see, any() returns False, while all() returns True. Many people are confused by this behavior. This is how you can remember that: By default, any() returns False. Only can it find a non-False item, it will return True immediately — a short-circuit evaluation. By contrast, all() returns True by default. Only can it find a non-True item, it will return False immediately — again, a short-circuit evaluation.

7. issuperset() vs. issubset()

We have talked about several methods related to lists. In terms of set objects, the one pair of methods that I find confusing is issuperset() and issubset(). Let’s first see how they work with some trivial examples.
>>> # Create a function to check set relationship
>>> def check_set_relation(set0, set1):
...     print(f"Is {set0} a superset of {set1}?", set0.issuperset(set1))
...     print(f"Is {set0} a subset of {set1}?", set0.issubset(set1))
... 
>>> check_set_relation({1, 2, 3}, {2, 3})
Is {1, 2, 3} a superset of {2, 3}? True
Is {1, 2, 3} a subset of {2, 3}? False
>>> check_set_relation({3, 4}, {3, 4, 5})
Is {3, 4} a superset of {3, 4, 5}? False
Is {3, 4} a subset of {3, 4, 5}? True
  • Both methods are used to check the relationship between two set objects.
  • The key distinction to understand these methods is that the caller of the method is to be checked against the input argument. For instance, set0.issuperset(set1) is to check whether set0 is a superset of set1.

8. zip() vs. zip_longest()

The zip() function is a built-in function that is used to create a zip object that can be used in a for loop. It takes multiple iterables and creates a generator that yields a tuple object each time. Each tuple object consists of elements from each iterable at the corresponding position. The zip_longest() works similarly, but has some differences.
>>> # Create two lists for zipping
>>> list0 = [1, 2, 3]
>>> list1 = ['a', 'b', 'c', 'd', 'e']
>>> 
>>> # Zip two lists with zip()
>>> zipped0 = list(zip(list0, list1))
>>> zipped0
[(1, 'a'), (2, 'b'), (3, 'c')]
>>> 
>>> # Zip two lists with zip_longest()
>>> from itertools import zip_longest
>>> zipped1 = list(zip_longest(list0, list1))
>>> zipped1
[(1, 'a'), (2, 'b'), (3, 'c'), (None, 'd'), (None, 'e')]
  • To create a list of the zip object, you’ll include the zip object in the list constructor method, as shown in Line 6.
  • The zip() function will create the tuples with the number that matches the length of the shortest iterable. In our example, it only creates three tuples because the shorter list (i.e., list0) only has three items.
  • By contrast, with the zip_longest() function, the number of created tuples will match the length of the longest iterable. For the shorter iterables, the tuples will use None instead.

Conclusions

In this article, we reviewed eight groups of functions with similar functionalities that can be somewhat confusing to some Python beginners.

Thanks for reading this piece.
下载开源日报APP:https://openingsource.org/2579/
加入我们:https://openingsource.org/about/join/
关注我们:https://openingsource.org/about/love/