skip to navigation
skip to content

Planet Python

Last update: February 16, 2019 10:48 AM UTC

February 16, 2019


Vasudev Ram

pprint.isrecursive: Check if object requires recursive representation



- By Vasudev Ram - Online Python training / SQL training / Linux training


Tree image attribution

Hi, readers,

I was using the pprint module to pretty-print some Python data structures in a program I was writing. Then saw that it has a function called isrecursive.

The docstring for pprint.isrecursive says:
>>> print pprint.isrecursive.__doc__
Determine if object requires a recursive representation.
Here is a Python 3 shell session that shows what the isrecursive function does, with a list:
>>> import pprint
>>> print(pprint.pprint.__doc__)
Pretty-print a Python object to a stream [default is sys.stdout].
>>> a = []
>>> a
[]
>>> pprint.isrecursive(a)
False
>>>
>>> a.append(a)
>>> a
[[...]]
>>>
>>> pprint.isrecursive(a)
True
How about for a dict?
>>> b = {}
>>> pprint.isrecursive(b)
False
>>>
>>> b[1] = b
>>> b
{1: {...}}
>>> id(b) == id(b[1])
True
>>> pprint.isrecursive(b)
True
How about if an object is recursive, but not directly, like in the above two examples? Instead, it is recursive via a chain of objects:
>>> c = []
>>> d = []
>>> e = []
>>> c.append(d)
>>> d.append(e)
>>> c
[[[]]]
>>> pprint.isrecursive(c)
False
>>> e.append(c)
>>> c
[[[[...]]]]
>>> pprint.isrecursive(c)
True
So we can see that isrecursive is useful to detect some recursive Python object structures.
Interestingly, if I compare c with c[0] (after making c a recursive structure), I get:
>>> c == c[0]
Traceback (most recent call last):
File "", line 1, in
RecursionError: maximum recursion depth exceeded in comparison
>>>
In Python 2, I get:
RuntimeError: maximum recursion depth exceeded in cmp

Also, relevant XKCD-clone.

The image at the top of the post is of a tree created in the LOGO programming language using recursion.

- Enjoy.

- Vasudev Ram - Online Python training and consulting

I conduct online courses on Python programming, Unix / Linux commands and shell scripting and SQL programming and database design, with course material and personal coaching sessions.

The course details and testimonials are here.

Contact me for details of course content, terms and schedule.

Try FreshBooks: Create and send professional looking invoices in less than 30 seconds.

Getting a new web site or blog, and want to help preserve the environment at the same time? Check out GreenGeeks.com web hosting.

Sell your digital products via DPD: Digital Publishing for Ebooks and Downloads.

Learning Linux? Hit the ground running with my vi quickstart tutorial. I wrote it at the request of two Windows system administrator friends who were given additional charge of some Unix systems. They later told me that it helped them to quickly start using vi to edit text files on Unix. Of course, vi/vim is one of the most ubiquitous text editors around, and works on most other common operating systems and on some uncommon ones too, so the knowledge of how to use it will carry over to those systems too.

Check out WP Engine, powerful WordPress hosting.

Get a fast web site with A2 Hosting.

Creating online products for sale? Check out ConvertKit, email marketing for online creators.

Teachable: feature-packed course creation platform, with unlimited video, courses and students.

Posts about: Python * DLang * xtopdf

My ActiveState Code recipes

Follow me on:



February 16, 2019 01:47 AM UTC

February 15, 2019


PyCharm

PyCharm 2019.1 EAP 4

Our fourth Early Access Program (EAP) version for PyCharm 2019.1 is now available on our website.

New in This Version

Parallel and concurrent testing with pytest

695a0367-eed2-45e1-96ed-46c70fc6067e

PyCharm makes it easy to run tests quickly using multiprocessing (parallelism) and multithreading (concurrency). All you need to do in order to run your pytest tests in parallel is to install the pytest-xdist plugin as a normal python package using the PyCharm’s package manager, specify pytest as the project testing framework, create a pytest run/debug configuration where you can specify the number of CPUs to run the tests on, and you’re good to go.

Read more about setting up and running pytest tests in parallel in our help

Further Improvements

Interested?

Download this EAP from our website. Alternatively, you can use the JetBrains Toolbox App to stay up to date throughout the entire EAP.

With PyCharm 2019.1 we’re moving to a new runtime environment: this EAP build already bundles the brand new JetBrains Runtime Environment (a customized version of JRE 11). Unfortunately, since this build uses the brand-new platform, the patch-update from previous versions is not available this time. Please use the full installation method instead.

If you’re on Ubuntu 16.04 or later, you can use snap to get PyCharm EAP, and stay up to date. You can find the installation instructions on our website.

PyCharm 2019.1 is in development during the EAP phase, therefore not all new features are already available. More features will be added in the coming weeks. As PyCharm 2019.1 is pre-release software, it is not as stable as the release versions. Furthermore, we may decide to change and/or drop certain features as the EAP progresses.

All EAP versions will ship with a built-in EAP license, which means that these versions are free to use for 30 days after the day that they are built. As EAPs are released weekly, you’ll be able to use PyCharm Professional Edition EAP for free for the duration of the EAP program, as long as you upgrade at least once every 30 days.

February 15, 2019 10:54 AM UTC

February 14, 2019


Continuum Analytics Blog

Intake released on Conda-Forge

Intake is a package for cataloging, finding and loading your data. It has been developed recently by Anaconda, Inc., and continues to gain new features. To read general information about Intake and how to use…

The post Intake released on Conda-Forge appeared first on Anaconda.

February 14, 2019 09:26 PM UTC


PyCon

Eighth Annual PyLadies Auction at PyCon 2019

Photo Courtesy of Mike Pirnat
PyLadies is an international mentorship community for women that use Python. Started with the help of a grant provided by The Python Software Foundation (PSF)  in 2011, PyLadies has continued to bring women into the Python community through a variety of methods, including hosting events in local PyLadies chapters and offering grant opportunities to attend PyCon. Their mission is to promote, educate and advance a diverse Python community through outreach, education, conferences, events, and social gatherings.

The Python Software Foundation (PSF) is proud to announce the Eighth Annual PyCon Charity Auction for 2019.

PyCon 2018’s auction was a huge success raising over $30K! More than 40 items from sponsors and fellow attendees were auctioned. Attendance was overwhelming and, rather than turn more people away for 2019, we have decided to increase capacity this year!

The PSF subsidizes this event each year by covering the cost of the venue, food, and beverages. In addition, the PSF adds a substantial donation to the event after everything is auctioned off.  

If you are interested in donating and item for the auction send the information to pycon-auction@python.org

Thinking about becoming a Sponsor?

We are hoping to find one or two companies who love what the PyLadies are doing and are willing to sponsor this wonderful event. It’s a great opportunity to let our community know that you support them. Sponsorships start at $7500. More information can be found here, or you can contact pycon-sponsors@python.org.


Photo Courtesy of Mike Pirnat
PyLadies also aims to provide a friendly support network for women and a bridge to the larger Python world. Anyone with an interest in Python is encouraged to participate! Check out local meetups here.

The auction is a fun and entertaining way to support the PyLadies community.

We hope to see you in Cleveland!









February 14, 2019 06:43 PM UTC


Made With Mu

A GPIOZero Theramin for Valentine’s Day

Thanks to Ben Nuttall, one of the maintainers of the wonderful, GPIOZero library, love is in the air.

Why not serenade your Valentine with Mu, a distance sensor, speaker, Raspberry Pi and some nifty Python code which uses GPIOZero to turn the hardware into a romantic Theramin..?

It’s only four lines of code, thus showing how easy it is to make cool hardware hacks with Mu, Python and GPIOZero:

from gpiozero import TonalBuzzer, DistanceSensor

buzzer = TonalBuzzer(20)
ds = DistanceSensor(14, 26)

buzzer.source = ds

The end result is full of love (for GPIO related shenanigans):


February 14, 2019 09:00 AM UTC


Talk Python to Me

#199 Automate all the things with Python at Zapier

Do your applications call a lot of APIs? Maybe you have a bunch of microservices driving your app. You probably don't have the crazy combinatorial explosion that Zapier does for connecting APIs! They have millions of users automating things with 1,000s of APIs. It's pretty crazy. And they are doing it all with Python. Join me and Bryan Helmig, the CTO and co-founder of Zapier as we discuss how they pull this off with Python.

February 14, 2019 08:00 AM UTC


Python Bytes

#117 Is this the end of Python virtual environments?

February 14, 2019 08:00 AM UTC


Codementor

What You Don't Know About Python Variables

The first time you get introduced to Python’s variable, it is usually defined as “parts of your computer’s memory where you store some information.” Some define it as a “storage placeholder for texts and numbers." Python variables is more than he above definition.

February 14, 2019 12:14 AM UTC

February 13, 2019


Dataquest

How to Learn Python for Data Science In 5 Steps

Why Learn Python For Data Science?

How to Learn Python for Data Science In 5 Steps

Before we explore how to learn Python for data science, we should briefly answer why you should learn Python in the first place.

In short, understanding Python is one of the valuable skills needed for a data science career.

Though it hasn’t always been, Python is the programming language of choice for data science. Here’s a brief history:

  • In 2016, it overtook R on Kaggle, the premier platform for data science competitions.
  • In 2017, it overtook R on KDNuggets’s annual poll of data scientists’ most used tools.
  • In 2018, 66% of data scientists reported using Python daily, making it the number one tool for analytics professionals.

Data science experts expect this trend to continue with increasing development in the Python ecosystem. And while your journey to learn Python programming may be just beginning, it’s nice to know that employment opportunities are abundant (and growing) as well.

According to Indeed, the average salary for a Data Scientist is $127,918.

The good news? That number is only expected to increase. The experts at IBM predicted a 28% increase in demand for data scientists by the year 2020.

So, the future is bright for data science, and Python is just one piece of the proverbial pie. Fortunately, learning Python and other programming fundamentals is as attainable as ever. We’ll show you how in five simple steps.

But remember – just because the steps are simple doesn’t mean you won’t have to put in the work. If you apply yourself and dedicate meaningful time to learning Python, you have the potential to not only pick up a new skill, but potentially bring your career to a new level.

How to Learn Python for Data Science

How to Learn Python for Data Science In 5 Steps

First, you’ll want to find the right course to help you learn Python programming. Dataquest’s courses are specifically designed for you to learn Python for data science at your own pace.

In addition to learning Python in a course setting, your journey to becoming a data scientist should also include soft skills. Plus, there are some complimentary technical skills we recommend you learn along the way.

Step 1: Learn Python Fundamentals

Everyone starts somewhere. This first step is where you’ll learn Python programming basics. You’ll also want an introduction to data science.

One of the important tools you should start using early in your journey is Jupyter Notebook, which comes prepackaged with Python libraries to help you learn these two things.

Kickstart your learning by: Joining a community

By joining a community, you’ll put yourself around like-minded people and increase your opportunities for employment. According to the Society for Human Resource Management, employee referrals account for 30% of all hires.

Create a Kaggle account, join a local Meetup group, and participate in Dataquest’s members-only Slack discussions with current students and alums.

Related skills: Try the Command Line Interface

The Command Line Interface (CLI) lets you run scripts more quickly, allowing you to test programs faster and work with more data.

Step 2: Practice Mini Python Projects

We truly believe in hands-on learning. You may be surprised by how soon you’ll be ready to build small Python projects.

Try programming things like calculators for an online game, or a program that fetches the weather from Google in your city. Building mini projects like these will help you learn Python. programming projects like these are standard for all languages, and a great way to solidify your understanding of the basics.

You should start to build your experience with APIs and begin web scraping. Beyond helping you learn Python programming, web scraping will be useful for you in gathering data later.

Kickstart your learning by: Reading

Enhance your coursework and find answers to the Python programming challenges you encounter. Read guidebooks, blog posts, and even other people’s open source code to learn Python and data science best practices - and get new ideas.

Automate The Boring Stuff With Python by Al Sweigart is an excellent and entertaining resource.

Related skills: Work with databases using SQL

SQL is used to talk to databases to alter, edit, and reorganize information. SQL is a staple in the data science community, as 40% of data scientists report consistently using it.*

Step 3: Learn Python Data Science Libraries

Unlike some other programming languages, in Python, there is generally a best way of doing something. The three best and most important Python libraries for data science are NumPy, Pandas, and Matplotlib.

NumPy and Pandas are great for exploring and playing with data. Matplotlib is a data visualization library that makes graphs like you’d find in Excel or Google Sheets.

Kickstart your learning by: Asking questions

You don’t know what you don’t know!

Python has a rich community of experts who are eager to help you learn Python. Resources like Quora, Stack Overflow, and Dataquest’s Slack are full of people excited to share their knowledge and help you learn Python programming. We also have an FAQ for each mission to help with questions you encounter throughout your programming courses with Dataquest.

Related skills: Use Git for version control

Git is a popular tool that helps you keep track of changes made to your code, which makes it much easier to correct mistakes, experiment, and collaborate with others.

Step 4: Build a Data Science Portfolio as you Learn Python

For aspiring data scientists, a portfolio is a must.

These projects should include several different datasets and should leave readers with interesting insights that you’ve gleaned. Your portfolio doesn’t need a particular theme; find datasets that interest you, then come up with a way to put them together.

Displaying projects like these gives fellow data scientists something to collaborate on and shows future employers that you’ve truly taken the time to learn Python and other important programming skills.

One of the nice things about data science is that your portfolio doubles as a resume while highlighting the skills you’ve learned, like Python programming.

Kickstart your learning by: Communicating, collaborating, and focusing on technical competence

During this time, you’ll want to make sure you’re cultivating those soft skills required to work with others, making sure you really understand the inner workings of the tools you’re using.

Related skills: Learn beginner and intermediate statistics

While learning Python for data science, you’ll also want to get a solid background in statistics. Understanding statistics will give you the mindset you need to focus on the right things, so you’ll find valuable insights (and real solutions) rather than just executing code.

Step 5: Apply Advanced Data Science Techniques

Finally, aim to sharpen your skills. Your data science journey will be full of constant learning, but there are advanced courses you can complete to ensure you’ve covered all the bases.

You’ll want to be comfortable with regression, classification, and k-means clustering models. You can also step into machine learning - bootstrapping models and creating neural networks using scikit-learn.

At this point, programming projects can include creating models using live data feeds. Machine learning models of this kind adjust their predictions over time.

Remember to: Keep learning!

Data science is an ever-growing field that spans numerous industries.

At the rate that demand is increasing, there are exponential opportunities to learn. Continue reading, collaborating, and conversing with others, and you’re sure to maintain interest and a competitive edge over time.

How Long Will It Take To Learn Python?

After reading these steps, the most common question we have people ask us is: “How long does all this take?”

There are a lot of estimates for the time it takes to learn Python. For data science specifically, estimates a range from 3 months to a year of consistent practice.

We’ve watched people move through our courses at lightning speed and others who have taken it much slower.

Really, it all depends on your desired timeline, free time that you can dedicate to learn Python programming and the pace at which you learn.

Dataquest’s courses are created for you to go at your own speed. Each path is full of missions, hands-on learning and opportunities to ask questions so that you get can an in-depth mastery of data science fundamentals.

Get started for free. Learn Python with our Data Scientist path and start mastering a new skill today.

Resources and studies cited:

February 13, 2019 03:57 PM UTC


Kushal Das

Tracking my phone's silent connections

My phone has more friends than me. It talks to more peers (computers) than the number of human beings I talk on an average. In this age of smartphones and mobile apps for A-Z things, we are dependent on these technologies. However, at the same time, we don’t know much of what is going on in the computers equipped with powerful cameras, GPS device, microphone we are carrying all the time. All these apps are talking to their respective servers (or can we call them masters?), but, there is no easy way to track them.

These questions bothered me for a long time: I wanted to see the servers my phone is connecting to, and I want to block those connections as I wish. However, I never managed to work on this. A few weeks ago, I finally sat down to start working to build up a system by reusing already available open source projects and tools to create the system, which will allow me to track what my phone is doing. Maybe not in full details, but, at least shed some light on the network traffic from the phone.

Initial trial

I tried to create a wifi hotspot at home using a Raspberry Pi and then started capturing all the packets from the device using standard tools (dumpcap) and later reading through the logs using Wireshark. This procedure meant that I could only capture when I am connected to the network at home. What about when I am not at home?

Next round

This time I took a bit different approach. I chose algo to create a VPN server. Using WireGuard, it became straightforward to connect my iPhone to the VPN. This process also allows capturing all the traffic from the phone very easily on the VPN server. A few days in the experiment, Kashmir started posting her experiment named Life Without the Tech Giants, where she started blocking all the services from 5 big technology companies. With her help, I contacted Dhruv Mehrotra, who is a technologist behind the story. After talking to him, I felt that I am going in the right direction. He already posted details on how they did the blocking, and you can try that at home :)

Looking at the data after 1 week

After capturing the data for the first week, I moved the captured pcap files into my computer. Wrote some Python code to put the data into a SQLite database, enabling me to query the data much faster.

Domain Name System (DNS) data

The Domain Name System (DNS) is a decentralized system which helps to translate the human memory safe domain names (like kushaldas.in) into Internet Protocol (IP) addresses (like 192.168.1.1 ). Computers talk to each other using these IP addresses, we, don’t have to worry to remember so many names. When the developers develop their applications for the phone, they generally use those domain names to specify where the app should connect.

If I plot all the different domains (including any subdomain) which got queried at least 10 times in a week, we see the following graph.

The first thing to notice is how the phone is trying to find servers from Apple, which makes sense as this is an iPhone. I use the mobile Twitter app a lot, so we also see many queries related to Twitter. Lookout is a special mention there, it was suggested to me by my friends who understand these technologies and security better than me. The 3rd position is taken by Google, though sometimes I watch Youtube videos, but, the phone queried for many other Google domains.

There are also many queries to Akamai CDN service, and I could not find any easy way to identify those hosts, the same with Amazon AWS related hosts. If you know any better way, please drop me a note.

You can see a lot of data analytics related companies were also queried. dev.appboy.com is a major one, and thankfully algo already blocked that domain in the DNS level. I don’t know which app is trying to connect to which all servers, I found about a few of the apps in my phone by searching about the client list of the above-mentioned analytics companies. Next, in coming months, I will start blocking those hosts/domains one by one and see which all apps stop working.

Looking at data flow

The number of DNS queries is an easy start, but, next I wanted to learn more about the actual servers my phone is talking to. The paranoid part inside of me was pushing for discovering these servers.

If we put all of the major companies the phone is talking to, we get the following graph.

Apple is leading the chart by taking 44% of all the connections, and the number is 495225 times. Twitter is in the second place, and Edgecastcdn is in the third. My phone talked to Google servers 67344 number of times, which is like 7 times less than the number of times Apple itself.

In the next graph, I removed the big players (including Google and Amazon). Then, I can see that analytics companies like nflxso.net and mparticle.com have 31% of the connections, which is a lot. Most probably I will start with blocking these two first. The 3 other CDN companies, Akamai, Cloudfront, and Cloudflare has 8%, 7%, and 6% respectively. Do I know what all things are these companies tracking? Nope, and that is scary enough that one of my friend commented “It makes me think about throwing my phone in the garbage.”

What about encrypted vs unencrypted traffic? What all protocols are being used? I tried to find the answer for the first question, and the answer looks like the following graph. Maybe the number will come down if I try to refine the query and add other parameters, that is a future task.

What next?

As I said earlier, I am working on creating a set of tools, which then can be deployed on the VPN server, that will provide a user-friendly way to monitor, and block/unblock traffic from their phone. The major part of the work is to make sure that the whole thing is easy to deploy, and can be used by someone with less technical knowledge.

How can you help?

The biggest thing we need is the knowledge of “How to analyze the data we are capturing?”. It is one thing to make reports for personal user, but, trying to help others is an entirely different game altogether. We will, of course, need all sorts of contributions to the project. Before anything else, we will have to join the random code we have, into a proper project structure. Keep following this blog for more updates and details about the project.

Note to self

Do not try to read data after midnight, or else I will again think a local address as some random dynamic address in Bangkok and freak out (thank you reverse-dns).

February 13, 2019 02:47 AM UTC

February 12, 2019


The No Title® Tech Blog

Why and how I have just redesigned my (other) website

Going through a moment of change in my life, I have decided to redesign my other website, using Pelican and other open source tools. The older version was starting to look a bit aged, especially on mobile devices, so it seemed like a good idea to start a complete makeover. As they use to say, new year… new website.

February 12, 2019 11:15 PM UTC


Codementor

Testing isn't everything, but it's important

A talk that I've been thinking about for the last little while is one by Gary Bernhardt called Ideology (https://www.destroyallsoftware.com/talks/ideology). I highly recommend that you go watch it...

February 12, 2019 09:32 PM UTC


Real Python

Supercharge Your Classes With Python super()

While Python isn’t purely an object-oriented language, it’s flexible enough and powerful enough to allow you to build your applications using the object-oriented paradigm. One of the ways in which Python achieves this is by supporting inheritance, which it does with super().

In this tutorial, you’ll learn about the following:

Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you'll need to take your Python skills to the next level.

An Overview of Python’s super() Function

If you have experience with object-oriented languages, you may already be familiar with the functionality of super().

If not, don’t fear! While the official documentation is fairly technical, at a high level super() gives you access to methods in a superclass from the subclass that inherits from it.

super() alone returns a temporary object of the superclass that then allows you to call that superclass’s methods.

Why would you want to do any of this? While the possibilities are limited by your imagination, a common use case is building classes that extend the functionality of previously built classes.

Calling the previously built methods with super() saves you from needing to rewrite those methods in your subclass, and allows you to swap out superclasses with minimal code changes.

super() in Single Inheritance

If you’re unfamiliar with object-oriented programming concepts, inheritance might be an unfamiliar term. Inheritance is a concept in object-oriented programming in which a class derives (or inherits) attributes and behaviors from another class without needing to implement them again.

For me at least, it’s easier to understand these concepts when looking at code, so let’s write classes describing some shapes:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length * self.length

    def perimeter(self):
        return 4 * self.length

Here, there are two similar classes: Rectangle and Square.

You can use them as below:

>>>
>>> square = Square(4)
>>> square.area()
16
>>> rectangle = Rectangle(2,4)
>>> rectangle.area()
8

In this example, you have two shapes that are related to each other: a square is a special kind of rectangle. The code, however, doesn’t reflect that relationship and thus has code that is essentially repeated.

By using inheritance, you can reduce the amount of code you write while simultaneously reflecting the real-world relationship between rectangles and squares:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

# Here we declare that the Square class inherits from the Rectangle class
class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

Here, you’ve used super() to call the __init__() of the Rectangle class, allowing you to use it in the Square class without repeating code. Below, the core functionality remains after making changes:

>>>
>>> square = Square(4)
>>> square.area()
16

In this example, Rectangle is the superclass, and Square is the subclass.

Because the Square and Rectangle .__init__() methods are so similar, you can simply call the superclass’s .__init__() method (Rectangle.__init__()) from that of Square by using super(). This sets the .length and .width attributes even though you just had to supply a single length parameter to the Square constructor.

When you run this, even though your Square class doesn’t explicitly implement it, the call to .area() will use the .area() method in the superclass and print 16. The Square class inherited .area() from the Rectangle class.

Note: To learn more about inheritance and object-oriented concepts in Python, be sure to check out Object-Oriented Programming (OOP) in Python 3.

What Can super() Do for You?

So what can super() do for you in single inheritance?

Like in other object-oriented languages, it allows you to call methods of the superclass in your subclass. The primary use case of this is to extend the functionality of the inherited method.

In the example below, you will create a class Cube that inherits from Square and extends the functionality of .area() (inherited from the Rectangle class through Square) to calculate the surface area and volume of a Cube instance:

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

class Cube(Square):
    def surface_area(self):
        face_area = super().area()
        return face_area * 6

    def volume(self):
        face_area = super().area()
        return face_area * self.length

Now that you’ve built the classes, let’s look at the surface area and volume of a cube with a side length of 3:

>>>
>>> cube = Cube(3)
>>> cube.surface_area()
54
>>> cube.volume()
27

Caution: Note that in our example above, super() alone won’t make the method calls for you: you have to call the method on the proxy object itself.

Here you have implemented two methods for the Cube class: .surface_area() and .volume(). Both of these calculations rely on calculating the area of a single face, so rather than reimplementing the area calculation, you use super() to extend the area calculation.

Also notice that the Cube class definition does not have an .__init__(). Because Cube inherits from Square and .__init__() doesn’t really do anything differently for Cube than it already does for Square, you can skip defining it, and the .__init__() of the superclass (Square) will be called automatically.

super() returns a delegate object to a parent class, so you call the method you want directly on it: super().area().

Not only does this save us from having to rewrite the area calculations, but it also allows us to change the internal .area() logic in a single location. This is especially in handy when you have a number of subclasses inheriting from one superclass.

A super() Deep Dive

Before heading into multiple inheritance, let’s take a quick detour into the mechanics of super().

While the examples above (and below) call super() without any parameters, super() can also take two parameters: the first is the subclass, and the second parameter is an object that is an instance of that subclass.

First, let’s see two examples showing what manipulating the first variable can do, using the classes already shown:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

class Square(Rectangle):
    def __init__(self, length):
        super(Square, self).__init__(self, length, length)

In Python 3, the super(Square, self) call is equivalent to the parameterless super() call. The first parameter refers to the subclass Square, while the second parameter refers to a Square object which, in this case, is self. You can call super() with other classes as well:

class Cube(Square):
    def surface_area(self):
        face_area = super(Square, self).area()
        return face_area * 6

    def volume(self):
        face_area = super(Square, self).area()
        return face_area * self.length

In this example, you are setting Square as the subclass argument to super(), instead of Cube. This causes super() to start searching for a matching method (in this case, .area()) at one level above Square in the instance hierarchy, in this case Rectangle.

In this specific example, the behavior doesn’t change. But imagine that Square also implemented an .area() function that you wanted to make sure Cube did not use. Calling super() in this way allows you to do that.

Caution: While we are doing a lot of fiddling with the parameters to super() in order to explore how it works under the hood, I’d caution against doing this regularly.

The parameterless call to super() is recommended and sufficient for most use cases, and needing to change the search hierarchy regularly could be indicative of a larger design issue.

What about the second parameter? Remember, this is an object that is an instance of the class used as the first parameter. For an example, isinstance(Cube, Square) must return True.

By including an instantiated object, super() returns a bound method: a method that is bound to the object, which gives the method the object’s context such as any instance attributes. If this parameter is not included, the method returned is just a function, unassociated with an object’s context.

For more information about bound methods, unbound methods, and functions, read the Python documentation on its descriptor system.

Note: Technically, super() doesn’t return a method. It returns a proxy object. This is an object that delegates calls to the correct class methods without making an additional object in order to do so.

super() in Multiple Inheritance

Now that you’ve worked through an overview and some examples of super() and single inheritance, you will be introduced to an overview and some examples that will demonstrate how multiple inheritance works and how super() enables that functionality.

Multiple Inheritance Overview

There is another use case in which super() really shines, and this one isn’t as common as the single inheritance scenario. In addition to single inheritance, Python supports multiple inheritance, in which a subclass can inherit from multiple superclasses that don’t necessarily inherit from each other (also known as sibling classes).

I’m a very visual person, and I find diagrams are incredibly helpful to understand concepts like this. The image below shows a very simple multiple inheritance scenario, where one class inherits from two unrelated (sibling) superclasses:

A diagrammed example of multiple inheritanceA diagrammed example of multiple inheritance (Image: Kyle Stratis)

To better illustrate multiple inheritance in action, here is some code for you to try out, showing how you can build a right pyramid (a pyramid with a square base) out of a Triangle and a Square:

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height

class RightPyramid(Triangle, Square):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 * perimeter * self.slant_height + base_area

Note: The term slant height may be unfamiliar, especially if it’s been a while since you’ve taken a geometry class or worked on any pyramids.

The slant height is the height from the center of the base of an object (like a pyramid) up its face to the peak of that object. You can read more about slant heights at WolframMathWorld.

This example declares a Triangle class and a RightPyramid class that inherits from both Square and Triangle.

You’ll see another .area() method that uses super() just like in single inheritance, with the aim of it reaching the .perimeter() and .area() methods defined all the way up in the Rectangle class.

Note: You may notice that the code above isn’t using any inherited properties from the Triangle class yet. Later examples will fully take advantage of inheritance from both Triangle and Square.

The problem, though, is that both superclasses (Triangle and Square) define a .area(). Take a second and think about what might happen when you call .area() on RightPyramid, and then try calling it like below:

>>>
>> pyramid = RightPyramid(2, 4)
>> pyramid.area()
Traceback (most recent call last):
  File "shapes.py", line 63, in <module>
    print(pyramid.area())
  File "shapes.py", line 47, in area
    base_area = super().area()
  File "shapes.py", line 38, in area
    return 0.5 * self.base * self.height
AttributeError: 'RightPyramid' object has no attribute 'height'

Did you guess that Python will try to call Triangle.area()? This is because of something called the method resolution order.

Note: How did we notice that Triangle.area() was called and not, as we hoped, Square.area()? If you look at the last line of the traceback (before the AttributeError), you’ll see a reference to a specific line of code:

return 0.5 * self.base * self.height

You may recognize this from geometry class as the formula for the area of a triangle. Otherwise, if you’re like me, you might have scrolled up to the Triangle and Rectangle class definitions and seen this same code in Triangle.area().

Method Resolution Order

The method resolution order (or MRO) tells Python how to search for inherited methods. This comes in handy when you’re using super() because the MRO tells you exactly where Python will look for a method you’re calling with super() and in what order.

Every class has an .__mro__ attribute that allows us to inspect the order, so let’s do that:

>>>
>>> RightPyramid.__mro__
(<class '__main__.RightPyramid'>, <class '__main__.Triangle'>, 
 <class '__main__.Square'>, <class '__main__.Rectangle'>, 
 <class 'object'>)

This tells us that methods will be searched first in Rightpyramid, then in Triangle, then in Square, then Rectangle, and then, if nothing is found, in object, from which all classes originate.

The problem here is that the interpreter is searching for .area() in Triangle before Square and Rectangle, and upon finding .area() in Triangle, Python calls it instead of the one you want. Because Triangle.area() expects there to be a .height and a .base attribute, Python throws an AttributeError.

Luckily, you have some control over how the MRO is constructed. Just by changing the signature of the RightPyramid class, you can search in the order you want, and the methods will resolve correctly:

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height
        super().__init__(self.base)

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 * perimeter * self.slant_height + base_area

Notice that RightPyramid initializes partially with the .__init__() from the Square class. This allows .area() to use the .length on the object, as is designed.

Now, you can build a pyramid, inspect the MRO, and calculate the surface area:

>>>
>>> pyramid = RightPyramid(2, 4)
>>> RightPyramid.__mro__
(<class '__main__.RightPyramid'>, <class '__main__.Square'>, 
<class '__main__.Rectangle'>, <class '__main__.Triangle'>, 
<class 'object'>)
>>> pyramid.area()
20.0

You see that the MRO is now what you’d expect, and you can inspect the area of the pyramid as well, thanks to .area() and .perimeter().

There’s still a problem here, though. For the sake of simplicity, I did a few things wrong in this example: the first, and arguably most importantly, was that I had two separate classes with the same method name and signature.

This causes issues with method resolution, because the first instance of .area() that is encountered in the MRO list will be called.

When you’re using super() with multiple inheritance, it’s imperative to design your classes to cooperate. Part of this is ensuring that your methods are unique so that they get resolved in the MRO, by making sure method signatures are unique—whether by using method names or method parameters.

In this case, to avoid a complete overhaul of your code, you can rename the Triangle class’s .area() method to .tri_area(). This way, the area methods can continue using class properties rather than taking external parameters:

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height
        super().__init__()

    def tri_area(self):
        return 0.5 * self.base * self.height

Let’s also go ahead and use this in the RightPyramid class:

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height
        super().__init__(self.base)

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 * perimeter * self.slant_height + base_area

    def area_2(self):
        base_area = super().area()
        triangle_area = super().tri_area()
        return triangle_area * 4 + base_area

The next issue here is that the code doesn’t have a delegated Triangle object like it does for a Square object, so calling .area_2() will give us an AttributeError since .base and .height don’t have any values.

You need to do two things to fix this:

  1. All methods that are called with super() need to have a call to their superclass’s version of that method. This means that you will need to add super().__init__() to the .__init__() methods of Triangle and Rectangle.

  2. Redesign all the .__init__() calls to take a keyword dictionary. See the complete code below.

class Rectangle:
    def __init__(self, length, width, **kwargs):
        self.length = length
        self.width = width
        super().__init__(**kwargs)

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

# Here we declare that the Square class inherits from 
# the Rectangle class
class Square(Rectangle):
    def __init__(self, length, **kwargs):
        super().__init__(length=length, width=length, **kwargs)

class Cube(Square):
    def surface_area(self):
        face_area = super().area()
        return face_area * 6

    def volume(self):
        face_area = super().area()
        return face_area ** 3

class Triangle:
    def __init__(self, base, height, **kwargs):
        self.base = base
        self.height = height
        super().__init__(**kwargs)

    def tri_area(self):
        return 0.5 * self.base * self.height

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height, **kwargs):
        self.base = base
        self.slant_height = slant_height
        kwargs["height"] = slant_height
        kwargs["length"] = base
        super().__init__(base=base, **kwargs)

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 * perimeter * self.slant_height + base_area

    def area_2(self):
        base_area = super().area()
        triangle_area = super().tri_area()
        return triangle_area * 4 + base_area

There are a number of important differences in this code:

Note: Following the state of kwargs can be tricky here, so here’s a table of .__init__() calls in order, showing the class that owns that call, and the contents of kwargs during that call:

Class Named Arguments kwargs
RightPyramid base, slant_height
Square length base, height
Rectangle length, width base, height
Triangle base, height

Now, when you use these updated classes, you have this:

>>>
>>> pyramid = RightPyramid(base=2, slant_height=4)
>>> pyramid.area()
20.0
>>> pyramid.area_2()
20.0

It works! You’ve used super() to successfully navigate a complicated class hierarchy while using both inheritance and composition to create new classes with minimal reimplementation.

Multiple Inheritance Alternatives

As you can see, multiple inheritance can be useful but also lead to very complicated situations and code that is hard to read. It’s also rare to have objects that neatly inherit everything from more than multiple other objects.

If you see yourself beginning to use multiple inheritance and a complicated class hierarchy, it’s worth asking yourself if you can achieve code that is cleaner and easier to understand by using composition instead of inheritance.

With composition, you can add very specific functionality to your classes from a specialized, simple class called a mixin.

Since this article is focused on inheritance, I won’t go into too much detail on composition and how to wield it in Python, but here’s a short example using VolumeMixin to give specific functionality to our 3D objects—in this case, a volume calculation:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

class VolumeMixin:
    def volume(self):
        return self.area() * self.height

class Cube(VolumeMixin, Square):
    def __init__(self, length):
        super().__init__(length)
        self.height = length

    def area(self):
        return super().area() * 6

In this example, the code was reworked to include a mixin called VolumeMixin. The mixin is then used by Cube and gives Cube the ability to calculate its volume, which is shown below:

>>>
>>> cube = Cube(2)
>>> cube.area()
24
>>> cube.volume()
48

This mixin can be used the same way in any class that has an area defined for it and for which the formula area * height returns the correct volume.

A super() Recap

In this tutorial, you learned how to supercharge your classes with super(). Your journey started with a review of single inheritance and then showed how to call superclass methods easily with super().

You then learned how multiple inheritance works in Python, and techniques to combine super() with multiple inheritance. You also learned about how Python resolves method calls using the method resolution order (MRO), as well as how to inspect and modify the MRO to ensure appropriate methods are called at appropriate times.

For more information about object-oriented programming in Python and using super(), check out these resources:


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

February 12, 2019 09:01 PM UTC


Codementor

Python, For The ❤ of It - part 3 (What I Built With It)

My Journey Into One of World's Most Awesome Languages

February 12, 2019 08:42 PM UTC


PyCoder’s Weekly

Issue #355 (Feb. 12, 2019)

#355 – FEBRUARY 12, 2019
View in Browser »

The PyCoder’s Weekly Logo


Goodbye Virtual Environments?

Could an npm-style __pypackages__ in your app’s project folder be an alternative to using virtual environments? Chad explores this question in his article and goes over PEP 582 as a sample implementation of this idea.
CHAD SMITH

The State of Python Packaging

Describes where Python packaging ecosystem is today, and where the Python Packaging Authority hopes will move next.
BERNAT.TECH

Find a Python Job Through Vettery

alt

Vettery specializes in developer roles and is completely free for job seekers. Interested? Submit your profile, and if accepted, you can receive interview requests directly from top companies seeking Python devs. Get started →
VETTERY sponsor

Python Elects a Steering Council

“After a two-week voting period, which followed a two-week nomination window, Python now has its governance back in place—with a familiar name in the mix.”
JAKE EDGE

Incrementally Migrating Over One Million Lines of Code From Python 2 to Python 3

How the Dropbox team handled their migration from Python 2 → 3. Great list of lessons learned at the end!
DROPBOX.COM

PyCon 2020-2021 Location Announced

The PSF announced that PyCon will be held in Pittsburgh in 2020 and 2021!
PYCON.BLOGSPOT.COM • Shared by Ricky White

Discussions

Conventions for the Order of Methods in Class Definitions?

Dunder methods all first? Alphabetical? How do you do it?
TWITTER.COM/PURPLEDIANE88

Which Python Packages Should I Study in Order to Develop My Python Skills?

REDDIT

Moving Away From Pipenv

REDDIT

Why Does Python Live On Land…?

Ba-dum-tss!
TWITTER.COM/REALPYTHON

Python Jobs

Senior Systems Engineer (Hamilton, ON, Canada)

Preteckt

Python Web Developer (Remote)

Premiere Digital Services

Software Developer (Herndon, VA)

L2T, LLC

Tech Lead / Senior Software Engineer (Seattle, WA)

Indeed.com Incubator

Python Software Engineer (London, UK)

Pole Star Space Applications Ltd.

Senior Engineer Python & More (Winterthur, Switzerland)

DEEP IMPACT AG

Sr Enterprise Python Developer (Toronto, ON, Canada)

Kognitiv

Senior Software Engineer (Santa Monica, CA)

GoodRX

Computer Science Teacher (Pasadena, CA)

ArtCenter College of Design

Senior Python Engineer (New York, NY)

15Five

Software Engineer (Herndon, VA)

Charon Technologies

Web UI Developer (Herndon, VA)

Charon Technologies

More Python Jobs >>>

Articles & Tutorials

The Ultimate List of Data Science Podcasts

Over a dozen shows that discuss topics in big data, data analysis, statistics, machine learning, and artificial intelligence. What’s your pick?
REAL PYTHON

Python Exceptions Considered an Anti-Pattern

Nikita goes over the drawbacks of Python exceptions and makes a case for why they could be considered an anti-pattern in some cases. Oh, and he also proposes a solution… Worth a read!
NIKITA SOBOLEV opinion

Take Control of Your Job Search With Indeed Prime

alt

With Indeed Prime, you’re in the driver’s seat. Tell us about your skills, career goals, and salary requirements and we’ll match you with top companies looking to hire candidates like you. Apply today to get started!
INDEED sponsor

A Successful Python 3 Migration Story

How the Zato engineering team migrated 130,000 lines of code from Python 2 to Python 3.
ZATO.IO

Python in Education: Request for Ideas

The PSF wants to hear your ideas on ways it can fund work to improve Python in education.
PSF

Python 3 Template Strings Instead of External Template Engine

I’ve been a fan of Python’s template strings and this article demonstrates a good use case for them.
ESHLOX.NET

Python Architecture Stuff: Do We Need More?

Some good resources linked in this article if you’re looking to improve the architecture of your Python apps, in order to make them easier to test, for example.
OBEYTHETESTINGGOAT.COM

Trying Out the := “Walrus Operator” in Python 3.8

The first alpha of Python 3.8 was just released. With that comes a mayor new feature in the form of PEP 572 (Assignment Expressions). Alexander demos this new feature in this short & sweet article.
ALEXANDER HULTNÉR

Python Itertools: For a Faster and Memory Efficient Code

KANOKI.ORG

Bayesian Analysis With Python (Interview With Osvaldo Martin)

Osvaldo Martin is one of the developers of PyMC3 and ArviZ. He is a researcher specialized in Bayesian statistics and data science.
FEDERICO CARRONE

Master Intermediate Python Skills With “Python 201”

If you already know the basics of Python and now you want to go to the next level, then this is the book for you. This book is for intermediate level Python programmers only—there won’t be any beginner chapters here. Learn More →
MIKE DRISCOLL book sponsor

Projects & Code

PythonEXE: How to Create an Executable File From a Python Script?

A simple project that demonstrates how to create an executable from a Python project.
GITHUB.COM/JABBALACI

Django Bugfix Releases: 2.1.7, 2.0.12 and 1.11.20

DJANGOPROJECT.COM

Dry-Python: Libraries for Pluggable Business Logic Components

DRY-PYTHON.ORG

PyPy V7.0.0: Triple Release of 2.7, 3.5 and 3.6-Alpha

MOREPYPY.BLOGSPOT.COM

python-o365: Interact With Microsoft Graph and Office 365 API

GITHUB.COM/O365

UnrealEnginePython: Embed Python in Unreal Engine 4

GITHUB.COM/20TAB • Shared by Mike Kennedy

art: ASCII Art Library for Python

GITHUB.COM/SEPANDHAGHIGHI

demoji: Accurately Remove Emojis From Text Strings

Accurately find or remove emojis from a blob of text.
BRAD SOLOMON

pipelines: Scripting Massively Parallel Pipelines With Python

GITHUB.COM/CALEBWIN

Events

Python North East

February 13, 2019
PYTHONNORTHEAST.COM

Python Atlanta

February 14, 2019
MEETUP.COM

PyCon Belarus 2019

February 15 to February 17, 2019
PYCON.ORG

Dominican Republic Python User Group

February 19, 2019
PYTHON.DO

PyCon Namibia 2019

February 19 to February 22, 2019
PYCON.ORG


Happy Pythoning!
This was PyCoder’s Weekly Issue #355.
View in Browser »

alt

[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]

February 12, 2019 08:30 PM UTC


Codementor

Downloading Files using Python (Simple Examples)

Learn how to download files from the web using Python modules like requests, urllib, and wget. We used many techniques and download from multiple sources.

February 12, 2019 05:39 PM UTC


Mike Driscoll

Creating a Calculator with wxPython

A lot of beginner tutorials start with “Hello World” examples. There are plenty of websites that use a calculator application as a kind of “Hello World” for GUI beginners. Calculators are a good way to learn because they have a set of widgets that you need to lay out in an orderly fashion. They also require a certain amount of logic to make them work correctly. For this calculator, let’s focus on being able to do the following:

  • Addition
  • Subtraction
  • Multiplication
  • Division

I think that supporting these four functions is a great starting place and also give you plenty of room for enhancing the application on your own.


Figuring Out the Logic

One of the first items that you will need to figure out is how to actually execute the equations that you build. For example, let’s say that you have the following equation:


1 + 2 * 5

What is the solution? If you read it left-to-right, the solution would seem to be 3 * 5 or 15. But multiplication has a higher precedence than addition, so it would actually be 10 + 1 or 11. How do you figure out precedence in code? You could spend a lot of time creating a string parser that groups numbers by the operand or you could use Python’s built-in `eval` function. The eval() function is short for evaluate and will evaluate a string as if it was Python code.

A lot of Python programmers actually discourage the user of eval(). Let’s find out why.


Is eval() Evil?

The eval() function has been called “evil” in the past because it allows you to run strings as code, which can open up your application’s to nefarious evil-doers. You have probably read about SQL injection where some websites don’t properly escape strings and accidentally allowed dishonest people to edit their database tables by running SQL commands via strings. The same concept can happen in Python when using the eval() function. A common example of how eval could be used for evil is as follows:

eval("__import__('os').remove('file')")

This code will import Python’s os module and call its remove() function, which would allow your users to delete files that you might now want them to delete. There are a couple of approaches for avoiding this issue:

  • Don’t use eval()
  • Control what characters are allowed to go to eval()

Since you will be creating the user interface for this application, you will also have complete control over how the user enters characters. This actually can protect you from eval’s insidiousness in a straight-forward manner. You will learn two methods of using wxPython to control what gets passed to eval(), and then you will learn how to create a custom eval() function at the end of the article.


Designing the Calculator

Let’s take a moment and try to design a calculator using the constraints mentioned at the beginning of the chapter. Here is the sketch I came up with:

Calculator mockup

Note that you only care about basic arithmetic here. You won’t have to create a scientific calculator, although that might be a fun enhancement to challenge yourself with. Instead, you will create a nice, basic calculator.

Let’s get started!


Creating the Initial Calculator

Whenever you create a new application, you have to consider where the code will go. Does it go in the wx.Frame class, the wx.Panel class, some other class or what? It is almost always a mix of different classes when it comes to wxPython. As is the case with most wxPython applications, you will want to start by coming up with a name for your application. For simplicity’s sake, let’s call it wxcalculator.py for now.

The first step is to add some imports and subclass the Frame widget. Let’s take a look:

import wx
 
class CalcFrame(wx.Frame):
 
    def __init__(self):
        super().__init__(
            None, title="wxCalculator",
            size=(350, 375))
        panel = CalcPanel(self)
        self.SetSizeHints(350, 375, 350, 375)
        self.Show()
 
 
if __name__ == '__main__':
    app = wx.App(False)
    frame = CalcFrame()
    app.MainLoop()

This code is very similar to what you have seen in the past. You subclass wx.Frame and give it a title and initial size. Then you instantiate the panel class, CalcPanel (not shown) and you call the SetSizeHints() method. This method takes the smallest (width, height) and the largest (width, height) that the frame is allowed to be. You may use this to control how much your frame can be resized or in this case, prevent any resizing. You can also modify the frame’s style flags in such a way that it cannot be resized too.

Here’s how:

class CalcFrame(wx.Frame):
 
    def __init__(self):
        no_resize = wx.DEFAULT_FRAME_STYLE & ~ (wx.RESIZE_BORDER | 
                                                wx.MAXIMIZE_BOX)
        super().__init__(
            None, title="wxCalculator",
            size=(350, 375), style=no_resize)
        panel = CalcPanel(self)
        self.Show()

Take a look at the no_resize variable. It is creating a wx.DEFAULT_FRAME_STYLE and then using bitwise operators to remove the resizable border and the maximize button from the frame.

Let’s move on and create the CalcPanel:

class CalcPanel(wx.Panel):
 
    def __init__(self, parent):
        super().__init__(parent)
        self.last_button_pressed = None
        self.create_ui()

I mentioned this in an earlier chapter, but I think it bears repeating here. You don’t need to put all your interfacer creation code in the init method. This is an example of that concept. Here you instantiate the class, set the last_button_pressed attribute to None and then call create_ui(). That is all you need to do here.

Of course, that begs the question. What goes in the create_ui() method? Well, let’s find out!

def create_ui(self):
    main_sizer = wx.BoxSizer(wx.VERTICAL)
    font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL)
 
    self.solution = wx.TextCtrl(self, style=wx.TE_RIGHT)
    self.solution.SetFont(font)
    self.solution.Disable()
    main_sizer.Add(self.solution, 0, wx.EXPAND|wx.ALL, 5)
    self.running_total = wx.StaticText(self)
    main_sizer.Add(self.running_total, 0, wx.ALIGN_RIGHT)
 
    buttons = [['7', '8', '9', '/'],
               ['4', '5', '6', '*'],
               ['1', '2', '3', '-'],
               ['.', '0', '', '+']]
    for label_list in buttons:
        btn_sizer = wx.BoxSizer()
        for label in label_list:
            button = wx.Button(self, label=label)
            btn_sizer.Add(button, 1, wx.ALIGN_CENTER|wx.EXPAND, 0)
            button.Bind(wx.EVT_BUTTON, self.update_equation)
        main_sizer.Add(btn_sizer, 1, wx.ALIGN_CENTER|wx.EXPAND)
 
    equals_btn = wx.Button(self, label='=')
    equals_btn.Bind(wx.EVT_BUTTON, self.on_total)
    main_sizer.Add(equals_btn, 0, wx.EXPAND|wx.ALL, 3)
 
    clear_btn = wx.Button(self, label='Clear')
    clear_btn.Bind(wx.EVT_BUTTON, self.on_clear)
    main_sizer.Add(clear_btn, 0, wx.EXPAND|wx.ALL, 3)
 
    self.SetSizer(main_sizer)

This is a decent chunk of code, so let’s break it down a bit:

def create_ui(self):
    main_sizer = wx.BoxSizer(wx.VERTICAL)
    font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL)

Here you create the sizer that you will need to help organize the user interface. You will also create a wx.Font object, which is used to modifying the default font of widgets like wx.TextCtrl or wx.StaticText. This is helpful when you want a larger font size or a different font face for your widget than what comes as the default.

self.solution = wx.TextCtrl(self, style=wx.TE_RIGHT)
self.solution.SetFont(font)
self.solution.Disable()
main_sizer.Add(self.solution, 0, wx.EXPAND|wx.ALL, 5)

These next three lines create the wx.TextCtrl, set it to right-justified (wx.TE_RIGHT), set the font and `Disable()` the widget. The reason that you want to disable the widget is because you don’t want the user to be able to type any string of text into the control.

As you may recall, you will be using eval() for evaluating the strings in that widget, so you can’t allow the user to abuse that. Instead, you want fine-grained control over what the user can enter into that widget.

self.running_total = wx.StaticText(self)
main_sizer.Add(self.running_total, 0, wx.ALIGN_RIGHT)

Some calculator applications have a running total widget underneath the actual “display”. A simple way to add this widget is via the wx.StaticText widget.

Now let’s add main buttons you will need to use a calculator effectively:

buttons = [['7', '8', '9', '/'],
           ['4', '5', '6', '*'],
           ['1', '2', '3', '-'],
           ['.', '0', '', '+']]
for label_list in buttons:
    btn_sizer = wx.BoxSizer()
    for label in label_list:
        button = wx.Button(self, label=label)
        btn_sizer.Add(button, 1, wx.ALIGN_CENTER|wx.EXPAND, 0)
        button.Bind(wx.EVT_BUTTON, self.update_equation)
    main_sizer.Add(btn_sizer, 1, wx.ALIGN_CENTER|wx.EXPAND)

Here you create a list of lists. In this data structure, you have the primary buttons used by your calculator. You will note that the there is a blank string in the last list that will be used to create a button that doesn’t do anything. This is to keep the layout correct. Theoretically, you could update this calculator down the road such that that button could be percentage or do some other function.

The next step is to create a the buttons, which you can do by looping over the list. Each nested list represents a row of buttons. So for each row of buttons, you will create a horizontally oriented wx.BoxSizer and then loop over the row of widgets to add them to that sizer. Once every button is added to the row sizer, you will add that sizer to your main sizer. Note that each of these button’s is bound to the `update_equation` event handler as well.

Now you need to add the equals button and the button that you may use to clear your calculator:

equals_btn = wx.Button(self, label='=')
equals_btn.Bind(wx.EVT_BUTTON, self.on_total)
main_sizer.Add(equals_btn, 0, wx.EXPAND|wx.ALL, 3)
 
clear_btn = wx.Button(self, label='Clear')
clear_btn.Bind(wx.EVT_BUTTON, self.on_clear)
main_sizer.Add(clear_btn, 0, wx.EXPAND|wx.ALL, 3)
 
self.SetSizer(main_sizer)

In this code snippet you create the “equals” button which you then bind to the on_total event handler method. You also create the “Clear” button, for clearing your calculator and starting over. The last line sets the panel’s sizer.

Let’s move on and learn what most of the buttons in your calculator are bound to:

def update_equation(self, event):
    operators = ['/', '*', '-', '+']
    btn = event.GetEventObject()
    label = btn.GetLabel()
    current_equation = self.solution.GetValue()
 
    if label not in operators:
        if self.last_button_pressed in operators:
            self.solution.SetValue(current_equation + ' ' + label)
        else:
            self.solution.SetValue(current_equation + label)
    elif label in operators and current_equation is not '' \
         and self.last_button_pressed not in operators:
        self.solution.SetValue(current_equation + ' ' + label)
 
    self.last_button_pressed = label
 
    for item in operators:
        if item in self.solution.GetValue():
            self.update_solution()
            break

This is an example of binding multiple widgets to the same event handler. To get information about which widget has called the event handler, you can call the `event` object’s GetEventObject() method. This will return whatever widget it was that called the event handler. In this case, you know you called it with a wx.Button instance, so you know that wx.Button has a `GetLabel()` method which will return the label on the button. Then you get the current value of the solution text control.

Next you want to check if the button’s label is an operator (i.e. /, *, -, +). If it is, you will change the text controls value to whatever is currently in it plus the label. On the other hand, if the label is not an operator, then you want to put a space between whatever is currently in the text box and the new label. This is for presentation purposes. You could technically skip the string formatting if you wanted to.

The last step is to loop over the operands and check if any of them are currently in the equation string. If they are, then you will call the update_solution() method and break out of the loop.

Now you need to write the update_solution() method:

def update_solution(self):
    try:
        current_solution = str(eval(self.solution.GetValue()))
        self.running_total.SetLabel(current_solution)
        self.Layout()
        return current_solution
    except ZeroDivisionError:
        self.solution.SetValue('ZeroDivisionError')
    except:
        pass

Here is where the “evil” eval() makes its appearance. You will extract the current value of the equation from the text control and pass that string to eval(). Then convert that result back to a string so you can set the text control to the newly calculated solution. You want to wrap the whole thin in a try/except statement to catch errors, such as the ZeroDivisionError. The last except statement is known as a bare except and should really be avoided in most cases. For simplicity, I left it in there, but feel free to delete those last two lines if they offend you.

The next method you will want to take a look at is the on_clear() method:

def on_clear(self, event):
    self.solution.Clear()
    self.running_total.SetLabel('')

This code is pretty straight-forward. All you need to do is call your solution text control’s Clear() method to empty it out. You will also want to clear the `running_total` widget, which is an instance of wx.StaticText. That widget does not have a Clear() method, so instead you will call SetLabel() and pass in an empty string.

The last method you will need to create is the on_total() event handler, which will calculate the total and also clear out your running total widget:

def on_total(self, event):
    solution = self.update_solution()
    if solution:
        self.running_total.SetLabel('')

Here you can call the update_solution() method and get the result. Assuming that all went well, the solution will appear in the main text area and the running total will be emptied.

Here is what the calculator looks like when I ran it on a Mac:

wxPython Calculator on Mac OS

And here is what the calculator looks like on Windows 10:

wxPython Calculator on Windows 10

Let’s move on and learn how you might allow the user to use their keyboard in addition to your widgets to enter an equation.


Using Character Events

Most calculators will allow the user to use the keyboard when entering values. In this section, I will show you how to get started adding this ability to your code. The simplest method to use to make this work is to bind the wx.TextCtrl to the wx.EVT_TEXT event. I will be using this method for this example. However another way that you could do this would be to catch wx.EVT_KEY_DOWN and then analyze the key codes. That method is a bit more complex though.

The first item that we need to change is our CalcPanel‘s constructor:

# wxcalculator_key_events.py
 
import wx
 
class CalcPanel(wx.Panel):
 
    def __init__(self, parent):
        super().__init__(parent)
        self.last_button_pressed = None
        self.whitelist = ['0', '1', '2', '3', '4',
                          '5', '6', '7', '8', '9',
                          '-', '+', '/', '*', '.']
        self.on_key_called = False
        self.empty = True
        self.create_ui()

Here you add a whitelist attribute and a couple of simple flags, self.on_key_called and self.empty. The white list are the only characters that you will allow the user to type in your text control. You will learn about the flags when we actually get to the code that uses them.

But first, you will need to modify the create_ui() method of your panel class. For brevity, I will only reproduce the first few lines of this method:

def create_ui(self):
    main_sizer = wx.BoxSizer(wx.VERTICAL)
    font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL)
 
    self.solution = wx.TextCtrl(self, style=wx.TE_RIGHT)
    self.solution.SetFont(font)
    self.solution.Bind(wx.EVT_TEXT, self.on_key)
    main_sizer.Add(self.solution, 0, wx.EXPAND|wx.ALL, 5)
    self.running_total = wx.StaticText(self)
    main_sizer.Add(self.running_total, 0, wx.ALIGN_RIGHT)

Feel free to download the full source from Github or refer to the code in the previous section. The main differences here in regards to the text control is that you are no longer disabling it and you are binding it to an event: wx.EVT_TEXT.

Let’s go ahead an write the on_key() method:

def on_key(self, event):
    if self.on_key_called:
        self.on_key_called = False
        return
 
    key = event.GetString()
    self.on_key_called = True
 
    if key in self.whitelist:
        self.update_equation(key)

Here you check to see if the self.on_key_called flag is True. If it is, we set it back to False and `return` early. The reason for this is that when you use your mouse to click a button, it will cause EVT_TEXT to fire. The `update_equation()` method will get the contents of the text control which will be the key we just pressed and add the key back to itself, resulting in a double value. This is one way to workaround that issue.

You will also note that to get the key that was pressed, you can call the event object’s GetString() method. Then you will check to see if that key is in the white list. If it is, you will update the equation.

The next method you will need to update is update_equation():

def update_equation(self, text):
    operators = ['/', '*', '-', '+']
    current_equation = self.solution.GetValue()
 
    if text not in operators:
        if self.last_button_pressed in operators:
            self.solution.SetValue(current_equation + ' ' + text)
        elif self.empty and current_equation:
            # The solution is not empty
            self.empty = False
        else:
            self.solution.SetValue(current_equation + text)
    elif text in operators and current_equation is not '' \
         and self.last_button_pressed not in operators:
        self.solution.SetValue(current_equation + ' ' + text)
 
    self.last_button_pressed = text
    self.solution.SetInsertionPoint(-1)
 
    for item in operators:
        if item in self.solution.GetValue():
            self.update_solution()
            break

Here you add a new elif that checks if the self.empty flag is set and if the current_equation has anything in it. In other words, if it is supposed to be empty and it’s not, then we set the flag to False because it’s not empty. This prevents a duplicate value when the keyboard key is pressed. So basically you need two flags to deal with duplicate values that can be caused because you decided to allow users to use their keyboard.

The other change to this method is to add a call to SetInsertionPoint() on your text control, which will put the insertion point at the end of the text control after each update.

The last required change to the panel class happens in the on_clear() method:

def on_clear(self, event):
    self.solution.Clear()
    self.running_total.SetLabel('')
    self.empty = True
    self.solution.SetFocus()

This change was done by adding two new lines to the end of the method. The first is to reset self.empty back to True. The second is to call the text control’s SetFocus() method so that the focus is reset to the text control after it has been cleared.

You could also add this SetFocus() call to the end of the on_calculate() and the on_total() methods. This should keep the text control in focus at all times. Feel free to play around with that on your own.


Creating a Better eval()

Now that you have looked at a couple of different methods of keeping the “evil” eval() under control, let’s take a few moments to learn how you can create a custom version of eval() on your own. Python comes with a couple of handy built-in modules called ast and operator. The ast module is an acronym that stands for “Abstract Syntax Trees” and is used “for processing trees of the Python abstract syntax grammar” according to the documentation. You can think of it as a data structure that is a representation of code. You can use the ast module to create a compiler in Python.

The operator module is a set of functions that correspond to Python’s operators. A good example would be operator.add(x, y) which is equivalent to the expression x+y. You can use this module along with the `ast` module to create a limited version of eval().

Let’s find out how:

import ast
import operator
 
allowed_operators = {ast.Add: operator.add, ast.Sub: operator.sub, 
                     ast.Mult: operator.mul, ast.Div: operator.truediv}
 
def noeval(expression):
    if isinstance(expression, ast.Num):
        return expression.n
    elif isinstance(expression, ast.BinOp):
        print('Operator: {}'.format(expression.op))
        print('Left operand: {}'.format(expression.left))
        print('Right operand: {}'.format(expression.right))
        op = allowed_operators.get(type(expression.op))
        if op:
            return op(noeval(expression.left), 
                      noeval(expression.right))
    else:
        print('This statement will be ignored')
 
if __name__ == '__main__':
    print(ast.parse('1+4', mode='eval').body)
    print(noeval(ast.parse('1+4', mode='eval').body))
    print(noeval(ast.parse('1**4', mode='eval').body))
    print(noeval(ast.parse("__import__('os').remove('path/to/file')", mode='eval').body))

Here you create a dictionary of allowed operators. You map ast.Add to operator.add, etc. Then you create a function called `noeval` that accepts an `ast` object. If the expression is just a number, you return it. However if it is a BinOp instance, than you print out the pieces of the expression. A BinOp is made up of three parts:

  • The left part of the expression
  • The operator
  • The right hand of the expression

What this code does when it finds a BinOp object is that it then attempts to get the type of ast operation. If it is one that is in our allowed_operators dictionary, then you call the mapped function with the left and right parts of the expression and return the result.

Finally if the expression is not a number or one of the approved operators, then you just ignore it. Try playing around with this example a bit with various strings and expressions to see how it works.

Once you are done playing with this example, let’s integrate it into your calculator code. For this version of the code, you can call the Python script wxcalculator_no_eval.py. The top part of your new file should look like this:

# wxcalculator_no_eval.py
 
import ast
import operator
 
import wx
 
 
class CalcPanel(wx.Panel):
 
    def __init__(self, parent):
        super().__init__(parent)
        self.last_button_pressed = None
        self.create_ui()
 
        self.allowed_operators = {
            ast.Add: operator.add, ast.Sub: operator.sub, 
            ast.Mult: operator.mul, ast.Div: operator.truediv}

The main differences here is that you now have a couple of new imports (i.e. ast and operator) and you will need to add a Python dictionary called self.allowed_operators. Next you will want to create a new method called noeval():

def noeval(self, expression):
    if isinstance(expression, ast.Num):
        return expression.n
    elif isinstance(expression, ast.BinOp):
        return self.allowed_operators[
            type(expression.op)](self.noeval(expression.left), 
                                 self.noeval(expression.right))
    return ''

This method is pretty much exactly the same as the function you created in the other script. It has been modified slightly to call the correct class methods and attributes however. The other change you will need to make is in the update_solution() method:

def update_solution(self):
    try:
        expression = ast.parse(self.solution.GetValue(),
                               mode='eval').body
        current_solution = str(self.noeval(expression))
        self.running_total.SetLabel(current_solution)
        self.Layout()
        return current_solution
    except ZeroDivisionError:
        self.solution.SetValue('ZeroDivisionError')
    except:
        pass

Now the calculator code will use your custom eval() method and keep you protected from the potentially harmfulness of eval(). The code that is in Github has the added protection of only allowing the user to use the onscreen UI to modify the contents of the text control. However you can easily change it to enable the text control and try out this code without worrying about eval() causing you any harm.


Wrapping Up

In this chapter you learned several different approaches to creating a calculator using wxPython. You also learned a little bit about the pros and cons of using Python’s built-in eval() function. Finally, you learned that you can use Python’s ast and operator modules to create a finely-grained version of eval() that is safe for you to use. Of course, since you are controlling all input into eval(), you can also control the real version quite easily though your UI that you generate with wxPython.

Take some time and play around with the examples in this article. There are many enhancements that could be made to make this application even better. When you find bugs or missing features, challenge yourself to try to fix or add them.


Download the Source

The source code for this article can be found on Github. This article is based on one of the chapters from my book, Creating GUI Applications with wxPython.

February 12, 2019 02:56 PM UTC


PyCon

Hatchery programs at PyCon 2019!

The PyCon Hatchery program was introduced last year to allow for the addition to PyCon of new tracks, summits, demos, or any other imaginable events which share and fulfill the mission of the Python Software Foundation.

The Hatchery program was first run as a trial in 2018, welcoming the PyCon Charlas as it’s inaugural program. This year we are happy to have built upon that trial and are delighted to have received so many proposals and to have accepted many more events!

PyCon Charlas will be returning in 2019, PyCon Charlas ("charla" is the Spanish word for conference "talk") will be a full day track of Python talks en Español and is open to the entire Python community. This track will showcase Spanish speaking Pythonistas from a variety of countries. Their schedule will launch later this week so stay tuned! us.pycon.org/2019/hatchery/charlas.

New this year for PyCon 2019!

The Art of Python is a miniature arts festival focusing on narrative, performance, and visual art. We intend to encourage and showcase novel art that helps us share our emotionally charged experiences of programming (particularly in Python). We hope that by attending, our audience will discover new aspects of empathy and rapport, and find a different kind of delight and perspective than might otherwise be expected at a large conference. For more details and information to submit a proposal visit us.pycon.org/2019/hatchery/artofpython.



Maintainers Summit is seeking to build a community of practice for project maintainers and key contributors. We seek to help the Python community sustain and grow healthy projects and communities. Activities will include talks and mini unconference around technology, community, resourcing, and more as it relates to package maintenance. For more details and information to submit a proposal visit us.pycon.org/2019/hatchery/maintainers.

Mentored Sprints for Diverse Beginners is a newcomer’s introduction to contributing to open source. Walking the path from user to collaborator, and thus contributing to an open source project, can sometimes be intimidating especially for newcomers. We also recognize that some groups are traditionally underrepresented in the open source community and we would like to support them in their open source path. For these reasons, we are running the first ever PyCon mentored sprints for individuals from underrepresented groups willing to start contributing to Python projects. This event will provide a supportive, friendly, and safe environment for all the attendees and partner open source projects. For more details and information to submit a proposal visit us.pycon.org/2019/hatchery/mentoredsprints.

We look forward to seeing you in Cleveland and hope that you’ll take some time to explore these exciting additions to the schedule!

February 12, 2019 11:25 AM UTC


Django Weblog

Django bugfix release: 2.0.13

Today we've issued the 2.0.13 bugfix release.

The release package and checksums are available from our downloads page, as well as from the Python Package Index. The PGP key ID used for this release is Carlton Gibson: E17DF5C82B4F9D00.

February 12, 2019 10:22 AM UTC


Codementor

small stuff about python3 print()

Some small stuff about Python3 print()...

February 12, 2019 09:16 AM UTC


gamingdirectional

Moving the player object in Pygame

In the last chapter we have created the animation effect for the player object and in this chapter, we will move the player object in the x-axis. We will leave the wall and boundary collision detection mechanism to the next chapter. In the last chapter we have already linked up the keyboard events with the game manager class and in this chapter, we only need a slight modification to move the...

Source

February 12, 2019 07:14 AM UTC


Codementor

Python, For The ❤ of It - part 2

My Journey Into One of World's Most Awesome Languages

February 12, 2019 05:02 AM UTC


Python Software Foundation

Python Community service award Q3: Mario Corchero




The PSF community service awards go to those individuals whose work and commitment complement and strengthen the PSF mission: to support and facilitate the growth of a diverse global Python community. So when thinking about individuals that go above and beyond to support the global community Mario Corchero is a name that comes easily to mind.


Not only is Mario a Senior Software Engineer for Bloomberg but he also devotes incredible amounts of his time to organise PyCon ES (Spain), PyLondinium, and more recently, the Spanish speaking track of PyCon: Las Pycon Charlas.

Mario is the true embodiment of the Python community spirit and for this reason, the Python Software Foundation has awarded Mario Corchero with the Q3 2018 Community Service Award.

RESOLVED, that the Python Software Foundation award the Q3 2018 Community Service Award to Mario Corchero for helping organize PyLondinium, the PyCon Charlas track, and PyCon Spain.


Mario's contributions to the Python Community


PyConES


With the growing popularity and global adoption of Python there also comes the need to bring together diverse community groups. Although large events such as PyCon US are incredibly important in bringing these groups together, these are not always accessible to the whole community. Smaller, localized events such as Python ES, France, Namibia, Colombia, and many many others help with the goal of bringing cohesion to the global community.

According to David Naranjo (co-organiser of PyConES), PyConES was the first event of this kind that he and Mario attended together. They loved it so much that while at PyConES16 they decided to submit an application to organise and bring this event to their region: Extremadura.

On top of the many challenges that come with organising an event of this type (i.e. drafting the programme, getting talks accepted, running the event on the day), they have faced an additional layer of complexity: neither of them lives in the region anymore.

This has made the organisation of PyConES a true community effort: from the organising committee to the sponsors and the volunteers that work together to make this a huge success. PyConEs is now a  staple Python event in Europe with more than 600 attendees every year, and it owes its success in a great deal to Mario’s efforts.



PyLondinium


A year after organising his first PyConES, Mario embarked on yet another journey: the organisation of PyLondinium. An event focused on showcasing the many use cases of Python as opposed to other events such as the PyData events.


PyLondinium is not only focused on bringing together the Python community but also to raise money for the PSF and its programmes around the world. In this particular case, Bloomberg, a long-time Python supporter, has played an important role in the success of the event. Not only do they host the event at their Europe headquarters in the heart of London but they also help to cover some of the costs as the main event sponsor, keeping the ticket prices at an affordable level.



Pylondinium 2018

Accessibility for the wider community


As a passionate community builder, from a non-English speaking country, localization and accessibility of the Python language is something that matters to Mario. Most of the coding resources out in the world are written in English, which can be a barrier to those whose primary language is not English or simply do not speak the language at all. That is why when he was presented with the opportunity to chair the Spanish track of PyCon US 2017 (Las PyCon Charlas) he did so wholeheartedly, embarking into yet another community journey alongside PSF Director Naomi Ceder.

Again, like his other endeavours, Las Charlas was an absolute success. It gathered people from all over from Latin America and Spain for a full day of talks in Spanish on such topics as machine learning, astronomy and security. In fact, it was such a success that the Charlas is back this coming year and the organisers are already receiving talks submissions (for more details visit https://us.pycon.org/2019/speaking/).



PyCon Charlas 2018


When asked why he organises all of these events, his answer is rather simple and honest. It is usually driven by a ‘how come no one is doing this yet?’" says Mario. But when digging deeper it becomes evident that Mario’s motivations lie in bringing the community together and nurturing it. Mario is extremely dedicated to the community and helping others to get involved. From creating Spanish tracks for PyCon USA or creating events serving specific areas or regions, Mario is constantly finding ways to bring Pythonistas together.



February 12, 2019 04:23 AM UTC


codingdirectional

Return the list of the number and its power with python

Before we start our new python project here is another solution for one of the python’s question on codewars. In this example, we need to create a method which will accept a number and returns a list consists of a pair of a number and power that are equal to that input number. For example, the input number is 9, then the number and it’s power which is equals to that input number is [3, 2] because 3 * 3 = 9. If there is no number and power that match the input value then None will be returned. Here is the solution to the above mentioned question.

def isPP(power):
    
    for num in range(1, power):
        for po in range(2, power):
            if(num ** po == power):
                return [num, po]
      
    return None

The above method is only suitable to solve a number input which is below 1800, the program will become freeze if the number goes beyond that value (depends on the processing power of your own computer). Well, hope you like this quick solution, in the next chapter we will begin our project.

February 12, 2019 04:15 AM UTC


Read the Docs

Defaulting New Projects to Python 3

New projects that are just getting started with Read the Docs will now use Python 3 by default. While it is still possible to configure your project to use Python 2.7 with our configuration file, we think it’s important to help push the Python ecosystem towards adopting Python 3.

Our default Python version is currently Python 3.7. Projects can also select Python versions 3.6 and 3.5 using our default build image. We will eventually remove support for building projects with Python versions 3.3 and 3.4, however it is still possible to select a build image with support for either version.

To select a specific version of Python, other than our default, you can use our configuration file to specify a Python version, using the python.version configuration option.

If your project does require Python version 3.3 or 3.4, you can only select these versions if you also specify stable build image for your project’s build.image configuration option. As we release new versions of our build image, this stable image will eventually lose support for Python 3.3 and 3.4, so it’s suggested that your project upgrade to a supported version of Python.

February 12, 2019 12:00 AM UTC