Basics Intermediate Advanced
aialgorithmsapibest-practicescareercommunitydatabasesdata-sciencedata-structuresdata-vizdevopsdjangodockereditorsflaskfront-endgamedevguimachine-learningnewsnumpyprojectspythonstdlibtestingtoolsweb-devweb-scraping

Working With the Python operator Module
Table of Contents
Whenever you perform calculations in Python, you make use of built-inoperators such as+,%, and**. Did you know that Python also provides anoperator module? While it may seem that the purpose ofoperator is to provide an alternative to these existing Python operators, the module actually has a far more specialized purpose than this.
The Pythonoperator module provides you with a set of functions, many of which correspond to the built-in operators but don’t replace them. The module also provides additional functionality, as you’ll soon discover.
In this tutorial, you’ll learn how to:
- Use any of the basicoperator-equivalent functions
- Pass
operatorfunctions asarguments - Serialize
operatorfunctions for later use - Gauge
operatorfunctionperformance against common alternatives - Usehigher-order
operatorfunctions in a range of interesting example cases
To get the most out of this tutorial, you should be familiar with the basic Pythonoperators and thefunctional programming idea of one function returning another function back to its caller.
With that knowledge, you’re ready to dive in and learn how to use the greatly misunderstoodoperator module to your advantage.
Get Your Code:Click here to download the free sample code that shows you how to use Python’soperator module.
Using the Pythonoperator Module’s Basic Functions
In this section, you’ll learn about theoperator module’soperator-equivalent functions that mimic built-in operators, and you’ll pass them as arguments to higher-order functions. You’ll also learn how to save them for later use. Finally, you’ll investigate the performance of the operator-equivalent functions and uncover why you should never use them where a built-in Python operator will do instead.
Learning How the Basic Functions Work
The Pythonoperator module contains over forty functions, many of which are equivalent to the Python operators that you’re already familiar with. Here’s an example:
>>>importoperator>>>operator.add(5,3)# 5 + 38>>>operator.__add__(5,3)# 5 + 38Here, you add5 and3 together using bothadd() and__add__(). Both produce the same result. On the face of it, these functions provide you with the same functionality as the Python+ operator, but this isn’t their purpose.
Note: Most of theoperator module functions contain two names, a dunder version and a without-dunder version. In the previous example,operator.__add__(5, 3) is the dunder version because it includes double underscores. From this point forward, you’ll use only the without-dunder versions, such asoperator.add(5, 3). The dunder versions are for backward compatibility with the Python 2 version ofoperator.
If you take a look at thelist of functions thatoperator provides for you, then you’ll discover that they cover not only the arithmetic operators, but also the equality, identity, Boolean, and even bitwise operators. Try out a random selection of them:
>>>operator.truediv(5,2)# 5 / 22.5>>>operator.ge(5,2)# 5 >= 2True>>>operator.is_("X","Y")# "X" is "Y"False>>>operator.not_(5<3)# not 5 < 3True>>>bin(operator.and_(0b101,0b110))# 0b101 & 0b110'0b100'In the code above, you work with a selection from the five main categories. First, you use the equivalent of anarithmetic operator, and then you try outequality and identity operator examples in the second and third examples, respectively. In the fourth example, you try aBoolean logical operator, while the final example uses abitwise operator The comments show the equivalent Python operators.
Before reading the rest of this tutorial, feel free to take some time to experiment with the range of operator-equivalent functions that Python’soperator module provides for you. You’ll learn how to use them next.
Passing Operators as Arguments Into Higher-Order Functions
You use the operator-equivalent functions most commonly as arguments forhigher-order functions. You could write a higher-order function that performs a series of different tasks depending on theoperator function passed to it. Suppose, for example, you wanted a single function that could perform addition, subtraction, multiplication, and division. One messy way of doing this would be to use anif …elif statement as follows:
>>>defperform_operation(operator_string,operand1,operand2):...ifoperator_string=="+":...returnoperand1+operand2...elifoperator_string=="-":...returnoperand1-operand2...elifoperator_string=="*":...returnoperand1*operand2...elifoperator_string=="/":...returnoperand1/operand2...else:...return"Invalid operator."...In yourperform_operation() function, the first parameter is astring representing one of the four basic arithmetic operations. To test the function, you pass in each of the four operators. The results are what you’d expect:
>>>number1=10>>>number2=5>>>calculations=["+","-","*","/"]>>>forop_stringincalculations:...perform_operation(op_string,number1,number2)...155502.0This code is not only messy, but also limited to the four operators defined in theelif clauses. Try, for example, passing in amodulo operator (%), and the function will return an"Invalid operator" message instead of the modulo division result that you were hoping for.
This is where you can make excellent use of theoperator functions. Passing these into a function gives you several advantages:
>>>defperform_operation(operator_function,operand1,operand2):...returnoperator_function(operand1,operand2)...This time, you’ve improved yourperform_operation() function so that the first parameter can acceptany of theoperator module’s functions that take exactly two arguments. The second and third parameters are those arguments.
The revised test code is similar to what you did before, except you pass inoperator functions for yourperform_operation() function to use:
>>>fromoperatorimportadd,sub,mul,truediv>>>number1=10>>>number2=5>>>calculations=[add,sub,mul,truediv]>>>forop_functionincalculations:...perform_operation(op_function,number1,number2)...155502.0This time, yourcalculations list contains references to the functions themselves. Note that you pass in functionnames and not functioncalls. In other words, you pass inadd toperform_operation(), and notadd(). You’re passing in the function object, not the result of its execution. Remember, the name of a function is actually a reference to its code. Using the() syntax calls the function.
There are two advantages to using your updated version ofperform_operation(). The first is expandability. You can use the revised code with any of the otheroperator functions that require exactly two arguments. Indeed, you might like to experiment by passing theoperator module’smod(),pow(), andrepeat() functions to both versions of your function. Your updated version works as expected, while your original version returns"Invalid operator".
The second advantage is readability. Take a look at both versions of yourperform_operation() function, and you’ll notice that your second version is not only significantly shorter, but also more readable, than the original.
Passing functions as arguments to other functions is a feature that you’ll often use infunctional programming. This is one of the main purposes of theoperator module. You’ll study other examples of this later.
Serializingoperator Module Functions
One way of saving objects, including functions, to disk is toserialize them. In other words, your code converts them into byte streams and stores them on disk for later use. Conversely, when you read serialized objects back from disk, you deserialize them, allowing them to be read from disk into a program for use.
There are several reasons why you might serialize functions, including to save them for future use in another program or to pass them between different processes running on one or more computers.
A common way to serialize functions in Python is by using thepickle module. This, along with its dictionary wrappershelve, provides one of the most efficient ways of storing data. However, when you serialize a function usingpickle, then you only serialize its fullyqualified name, not the code in the function body. When you deserialize a function, the environment must provide access to the function’s code. The function can’t work otherwise.
To see an example, you’ll revisit the earlierperform_operation() example. You’ll call differentoperator functions to perform the different operations. The following code adds a dictionary that you’ll use to map a string operator to its matching function:
>>>importoperator>>>operators={..."+":operator.add,..."-":operator.sub,..."*":operator.mul,..."/":operator.truediv,...}>>>defperform_operation(op_string,number1,number2):...returnoperators[op_string](number1,number2)...>>>perform_operation("-",10,5)5The operations supported byperform_operation() are the ones defined inoperators. As an example, you run the"-" operation, which callsoperator.sub() in the background.
One way to share the supported operators between processes is to serialize theoperators dictionary to disk. You can do this usingpickle as follows:
>>>importpickle>>>withopen("operators.pkl",mode="wb")asf:...pickle.dump(operators,f)...You open abinary file for writing. To serializeoperators, you callpickle.dump() and pass the structure that you’re serializing and the handle of the destination file.
This creates the fileoperators.pkl in your local working directory. To demonstrate how to reuseoperators in a different process, restart your Python shell and load the pickled file:
>>>importpickle>>>withopen("operators.pkl",mode="rb")asf:...operators=pickle.load(f)...>>>operators{'+': <built-in function add>, '-': <built-in function sub>, '*': <built-in function mul>, '/': <built-in function truediv>}Firstly, you importpickle again and reopen the binary file for reading. To read theoperator structure, you usepickle.load() and pass in the file handle. Your code then reads in the saved definition and assigns it to a variable namedoperators. This name doesn’t need to match your original name. This variable points to the dictionary that references the different functions, assuming they’re available.
Note that you don’t need to explicitly importoperator, although the module needs to be available for Python to import in the background.
You can defineperform_operation() again to see that it can refer to and use the restoredoperators:
>>>defperform_operation(op_string,number1,number2):...returnoperators[op_string](number1,number2)...>>>perform_operation("*",10,5)50Great! Your code handles multiplication as you’d expect.
Now, there’s nothing special aboutoperator supporting pickling of functions. You can pickle and unpickle any top-level function, as long as Python is able to import it in the environment where you’re loading the pickled file.
However, you can’t serialize anonymouslambda functions like this. If you implemented the example without using theoperator module, then you’d probably define the dictionary as follows:
>>>operators={..."+":lambdaa,b:a+b,..."-":lambdaa,b:a-b,..."*":lambdaa,b:a*b,..."/":lambdaa,b:a/b,...}Thelambda construct is a quick way to define simple functions, and they can be quite useful. However, becausepickle doesn’t serialize the function body, only the name of the function, you can’t serialize the nameless lambda functions:
>>>importpickle>>>withopen("operators.pkl",mode="wb")asf:...pickle.dump(operators,f)...Traceback (most recent call last):...PicklingError:Can't pickle <function <lambda> at 0x7f5b946cfba0>: ...If you try to serialize lambda functions withpickle, then you’ll get an error. This is a case where you can often useoperator functions instead of lambda functions.
Look back at your serialization code and notice that you imported bothoperator andpickle, while your deserialization code imported onlypickle. You didn’t need to importoperator becausepickle did this automatically for you when you called itsload() function. This works because the built-inoperator module is readily available.
Investigatingoperator Function Performance Against the Alternatives
Now that you have an idea of how to use the operator-equivalent functions, you may wonder if you should use them instead of either the Python operators or lambda functions. The answer is no to the first case and yes to the second. The built-in Python operators are always significantly faster than theiroperator module counterparts. However, theoperator module’s functions are faster than lambda functions, and they’re more readable as well.
If you wish to time theoperator module’s functions against their built-in or lambda equivalents, then you can use thetimeit module. The best way to do this is to run it directly from the command line:
PS>python-mtimeit"(lambda a, b: a + b)(10, 10)"5000000 loops, best of 5: 82.3 nsec per loopPS>python-mtimeit-s"from operator import add""add(10, 10)"10000000 loops, best of 5: 24.5 nsec per loopPS>python-mtimeit"10 + 10"50000000 loops, best of 5: 5.19 nsec per loopPS>python-mtimeit"(lambda a, b: a ** b)(10, 10)"1000000 loops, best of 5: 226 nsec per loopPS>python-mtimeit-s"from operator import pow""pow(10, 10)"2000000 loops, best of 5: 170 nsec per loopPS>python-mtimeit"10 ** 10"50000000 loops, best of 5: 5.18 nsec per loopThe abovePowerShell session uses thetimeit module to compare the performance of various implementations of addition and exponentiation. Your results show that for both operations, the built-in operator is fastest, with theoperator module function only outperforming the lambda function. The actual time values themselves are machine-specific, but their relative differences are significant.
Note: Python’stimeit module allows you to time small pieces of your code. You usually invoketimeit from the command line usingpython -m timeit followed by a string containing the command that you want to measure. You use the-s switch to indicate code that you want to run once justbefore the timing begins. In the example above, you used-s to importpow() andadd() from theoperator module before timing your code.
Go ahead and try the otheroperator functions out for yourself. Although the exact timings will vary from machine to machine, their relative differences will still show that built-in operators are always faster than theoperator module equivalents, which are always faster than lambda functions.
Now you’re familiar with the operator-equivalent functions from theoperator module, but you might want to spend some time exploring the rest of these functions. Once you’re ready to move on, keep reading to investigate some of the other ways to useoperator.
Using the Pythonoperator Module’s Higher-Order Functions
In this section, you’ll learn about three of thehigher-order functions that Python’soperator module makes available to you:itemgetter(),attrgetter(), andmethodcaller(). You’ll learn how these allow you to work with Python collections in a range of useful ways that encourage afunctional style of Python programming.
Selecting Values From Multidimensional Collections Withitemgetter()
The first function that you’ll learn about isoperator.itemgetter(). In its basic form, you pass it a single parameter that represents anindex. Thenitemgetter() returns a function that, when passed a collection, returns the element at that index.
To begin with, you create alist ofdictionaries:
>>>musician_dicts=[...{"id":1,"fname":"Brian","lname":"Wilson","group":"Beach Boys"},...{"id":2,"fname":"Carl","lname":"Wilson","group":"Beach Boys"},...{"id":3,"fname":"Dennis","lname":"Wilson","group":"Beach Boys"},...{"id":4,"fname":"Bruce","lname":"Johnston","group":"Beach Boys"},...{"id":5,"fname":"Hank","lname":"Marvin","group":"Shadows"},...{"id":6,"fname":"Bruce","lname":"Welch","group":"Shadows"},...{"id":7,"fname":"Brian","lname":"Bennett","group":"Shadows"},...]Each dictionary contains a record of a musician belonging to one of two groups, theBeach Boys or theShadows. To learn howitemgetter() works, suppose you want to select a single item frommusician_dicts:
>>>importoperator>>>get_element_four=operator.itemgetter(4)>>>get_element_four(musician_dicts){"id": 5, "fname": "Hank", "lname": "Marvin", "group": "Shadows"}When you passitemgetter() an index of4, it returns a function, referenced byget_element_four, that returns the element at index position4 in a collection. In other words,get_element_four(musician_dicts) returnsmusician_dicts[4]. Remember that list elements are indexed starting at0 and not1. This meansitemgetter(4) actually returns thefifth element in the list.
Next suppose you want to select elements from positions1,3, and5. To do this, you passitemgetter() multiple index values:
>>>get_elements_one_three_five=operator.itemgetter(1,3,5)>>>get_elements_one_three_five(musician_dicts)({"id": 2, "fname": "Carl", "lname": "Wilson", "group": "Beach Boys"}, {"id": 4, "fname": "Bruce", "lname": "Johnston", "group": "Beach Boys"}, {"id": 6, "fname": "Bruce", "lname": "Welch", "group": "Shadows"})Hereitemgetter() creates a function that you use to find all three elements. Your function returns atuple containing the results.
Now suppose you want to list only the first and last name values from the dictionaries at index positions1,3, and5. To do this, you passitemgetter() the"fname" and"lname" keys:
>>>get_names=operator.itemgetter("fname","lname")>>>formusicianinget_elements_one_three_five(musician_dicts):...print(get_names(musician))...("Carl", "Wilson")("Bruce", "Johnston")("Bruce", "Welch")This time,itemgetter() provides a function for you to get the values associated with the"fname" and"lname" keys. Your code iterates over the tuple of dictionaries returned byget_elements_one_three_five() and passes each one to yourget_names() function. Each call toget_names() returns a tuple containing the values associated with the"fname" and"lname" dictionary keys of the dictionaries at positions1,3, and5 ofmusician_dicts.
Two Python functions that you may already be aware of aremin() andmax(). You can use these to find the lowest and highest elements in a list:
>>>prices=[100,45,345,639]>>>min(prices)45>>>max(prices)639In the above code, you’ve gotten the lowest and highest values:min() returns the cheapest item, whilemax() returns the most expensive.
Themin() andmax() functions contain akey parameter that accepts a function. If you create the function usingitemgetter(), then you can use it to instructmin() andmax() to analyze specific elements within a list of lists or dictionaries. To explore this, you first create a list of lists of musicians:
>>>musician_lists=[...[1,"Brian","Wilson","Beach Boys"],...[2,"Carl","Wilson","Beach Boys"],...[3,"Dennis","Wilson","Beach Boys"],...[4,"Bruce","Johnston","Beach Boys"],...[5,"Hank","Marvin","Shadows"],...[6,"Bruce","Welch","Shadows"],...[7,"Brian","Bennett","Shadows"],...]The content ofmusician_lists is identical tomusician_dicts, except each record is in a list. This time, suppose you want to find the list elements with the lowest and highestid values:
>>>get_id=operator.itemgetter(0)>>>min(musician_lists,key=get_id)[1, "Brian", "Wilson", "Beach Boys"]>>>max(musician_lists,key=get_id)[7, "Brian", "Bennett", "Shadows"]You first create a function usingitemgetter() to select the first element from a list. You then pass this as thekey parameter ofmin() andmax(). Themin() andmax() functions will return to you the lists with the lowest and highest values in their index0 positions, respectively.
You can do the same with a list of dictionaries by usingitemgetter() to create a function that selects key names. Suppose you want the dictionary that contains the musician whose last name comes first in the alphabet:
>>>get_lname=operator.itemgetter("lname")>>>min(musician_dicts,key=get_lname){"id": 7, "fname": "Brian", "lname": "Bennett", "group": "Shadows"}This time, you set up anitemgetter() function that selects the"lname" dictionary key. You then pass this as themin() function’skey parameter. Themin() function returns the dictionary with the lowest value of"lname". The"Bennett" record is the result. Why not retry this withmax()? Try predicting what will happen before running your code to check.
Sorting Multidimensional Collections Withitemgetter()
In addition to selecting specific elements, you can use the function fromitemgetter() as thekey parameter to sort data. One of the common Python functions that you may have already used issorted(). Thesorted() function creates a new sorted list:
>>>star_wars_movies_release_order=[4,5,6,1,2,3,7,8,9]>>>sorted(star_wars_movies_release_order)[1, 2, 3, 4, 5, 6, 7, 8, 9]Now the elements of the new list are in ascending order. If you wanted to sort the list in place, then you could use the.sort() method instead. As an exercise, you might like to try it.
It’s also possible to useitemgetter() to sort lists. This allows you to sort multidimensional lists by specific elements. To do this, you pass anitemgetter() function into thesorted() function’skey parameter.
Consider once more yourmusician_lists:
>>>musician_lists=[...[1,"Brian","Wilson","Beach Boys"],...[2,"Carl","Wilson","Beach Boys"],...[3,"Dennis","Wilson","Beach Boys"],...[4,"Bruce","Johnston","Beach Boys"],...[5,"Hank","Marvin","Shadows"],...[6,"Bruce","Welch","Shadows"],...[7,"Brian","Bennett","Shadows"],...]You’ll now sort this list usingitemgetter(). To begin with, you decide to sort the list elements in descending order byid value:
>>>importoperator>>>get_id=operator.itemgetter(0)>>>sorted(musician_lists,key=get_id,reverse=True)[[7, "Brian", "Bennett", "Shadows"], [6, "Bruce", "Welch", "Shadows"], [5, "Hank", "Marvin", "Shadows"], [4, "Bruce", "Johnston", "Beach Boys"], [3, "Dennis", "Wilson", "Beach Boys"], [2, "Carl", "Wilson", "Beach Boys"], [1, "Brian", "Wilson", "Beach Boys"] ]To do this, you useitemgetter() to create a function that will select index position0, the musician’sid. You then callsorted() and pass itmusician_lists plus the reference to the function fromitemgetter() as itskey. To ensure the sort is in descending order, you setreverse=True. Now your list is sorted in descending order byid.
It’s also possible to perform more complex sorting of multidimensional lists. Suppose you want to sort each list in reverse order withinmusician_lists. First, you sort by last name, then you sort any lists with the same last name by first name. In other words, you’re sorting on first name within last name:
>>>get_elements_two_one=operator.itemgetter(2,1)>>>sorted(musician_lists,key=get_elements_two_one,reverse=True)[[3, "Dennis", "Wilson", "Beach Boys"], [2, "Carl", "Wilson", "Beach Boys"], [1, "Brian", "Wilson", "Beach Boys"], [6, "Bruce", "Welch", "Shadows"], [5, "Hank", "Marvin", "Shadows"], [4, "Bruce", "Johnston", "Beach Boys"], [7, "Brian", "Bennett", "Shadows"]]This time, you passitemgetter() two arguments, positions2 and1. Your list is sorted in reverse alphabetical order, first by last name (2), then by first name (1) where applicable. In other words, the threeWilson records appear first, withDennis,Carl, andBrian in descending order. Feel free to rerun this code and use it to select other fields. Try to predict what will happen before running your code to test your understanding.
The same principles also apply to dictionaries, provided you specify the keys whose values you wish to sort. Again, you can use themusician_dicts list of dictionaries:
>>>musician_dicts=[...{"id":1,"fname":"Brian","lname":"Wilson","group":"Beach Boys"},...{"id":2,"fname":"Carl","lname":"Wilson","group":"Beach Boys"},...{"id":3,"fname":"Dennis","lname":"Wilson","group":"Beach Boys"},...{"id":4,"fname":"Bruce","lname":"Johnston","group":"Beach Boys"},...{"id":5,"fname":"Hank","lname":"Marvin","group":"Shadows"},...{"id":6,"fname":"Bruce","lname":"Welch","group":"Shadows"},...{"id":7,"fname":"Brian","lname":"Bennett","group":"Shadows"},...]>>>get_names=operator.itemgetter("lname","fname")>>>sorted(musician_dicts,key=get_names,reverse=True)[{"id": 3, "fname": "Dennis", "lname": "Wilson", "group": "Beach Boys"}, {"id": 2, "fname": "Carl", "lname": "Wilson", "group": "Beach Boys"}, {"id": 1, "fname": "Brian", "lname": "Wilson", "group": "Beach Boys"}, {"id": 6, "fname": "Bruce", "lname": "Welch", "group": "Shadows"}, {"id": 5, "fname": "Hank", "lname": "Marvin", "group": "Shadows"}, {"id": 4, "fname": "Bruce", "lname": "Johnston", "group": "Beach Boys"}, {"id": 7, "fname": "Brian", "lname": "Bennett", "group": "Shadows"} ]This time, you passitemgetter() the"fname" and"lname" keys. The output is similar to what you got previously, except it now contains dictionaries. While you created a function that selected the index elements2 and1 in the previous example, this time your function selects the dictionary keys"lname" and"fname".
Retrieving Attributes From Objects With attrgetter()
Next you’ll learn about theoperator module’sattrgetter() function. Theattrgetter() function allows you to get an object’s attributes. The function accepts one or more attributes to be retrieved from an object, and it returns a function that will return those attributes from whatever object you pass to it. The objects passed toattrgetter() don’t need to be of the same type. They only need to contain the attribute that you want to retrieve.
To understand howattrgetter() works, you’ll first need to create a newclass:
>>>fromdataclassesimportdataclass>>>@dataclass...classMusician:...id:int...fname:str...lname:str...group:str...You’ve created adata class namedMusician. Your data class’s primary purpose is to hold data about different musician objects, although it could also contain methods, as you’ll discover later. The@dataclassdecorator allows you to define the attributes directly by specifying their names and atype hint for their data types. Your class contains four attributes that describe a musician.
Note: You may be wondering where.__init__() has gone. One of the benefits of using a data class is that there’s no longer a need for an explicit initializer. To create an object, you pass in values for each of the attributes in the class. In theMusician class above, the first attribute gets assigned to.id, the second to.fname, and so on.
Next, you need a list of objects to work with. You’ll reusemusician_lists from earlier and use it to generate a list of objects namedgroup_members:
>>>musician_lists=[...[1,"Brian","Wilson","Beach Boys"],...[2,"Carl","Wilson","Beach Boys"],...[3,"Dennis","Wilson","Beach Boys"],...[4,"Bruce","Johnston","Beach Boys"],...[5,"Hank","Marvin","Shadows"],...[6,"Bruce","Welch","Shadows"],...[7,"Brian","Bennett","Shadows"],...]>>>group_members=[Musician(*musician)formusicianinmusician_lists]You populategroup_members withMusician objects by transformingmusician_lists with alist comprehension.
Note: You may have noticed that you used*musician to pass each list when creating class objects. The asterisk tells Python tounpack the list into its individual elements when creating the objects. In other words, the first object will have an.id attribute of1, an.fname attribute of"Brian", and so on.
You now have agroup_members list that contains sevenMusician objects, four from the Beach Boys and three from the Shadows. You’ll next learn how you can use these withattrgetter().
Suppose you wanted to retrieve the.fname attribute from eachgroup_members element:
>>>importoperator>>>get_fname=operator.attrgetter("fname")>>>forpersoningroup_members:...print(get_fname(person))...BrianCarlDennisBruceHankBruceBrianYou first callattrgetter() and specify that its output will get the.fname attribute. Theattrgetter() function then returns a function that will get you the.fname attribute of whatever object you pass to it. When you loop over your collection ofMusician objects,get_fname() will return the.fname attributes.
Theattrgetter() function also allows you to set up a function that can return several attributes at once. Suppose this time you want to return both the.id and.lname attributes of each object:
>>>get_id_lname=operator.attrgetter("id","lname")>>>forpersoningroup_members:...print(get_id_lname(person))...(1, "Wilson")(2, "Wilson")(3, "Wilson")(4, "Johnston")(5, "Marvin")(6, "Welch")(7, "Bennett")This time, when you callattrgetter() and ask for both the.id and.lname attributes, you create a function capable of reading both attributes for any object. When run, your code returns both.id and.lname from the list ofMusician objects passed to it. Of course, you can apply this function to any object, whether built in or custom, as long as the object contains both an.id and an.lname attribute.
Sorting and Searching Lists of Objects by Attribute Withattrgetter()
Theattrgetter() function also allows you to sort a collection of objects by their attributes. You’ll try this out by sorting theMusician objects ingroup_members by each object’s.id in reverse order.
First, make sure that you have access togroup_members, as defined in theprevious section of the tutorial. Then, you can use the power ofattrgetter() to perform your custom sort:
>>>get_id=operator.attrgetter("id")>>>formusicianinsorted(group_members,key=get_id,reverse=True):...print(musician)...Musician(id=7, fname='Brian', lname='Bennett', group='Shadows')Musician(id=6, fname='Bruce', lname='Welch', group='Shadows')Musician(id=5, fname='Hank', lname='Marvin', group='Shadows')Musician(id=4, fname='Bruce', lname='Johnston', group='Beach Boys')Musician(id=3, fname='Dennis', lname='Wilson', group='Beach Boys')Musician(id=2, fname='Carl', lname='Wilson', group='Beach Boys')Musician(id=1, fname='Brian', lname='Wilson', group='Beach Boys')In this code snippet, you set up anattrgetter() function that can return an.id attribute. To sort the list in reverse by.id, you assign theget_id reference to thesorted() method’skey parameter and setreverse=True. When you print theMusician objects, the output shows that your sort has indeed worked.
If you want to show the object with the highest or lowest.id value, then you use themin() ormax() function and pass it a reference to yourget_id() function as itskey:
>>>min(group_members,key=get_id)Musician(id=1, fname='Brian', lname='Wilson', group='Beach Boys')>>>max(group_members,key=get_id)Musician(id=7, fname='Brian', lname='Bennett', group='Shadows')You first create anattrgetter() function that can locate an.id attribute. Then you pass it into themin() andmax() functions. In this case, your code returns the objects containing the lowest and highest.id attribute values. In this case, those are the objects with.id values of1 and7. You might like to experiment with this further by sorting on other attributes.
Calling Methods on Objects Withmethodcaller()
The final function that you’ll learn about ismethodcaller(). It’s conceptually similar toattrgetter(), except it works on methods. To use it, you pass in the name of a method, along with any parameters that the method requires. It’ll return a function that will call the method on any object that you pass to it. The objects passed tomethodcaller() don’t need to be of the same type. They only need to contain the method that you’re calling.
To learn aboutmethodcaller(), you first need to enhance your existingMusician data class with a method:
>>>fromdataclassesimportdataclass>>>@dataclass...classMusician:...id:int...fname:str...lname:str...group:str......defget_full_name(self,last_name_first=False):...iflast_name_first:...returnf"{self.lname},{self.fname}"...returnf"{self.fname}{self.lname}"...You add a.get_full_name() method toMusician that accepts a single parameter namedlast_name_first with a default ofFalse. This allows you to specify the order in which the names are returned.
Suppose you want to call.get_full_name() on each object in the previously definedgroup_members list:
>>>importoperator>>>first_last=operator.methodcaller("get_full_name")>>>forpersoningroup_members:...print(first_last(person))...Brian WilsonCarl WilsonDennis WilsonBruce JohnstonHank MarvinBruce WelchHere you usemethodcaller() to create a function namedfirst_last() that will call the.get_full_name() method of any object that you pass to it. Notice that you don’t pass any additional arguments tofirst_last(), so you receive back a list of the first names followed by the last names of allMusician objects.
If you want the first names to follow the last names, then you can pass in aTrue value forlast_name_first:
>>>last_first=operator.methodcaller("get_full_name",True)>>>forpersoningroup_members:...print(last_first(person))...Wilson, BrianWilson, CarlWilson, DennisJohnston, BruceMarvin, HankWelch, BruceBennett, BrianThis time, you usemethodcaller() to create a function namedlast_first() that will call the.get_full_name() method of any object passed to it, but it’ll also passTrue to thelast_name_first parameter. Now you receive a list of the last names then the first names of all theMusician objects.
Just like when you’re usingattrgetter() toretrieve attributes, objects passed tomethodcaller() can be either built in or custom. They only need to contain the method that you want to call.
FAQs
You’re now familiar with the purpose and uses of Python’soperator module. You’ve covered a lot of ground, and here, you’ll find a few questions and answers that sum up the most important concepts that you’ve covered in this tutorial.
You can use these questions to check your understanding or to recap and solidify what you’ve just learned. After each question, you’ll find a brief explanation hidden in a collapsible section. Click theShow/Hide toggle to reveal the answer. Time to dive in!
Theoperator module’s functions don’t replace Python’s built-in operators. Instead, you use many of the operator-equivalentoperator functions infunctional programming bypassing them to higher-order functions. This isn’t possible with the built-in operators, because you can’t easily obtain a reference to them.
Theoperator module also provides the higher-orderattrgetter(),itemgetter(), andmethodcaller() functions. You can pass the functions that these three return to many common functions, includingsorted(),max(), andmin().
Unlike with built-in operators,you can serialize anoperator module’s function, which is great because thepickle module is one of the most efficient ways of saving data. The downside is that it can’t usually cope with functions. You can, however, use it withoperator module functions.
Python built-in operators willoutperform both theoperator module’s versions and lambdas, while theoperator functions will outperform lambdas. If you make a single call to a lambda or theoperator module equivalent, then you won’t notice much difference. However, when you make repeated calls, the difference becomes significant. Theoperator module’s functions aren’t replacements for the built-in operators when they’re all you need.
Using theoperator functions canmake your code more readable than the sometimes-confusing lambda function equivalents.
Do you have an interesting example of using theoperator module? If you have a clever use case, why not share with your fellow programmers? Feel free to include it as a comment below.
Get Your Code:Click here to download the free sample code that shows you how to use Python’soperator module.
🐍 Python Tricks 💌
Get a short & sweetPython Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

AboutIan Eyre
Ian is an avid Pythonista and Real Python contributor who loves to learn and teach others.
» More about IanMasterReal-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
MasterReal-World Python Skills
With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students.Get tips for asking good questions andget answers to common questions in our support portal.
Looking for a real-time conversation? Visit theReal Python Community Chat or join the next“Office Hours” Live Q&A Session. Happy Pythoning!
Keep Learning
Related Topics:intermediatedata-structurespython
Keep reading Real Python by creating a free account or signing in:
Already have an account?Sign-In





