{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Lecture 11: Objects and Classes\n", "\n", "CSCI 1360: Foundations for Informatics and Analytics" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Overview and Objectives\n", "\n", "In this lecture, we'll delve into the realm of \"object-oriented programming,\" or OOP. This is a programming paradigm in which concepts and actions are \"packaged\" using the abstraction of *objects*: modeling the system after real-world phenomena, both to aid our own understanding of the program and to enforce a good design paradigm. By the end of this lecture, you should be able to:" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "source": [ " - Understand the core concepts of encapsulation and abstraction that make object-oriented programming so powerful\n", " - Implement your own class hierarchy, using inheritance to avoid redundant code\n", " - Explain how Python's OOP mechanism differs from that in other languages, such as Java" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Part 1: Object-oriented Programming" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Poll: how many people have programmed in Java or C++?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "How many have heard of object-oriented programming?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Up until now (and after this lecture), we've stuck mainly with *procedural* programming: focusing on the actions." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Object-oriented programming, by contrast, focuses on the *objects*.\n", "\n", "![oop](http://www.alphansotech.com/wp-content/uploads/2015/11/object-oriented-concept-13-728-1.jpg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Sort of a \"verbs\" (procedural programming) versus \"nouns\" (object-oriented programming) thing." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Objects versus Classes" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Main idea: you design **objects**, usually modeled after real-world constructs, that interact with each other." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "These designs are called **classes**. Think of them as a blueprint that detail out the various properties and capabilities of your object." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "![class](https://i.ytimg.com/vi/NUl8lcbeN2Y/maxresdefault.jpg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "From the designs (classes), you can create an **object** by **instantiating** the class, or creating an **instance** of the class." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "If the class is the blueprint, the object is the physical manifestation from the blueprint." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "A car can have several properties--steering column, fuel injector, brake pads, airbags--but an *instantiation* of car would be a 2015 Honda Accord. Another instantiation would be a 2016 Tesla Model S. These are *instances* of a car." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "In some [abstract] sense, these both derive from a common blueprint of a car, but their specific details differ. This is precisely how to think of the difference between **classes** and **objects**." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Part 2: Objects in Python" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Every object in Python has certain things in common." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ " - **Methods**: Remember when we covered the difference between functions and methods? This is where that difference comes into play. Methods are the way the object interacts with the outside world. They're functions, but they're attached directly to object instances." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ " - **Constructors**: These are *specialized* methods that deal specifically with how an object instance is created. Every single object has a constructor, whether you explicitly write one or not." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ " - **Attributes**: These are the physical properties of the object; maybe they change, maybe they don't. For a car, this could be color, make, model, or name. These are the things that distinguish one *instance* of the class from another." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ " - **Inheritance**: This is where the power of object-oriented programming really comes into play. Quite often, our understanding of physical objects in the world is hierarchical: there are cars; then there are race cars, sedans, and SUVs; then there are gas-powered sedans, hybrid sedans, and electric sedans; then there are 2015 Honda Accords and 2016 Honda Accords. Wouldn't it be great if our class design reflected this hierarchy?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Defining Classes" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's start with the first step of designing a class: its actual definition. We'll stick with the car example." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Car():\n", " \"\"\" A simple representation of a car. \"\"\"\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "To define a new class, you need a `class` keyword, followed by the name (in this case, `Car`). The parentheses are important, but for now we'll leave them empty. Like loops and functions and conditionals, everything that belongs to the class--variables, methods, etc--are indented underneath." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "We can then *instantiate* this class using the following:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Car object at 0x106e41978>\n" ] } ], "source": [ "my_car = Car()\n", "print(my_car)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Now `my_car` holds an instance of the `Car` class! It doesn't do much, but it's a valid object." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Constructors" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The first step in making an interesting class is by creating a *constructor*. It's a special kind of function that provides a customized recipe for how an instance of that class is built." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "It takes a special form, too:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Car():\n", " \n", " def __init__(self):\n", " print(\"This is the constructor!\")" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This is the constructor!\n" ] } ], "source": [ "my_car = Car()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Let's look at this method in more detail." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ " def __init__(self):\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ " - The `def` is normal: the Python keyword we use to identify a function definition." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ " - `__init__` is the name of our method. It's an interesting name for sure, and turns out this is a *very specific name* Python is looking for: whenever you instantiate an object, this is the method that's run. If you don't explicitly write a constructor, Python implicitly supplies a \"default\" one (where basically nothing really happens)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ " - The method argument is strange; what is this mysterious `self`, and why--if an argument is required--didn't we supply one when we executed `my_car = Car()`?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### A note on `self`" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This is how the object refers to *itself* from inside the object. We'll see this in greater detail once we get to attributes." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "*Every method in a class* must have `self` as the first argument. Even though you don't actually *supply* this argument yourself when you call the method, it still has to be in the function definition." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Otherwise, you'll get some weird error messages:\n", "\n", "![self](http://cs.uga.edu/~squinn/courses/fa16/csci1360/assets/self_error.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Attributes" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Attributes are variables contained inside a class, and which take certain values when the class is instantiated." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The most common practice is to define these attributes within the constructor of the class." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Car():\n", " \n", " def __init__(self, year, make, model):\n", " \n", " # All three of these are class attributes.\n", " self.year = year\n", " self.make = make\n", " self.model = model" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2015\n" ] } ], "source": [ "my_car = Car(2015, \"Honda\", \"Accord\") # Again, note that we don't specify something for \"self\" here.\n", "print(my_car.year)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "These attributes are accessible from anywhere *inside* the class, but direct access to them from *outside* (as did in the `print(my_car.year)` statement) is heavily frowned upon." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Instead, good object-oriented design stipulates that these attributes be treated as *private* variables to the class." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "To be modified or otherwise used, the classes should have public methods that expose very specific avenues for interaction with the class attributes." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This is the concept of *encapsulation*: restricting direct access to attributes, and instead encouraging the use of class methods to interact with the attributes in very specific ways.\n", "\n", "![encapsulation](http://cis1.towson.edu/~cssecinj/wp-content/uploads//encapsulation.JPG)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Methods" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Methods are functions attached to the class, but which are accessible from outside the class, and define the ways in which the instances of the class can interact with the outside world." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Whereas classes are usually nouns, the methods are typically the verbs. For example, what would a `Car` class do?" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Car():\n", " \n", " def __init__(self, year, make, model):\n", " self.year = year\n", " self.make = make\n", " self.model = model\n", " self.mileage = 0\n", " \n", " def drive(self, mileage = 0):\n", " if mileage == 0:\n", " print(\"Driving!\")\n", " else:\n", " self.mileage += mileage\n", " print(\"Driven {} miles total.\".format(self.mileage))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Driven 100 miles total.\n", "Driving!\n", "Driven 150 miles total.\n" ] } ], "source": [ "my_car = Car(2016, \"Tesla\", \"Model S\")\n", "my_car.drive(100)\n", "my_car.drive()\n", "my_car.drive(50)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Classes can have as many methods as you want, named whatever you'd like (though usually named so they reflect their purpose)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Methods are what are ultimately allowed to edit the class attributes (the `self.` variables), as per the concept of encapsulation. For example, the `self.mileage` attribute in the previous example that stores the total mileage driven by that instance." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Like the constructor, all the class methods must have `self` as the first argument in their headers, even though you don't explicitly supply it when you call the methods." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Inheritance" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Inheritance is easily the most complicated aspect of object-oriented programming, but is most certainly where OOP derives its power for modular design." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "When considering cars, certainly most are very similar and can be modeled effectively with one class, but eventually there are enough differences to necessitate the creation of a separate class. For example, a class for gas-powered cars and one for EVs." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "But considering how much overlap they still share, it'd be highly redundant to make wholly separate classes for both." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class GasCar():\n", " def __init__(self, make, model, year, tank_size):\n", " # Set up attributes.\n", " pass\n", " \n", " def drive(self, mileage = 0):\n", " # Driving functionality.\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class ElectricCar():\n", " def __init__(self, make, model, year, battery_cycles):\n", " # Set up attributes.\n", " pass\n", " \n", " def drive(self, mileage = 0):\n", " # Driving functionality, probably identical to GasCar.\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Enter *inheritance*: the ability to create *subclasses* of existing classes that retain all the functionality of the parent, while requiring the implementation only of the things that differentiate the child from the parent." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Car(): # Parent class.\n", " def __init__(self, make, model, year):\n", " self.make = make\n", " self.model = model\n", " self.year = year\n", " self.mileage = 0\n", " \n", " def drive(self, mileage = 0):\n", " self.mileage += mileage\n", " print(\"Driven {} miles.\".format(self.mileage))" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class EV(Car): # Child class--explicitly mentions \"Car\" as the parent!\n", " def __init__(self, make, model, year, charge_range):\n", " Car.__init__(self, make, model, year)\n", " self.charge_range = charge_range\n", " \n", " def charge_remaining(self):\n", " if self.mileage < self.charge_range:\n", " print(\"Still {} miles left.\".format(self.charge_range - self.mileage))\n", " else:\n", " print(\"Battery depleted! Find a SuperCharger station.\")" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Driven 100 miles.\n", "Still 150 miles left.\n", "Driven 250 miles.\n", "Battery depleted! Find a SuperCharger station.\n" ] } ], "source": [ "tesla = EV(2016, \"Tesla\", \"Model S\", 250)\n", "tesla.drive(100)\n", "tesla.charge_remaining()\n", "tesla.drive(150)\n", "tesla.charge_remaining()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Hopefully you noticed--we could call `tesla.drive()` and it worked as it was defined in the *parent* `Car` class, without us having to write it again!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This is the power of inheritance: every child class *inherits* all the functionality of the parent class." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**With ONE exception**: if you *override* a parent attribute or method in the child class, then that takes precedence." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Hybrid(Car):\n", " \n", " def drive(self, mileage, mpg):\n", " self.mileage += mileage\n", " print(\"Driven {} miles at {:.1f} MPG.\".format(self.mileage, mpg))" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Driven 100 miles at 35.5 MPG.\n" ] } ], "source": [ "hybrid = Hybrid(2015, \"Toyota\", \"Prius\")\n", "hybrid.drive(100, 35.5)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Using inheritance, you can build an entire hierarchy of classes and subclasses, inheriting functionality where needed and overriding it where necessary." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "![hierarchy](http://rfhs8012.fh-regensburg.de/~saj39122/jfroehl/diplom/fig/abs03.gif)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This illustrates the concept of *polymorphism* (meaning \"many forms\"): all cars are vehicles; therefore, any functions a vehicle has, a car will also have." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "All transporters are vehicles--and also cars--and have all the associated functionality defined in those classes." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "However, it does NOT work in reverse: not all vehicles are motorcycles! Thus, as you move down the hierarchy, the objects become more *specialized*." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Multiple Inheritance" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Just a quick note on this, for all the Java converts--" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Python **does support** multiple inheritance, meaning a child class can directly inherit from *multiple* parent classes." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class DerivedClassName(EV, Hybrid):\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This can get very complicated (and is why Java restricts \"multiple inheritance\" to interfaces only) in terms of what method and attribute definitions takes precedence when found in multiple parent classes." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "As such, we won't explore this very much if at all in this class." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Review Questions\n", "\n", "Some questions to discuss and consider:" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "1: Buzzword bingo: define *encapsulation*, *inheritance*, *polymorphism*, *instantiation*, and the difference between *objects* and *classes*.\n", "\n", "2: For those who are Java converts, you may recall `public` and `private` methods and variables. Python makes no such distinction; everything is intrinsically public. In this case, why still use methods to interact with classes, instead of directly accessing the class attributes?\n", "\n", "3: What is the difference between method overriding and [method overloading](https://en.wikipedia.org/wiki/Function_overloading)? Does Python support one, both, or neither?\n", "\n", "4: Class variable scope exists when working with objects in Python. If I define a variable `x` outside a class, define `x` again inside the class method, and refer to `x` after the class definition, which `x` is accessed? If `x` is also an attribute of the class, how do you access it from outside the class? How can you access the `x` defined outside the class from inside a class method?\n", "\n", "5: Design a class hierarchy for different kinds of drinks. Include as much detail as you can. Where are attributes and methods inherited, where are they overridden, and where are new attributes and methods defined?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Course Administrivia" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "How is A4 going?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Volunteers for tomorrow's flipped lecture?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Review session #2 on Thursday! Come with questions!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Additional Resources\n", "\n", " 1. Matthes, Eric. *Python Crash Course*. 2016. ISBN-13: 978-1593276034\n", " 2. Python Classes documentation: https://docs.python.org/3/tutorial/classes.html" ] } ], "metadata": { "anaconda-cloud": {}, "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python [default]", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 0 }