Quantcast
Channel: Planet Python
Viewing all 22625 articles
Browse latest View live

Python Celery - Weekly Celery Tutorials and How-tos: Parallel Monte Carlo with Dask

$
0
0

Today, we will look at how to implement a Monte Carlo simulation to value an Asian option in a Jupyter notebook; and how to distribute that Monte Carlo simluation across a cluster using Dask. Don’t worry if you are not a Quantitative Analyst, this blog is more about Dask, from dask import delayed and exciting cluster stuff than about financial mathematics.

Asian options

An Asian option is an exotic type of financial option contract. The payoff at expiry depends on the average underlying price over some pre-set period of time - different to a plain vanilla option where the payoff depends on the underlying price at expiry (fun fact: they are called Asian options because legend has it that Bankers Trust traded the first option linked to the average price of crude oil in in Tokyo back in 1987).

The payoff of a fixed-strike Asian call option at expiry is defined as: C(T) = max(0, A(T) - K). There is no closed form analytical formula to determine the fair value any time before expiry for this kind of option. But we can determine the fair value by simulating a large number of paths for the underlying asset price and calculate the payout at expiry for each path. The fair value is then the discounted average over all these Monte Carlo simulations.

Random walk

We assume that the current value of the underlying asset price is composed of the past value and a determinstic upward trend plus an error term defined as a white noise (a normal variable with zero mean and variance one). This is called a random walk with drift. Given a start price s0, a constant drift mu and volatility sigma, we can simulate the price path over days number of days like this:

import numpy as np

def random_walk(s0, mu, sigma, days):
    dt = 1/365.
    prices = np.zeros(days)
    shocks = np.zeros(days)
    prices[0] = s0
    for i in range(1, days):
        e = np.random.normal(loc=mu * dt, scale=sigma * np.sqrt(dt))
        prices[i] = prices[i-1] * (1 + e)
    return prices

For any random path, we can now calculate the average price and, given the strike price K, the payoff at expiry:

days = 365 * 4  # days to expiry
s0 = 100  # current underlying price
mu = 0.02  # drift
sigma = 0.2  # volatility
K = 100  # strike price

A = np.average(random_walk(s0, mu, sigma, days)
C = max(0, A - K)

Monte Carlo Simulation

We need to generate a large number of random price paths for the underlying. For each price path we calculate the associated payoff. These payoffs are averaged and discounted to today. This result is the value of the option. The actual number of simulations n required depends on the confidence level you are comfortable with for the estimate. That, in turn, depends on your option’s parameters. I won’t go into details here, so let’s just assume that n = 10000. We need to generate n random paths and calculate the average value of the option at expiry:

n = 10000
%time np.average([max(0, np.average(random_walk(s0, mu, sigma, days)) - K) for i in range(0, n)])
Wall time: 1min 10s
11.673350929139112

Dask task scheduler: dask.distributed

If you have more than one CPU at your disposal, you can bring down the calculation time by distributing the random walk generation across multiple CPUs. This is where Dask comes in. Dask is a flexible library for parallel computing in Python, optimised for interactive computational workloads. Instead of running n random walks in a single Jupyter notebook process, we make Dask distribute these calculations across a number of processes.

Distributing these calculations is the job of the task scheduler. Think of the task scheduler as a server that manages where and how calculations as executed. There are different types of schedulers: single machine scheduler (limited to local machine) and distributed scheduler (distributed across a cluster). Here, I will use a simple single machine scheduler.

In a new shell/command window, start a local dask.distributed process scheduler:

Python 3.6.0 |Continuum Analytics, Inc.| (default, Dec 23 2016, 11:57:41) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from dask.distributed import Client
>>> client = Client(scheduler_port=8786)

You can interrogate the client object to confirm it runs on port 8786 on localhost. By default, it spins up as many processes as you have CPUs on your computer, in my case I have 4 cores:

>>> client
<Client: scheduler='tcp://127.0.0.1:8786' processes=4 cores=4>

Wrapping functions with dask.delayed

We need to instruct our random_walk and np.average function calls to execute lazily. Instead of executing the functions immediately, we want to defer execution via the Dask task scheduler. We can achieve this by wrapping functions in dask.delayed. Alternatively, you can decorate your functions with @dask.delayed (not covered in this blog post, though have a look at http://dask.pydata.org/en/latest/delayed.html#decorator). This delays the execution of the function and generates a task graph instead.

from dask import delayed

s0 = 100
K = 100
mu = 0.02
sigma = 0.2
days = 365*4
n = 10000

result = delayed(np.average)([
    delayed(max)(
        0, 
        delayed(np.average)(random_walk(s0, mu, sigma, days)) - K
    ) for i in range(0, n)
])

result is now a Delayed object and contains the task graph. The task graph is a directed acyclic graph (DAG) and models the dependencies between the np.average, max and random_walk function calls - without executing them. This allows Dask to optimise its task execution strategy.

screenshot

Execute the task graph

We have everything in place now to execute the actual calculation of result. Remember that result up to this point is just a Delayed object. Execute the computation:

%time result.compute()
Wall time: 39.9 s
11.617145839123664

In essence, the calculation time has roughly halved on 4 cores compared to 1 core. The reason it has not reduced to 25% is that there is some overhead involved in managing and distributing the task themselves. Have a look at the Dask docs about dask.delayed and task scheduling.


Ned Batchelder: Me on Talk Python To Me

$
0
0

I was the guest on the most recent episode of the Talk Python To Me podcast: #178: Coverage.py. It was a really fun conversation. I liked at one point being able to say, “I’ll answer the question, and then I will challenge the premise of the question.” Michael does a great job running the show, including providing a full transcript of the episode!

We talked mostly about coverage.py, but there was a small digression into what I do for work, which is the Open edX team at edX. EdX has thousands of courses from hundreds of institutions. But the software we write is open source, and is used by a thousand other sites to do online education. It’s really gratifying to see them doing the kinds of education that edX will never do.

The example I gave on the show is UKK Guru, an Indonesian site running hundreds of small vocational courses that will mean a lot to its learners.

Sorry to repeat myself! Mostly the show is about coverage.py, it’s good. :)

Python Software Foundation: CPython Core Developer Sprint 2018

$
0
0
This September, twenty-nine core committers arrived at Microsoft’s main campus in Redmond, Washington, USA for the now-annual core developer sprints. These are an opportunity for the core team to have focused discussions, in-depth conversations and work free from interruption for five days. By the end of the week, thirty-one core developers went home tired but satisfied.

The major sponsor of the sprints this year was the Python Software Foundation. Microsoft provided the venue and some events during the week, and Facebook, Microsoft, Google, LinkedIn, Bloomberg, and a number of small companies covered their employees’ expenses.

Major Achievements

With over thirty people working for a week, there were many achievements and improvements made for Python 3.8 and earlier versions. Some of the highlights include:

  • The asyncio documentation was completely rewritten
  • Most of PEP 572 (assignment expressions) was implemented and tested
  • Recursive tracebacks and syntax errors in f-strings were improved to provide better feedback
  • The Automerge bot was enabled on the CPython GitHub repository, helping reduce the amount of manual work done by core committers
  • importlib_metadata was improved, helping to standardize the way Python exposes information about installed packages
  • The zipimport module was rewritten in Python code, which will enable new development and improvements for importing modules directly from ZIP files
  • Over 45 contributions (besides our own) were reviewed and merged, and over 80 issues were closed in the CPython repository, with many others in related projects such as Buildbot, Roundup, blurb, and our GitHub bots


Governance Discussions

With our BDFL of Python retiring earlier this year, we spent some time discussing how to approach the future of Python. More than any specific outcomes, the discussions were very fruitful and helped many of us see how similar our positions are to each other.

While no decisions have been made, all current proposals intend to keep the PEP process, and limit the new leaders’ responsibilities to arbitrating controversial decisions.

Final proposals are due by the end of September. See PEP 8000 for an overview of the process and links to related PEPs.

New Core Developers

Those who read the first paragraph carefully will have noticed that more committers left the sprints than arrived. This happened because we appointed two new core developers during the week. Congratulations to Emily Morehouse and Lisa Roach!


From Raymond Hettinger’s announcement post:
Emily is the Director of Engineering at Cuttlesoft. She has previously attended two Language Summits and three core development sprints at PyCon. Since July, Emily has worked with Guido's guidance to implement PEP 572, Assignment Expressions.  She has also worked with Eric Snow to dive into CPython's runtime as well as subinterpreters.  This year at PyCon she gave a talk on Python's AST.
Lisa has a background in network engineering and supported the Cisco sale engineer team to develop high quality Python product demonstrations.  Later she moved to the Facebook security team.  This is her third core developer sprint.  She and Guido are co-authors of PEP 526, Syntax for Variable Annotations. Last year, she worked with Eric Smith on PEP 557, Data Classes.

Other Blogs

Other attendees have posted their own blogs describing their experiences at the sprints. (This list may be updated over time as more are published.)


Thank you!

A huge thanks to all the participants who attended, the various companies who sponsored parts of the event, and the PSF for covering the majority of travel expenses. Thanks also to those contributors who were unable to make it this year. Hopefully next year we can include even more core contributors.

Attendees: Brett Cannon, Kushal Das, Ned Deily, Steve Dower, Ethan Furman, Larry Hastings, Christian Heimes, Raymond Hettinger, Łukasz Langa, Ezio Melotti, Emily Morehouse, Benjamin Peterson, Davin Potts, Lisa Roach, Pablo Galindo Salgado, Neil Schemenauer, Yury Selivanov, Eric V. Smith, Gregory P. Smith, Nathaniel Smith, Eric Snow, Victor Stinner, Andrew Svetlov, Guido van Rossum, Dino Viehland, Petr Viktorin, Zachary Ware, Barry Warsaw, Mariatta Wijaya, Carol Willing

Written by: Steve Dower

Stack Abuse: Daily Coding Problem: Programming Puzzles to your Inbox

$
0
0
Daily Coding Problem: Programming Puzzles to your Inbox

Like just about any other profession, the key to becoming a great programmer is to practice. Practicing often and consistently is an amazing way, and arguably the best way, to challenge yourself and improve your programming skills.

A lot of us have the desire to work in top-tier tech companies, like Microsoft, Google, Facebook, etc. Although a lot of people are scared to even attempt to apply to such high-caliber jobs, feeling too intimidated or under-qualified for a position at one of those companies.

Going to an interview with a top company, or any interview, clear-headed and prepared is important, as looking confident and competent in an interview can mean the difference between landing your dream job or being rejected.

There's just so many things a person can do to become confident for such an interview, and in my opinion - the absolute best way is simply to practice. Being able to solve a problem on the spot tells the interviewer more about your skills than anything you can say verbally.

There are many ways a person can practice, whether it be on personal problems and projects, other resources like books, or using other services online. Daily Coding Problem is one such service.

What is Daily Coding Problem?

Daily Coding Problem is a simple and very useful platform that emails you one coding problem to solve every morning. This ensures that you practice consistently and often enough to stay in shape over a long period of time.

Practicing just one problem a day is enough to make a huge impact on your skill-set and confidence, especially when faced with a task that you might be facing in a top-tier tech company in the near future.

The best thing about their problems is that they use actual problems and questions from top companies that other candidates have faced in their interview sessions there. And personally, I agree with their philosophy - it's always better to be over-prepared rather than under-prepared.

Anyone can use their service, whether you're a self-taught aspiring developer, a recent college graduate, or an experienced professional developer looking to sharpen your skills. What matters most is your understanding of the concepts, data structures, and algorithms you have at your disposal to solve a given problem.

And one of the best things about DCP is that you get these coding problems to your inbox for free. For a small fee, they'll also send you the solutions and in-depth explanations for every single problem you get emailed to your inbox, which you'll receive the day after the problem, giving you time to work it out on your own.

An Example Problem

With each problem emailed to you, you'll get the company of origin, the formulation of the problem, and a code example. In this section we'll take a look at one of DCP's coding problems and the corresponding detailed solution:

Given two singly linked lists that intersect at some point, find the intersecting node. The lists are non-cyclical.

For example, given A = 3 -> 7 -> 8 -> 10 and B = 99 -> 1 -> 8 -> 10, return the node with value 8.

In this example, assume nodes with the same value are the exact same node objects.

Do this in O(M + N) time (where M and N are the lengths of the lists) and constant space.  

This problem was asked by Google in an actual interview.

Take your time to carefully read this problem and think a bit about how you would solve it before proceeding. According to the DCP's FAQ, you should have around an hour or so to solve these problems in the interviews, so that should also be the timeframe you allow yourself to solve them at home.

Once you think you've figured it out, or when you get stuck, you can take a look at the detailed solution provided by Daily Coding Problem:

We might start this problem by first ignoring the time and space constraints, in order to get a better grasp of the problem.

Naively, we could iterate through one of the lists and add each node to a set or dictionary, then we could iterate over the other list and check each node we're looking at to see if it's in the set. Then we'd return the first node that is present in the set. This takes O(M + N) time but also O(max(M, N)) space (since we don't know initially which list is longer). How can we reduce the amount of space we need?

We can get around the space constraint with the following trick: first, get the length of both lists. Find the difference between the two, and then keep two pointers at the head of each list. Move the pointer of the larger list up by the difference, and then move the pointers forward in conjunction and check if they match.

def length(head):  
    if not head:
        return 0
    return 1 + length(head.next)

def intersection(a, b):  
    m, n = length(a), length(b)
    cur_a, cur_b = a, b

    if m > n:
        for _ in range(m - n):
            cur_a = cur_a.next
    else:
        for _ in range(n - m):
            cur_b = cur_b.next

    while cur_a != cur_b:
        cur_a = cur_a.next
        cur_b = cur_b.next
    return cur_a

DCP offers in-depth and detailed explanations like this to help you solve each problem and actually understand the solution, as well as reinforce the logic you utilized if you were able to solve it on your own.

As you can see, these problems rely heavily on both logic and creativity, which is what makes them very difficult to solve. But once you've practiced enough and learn the tricks behind these problems - like how to think about the problem and solution, and what tools you have at your disposal - they will become much easier.

Which Programming Languages?

Currently, DCP provides solutions in Python as it is very well-known and similar to pseudo-code, as well as being simple to learn. This seems to be a good choice for solutions since Python code is fairly easy to translate to other languages, given its simple syntax and straight-forward programming style.

Additionally, DCP is looking to expand their pool of solutions to other languages as well, like Java, JavaScript, C++, and Ruby.

Pricing

The best part about all of this is that it's absolutely free to sign up for their service and receive problems to solve each morning.

To get the solutions emailed to you, on the other hand, requires that you pay a small fee:

Daily Coding Problem: Programming Puzzles to your Inbox

The yearly plan charges you only $7.50/month (billed annually) for their services - that's a tad more than a cup of Starbucks coffee in Germany, or around the price of two average cups of coffee in the US!

For the price of a couple of cups of coffee, each month you will receive about 30 real-life interview problems to solve and improve your skills and confidence vastly. It's hard to find another investment that'll pay off this well.

Other Resources for Learning

Lucky for you, Daily Coding Problem isn't the only place to practice and learn online for free!

If books are more your thing, make sure to take a look at these resources and lists:

If you're preparing for a big interview, I'd also suggest that you read up on some tips that will help you improve your chances of landing the job:

Or if you're interested in reading articles on some of the most in-demand and popular programming languages in the world today, check out our Node, Python, or Java articles.

Happy coding!

Codementor: Networking: How To Communicate Between Two Python Programs.

$
0
0
Networking is a huge field, so we’ll stick to the level concept that are important for programming. This is basically a beginner intro to networking. I am passionate about security and as I was reading and watching videos, I decided to write and share out this.

Codementor: Data from Quandl With Celery and Falcon

$
0
0
This post was originally published on Distributed Python (https://www.distributedpython.com/) on July 10th, 2018. All source code examples used in this blog post can be found on GitHub: ...

Python Anywhere: The PythonAnywhere newsletter, September 2018

$
0
0

Well, our last "monthly" newsletter was in September 2017. We must have shifted the bits in the period left one, or something like that :-)

Anyway, welcome to the September 2018 PythonAnywhere newsletter :-) Here's what we've been up to.

Python 3.7 support

We recently added support for Python 3.7. If you signed up since 28 August, you'll have it available on your account -- you can use it just like any other Python version.

If you signed up before then, it's a little more complicated, but we can update your account to provide it -- there's more information in this blog post.

Self-installation of HTTPS certificates

We've also been working on making setting up HTTPS on your website a bit more streamlined. Previously you had to get the certificate and the private key, and then email us asking for them to be installed, which could take up to 24 hours. Now you can cut our support team out of the loop and install it all yourself. Check out this blog post for the details.

There will be more improvements to HTTPS support coming soon...

Force HTTPS

Another shiny new feature: built-in support for forcing people who visit your site to use HTTPS instead of non-secure HTTP, without the need to change your code! Once again, there's more info on the blog.

PythonAnywhere metrics

We're wondering if it would be interesting for you to hear a bit about some of the metrics we monitor internally to see what's happening in our systems. Here's a random grab-bag of some numbers for this month:

  • Web requests: we're processing on average about 225 hits/second through our systems (across all websites) with spikes at busy times of up to 350/second. For comparison -- apparently that's about what Stack Overflow have to deal with. But there's a difference; they're just one site, but for us...
  • That's across about 28,000 websites. Of course the number of hits sites get is very much spread over a long tail distribution -- many of those sites are ones that people set up as part of tutorials (like the excellent Django Girls), so they only get hits from their owners, while on the other hand the busiest websites might be processing 40 hits/second at their peak times
  • By contrast, there are only 10,000 scheduled tasks :-S
  • Our live system currently comprises 51 separate machines on Amazon AWS.

Let us know whether those are the metrics you'd like to see, whether you'd like to see more, or if you think it's completely uninteresting :-)

GDPR

Like every tech business in the world, we spent a lot of time late last year and early this year working on ensuring that we were compliant with the GDPR. This was doubly-important to us -- most companies are "data controllers" in GDPR terminology, which means that they store data about people, but we are also "data processors", which means that we run computers and programs that other people use in their role as data controllers. Or, to make that a bit more concrete -- if you have personal data about people on a website that you host with us, you're a data controller, and you're delegating the data processing to us.

This of course, meant that it was more than twice as much work for us as it was for most people, but we got it all done a week before the deadline :-)

One interesting side-effect of all of this was that we realised that these newsletters sometime say things like "hey, we've added this cool new feature (for paid accounts only)" -- and when they do say something like that, it's not unreasonable to see them as marketing. We had to make super-sure that all marketing messages from us were opt-in only, so we unsubscribed everyone from the newsletter and put up a banner on login so that people would know about it.

That in turn means that this newsletter is going to about ten times fewer people than the one before -- so if you're reading it over email, thanks for choosing to receive it :-) Of course, you can always unsubscribe using the link at the bottom of the message, or from the email settings tab on the Account page.

New modules

Although you can install Python packages on PythonAnywhere yourself, we like to make sure that we have plenty of batteries included.

Everything got updated for the new system image that provides access to Python 3.7, so if you're using that image, you should have the most recent (or at least a very recent) version of everything :-)

New whitelisted sites

Paying PythonAnywhere customers get unrestricted Internet access, but if you're a free PythonAnywhere user, you may have hit problems when writing code that tries to access sites elsewhere on the Internet. We have to restrict you to sites on a whitelist to stop hackers from creating dummy accounts to hide their identities when breaking into other people's websites.

But we really do encourage you to suggest new sites that should be on the whitelist. Our rule is, if it's got an official public API, which means that the site's owners are encouraging automated access to their server, then we'll whitelist it. Just drop us a line with a link to the API docs.

We've added too many sites to list since our last newsletter to list them all -- but please keep them coming!

That's all for now

That's all we've got this time around. We have some big new features in the pipeline, so keep tuned! Maybe we'll even get our next newsletter out in October 2018. Or at least sometime in 2018...

Roberto Alsina: Quick Nikola Feature: document APIs using pdoc

$
0
0

A user asked in the nikola-discuss if there was a way to use Nikola to document APIs. Well, there wasn't and now there is. I took pdoc and wrote a wrapper as a plugin for Nikola.

And now you can just document python modules using it in a couple of minutes.

Here is the documentation for the re module from stdlib as an example.

Yes, the output is not great, and it needs CSS, and many other fixes, but it's easy to improve now that it's there, as long as there is interest.


Dataquest: Understanding Regression Error Metrics

$
0
0
Understanding Regression Error Metrics

Human brains are built to recognize patterns in the world around us. For example, we observe that if we practice our programming everyday, our related skills grow. But how do we precisely describe this relationship to other people? How can we describe how strong this relationship is? Luckily, we can describe relationships between phenomena, such as practice and skill, in terms of formal mathematical estimations called regressions.

Regressions are one of the most commonly used tools in a data scientist's kit. When you learn Python or R, you gain the ability to create regressions in single lines of code without having to deal with the underlying mathematical theory. But this ease can cause us to forget to evaluate our regressions to ensure that they are a sufficient enough representation of our data.

We can plug our data back into our regression equation to see if the predicted output matches corresponding observed value seen in the data. The quality of a regression model is how well its predictions match up against actual values, but how do we actually evaluate quality?

Luckily, smart statisticians have developed error metrics to judge the quality of a model and enable us to compare regresssions against other regressions with different parameters. These metrics are short and useful summaries of the quality of our data. This article will dive into four common regression metrics and discuss their use cases.

There are many types of regression, but this article will focus exclusively on metrics related to the linear regression. The linear regression is the most commonly used model in research and business and is the simplest to understand, so it makes sense to start developing your intuition on how they are assessed. The intuition behind many of the metrics we'll cover here extend to other types of models and their respective metrics.

If you'd like a quick refresher on the linear regression, you can consult this fantastic blog post or the Linear Regression Wiki page.

A primer on linear regression

In the context of regression, models refer to mathematical equations used to describe the relationship between two variables. In general, these models deal with prediction and estimation of values of interest in our data called outputs. Models will look at other aspects of the data called inputs that we believe to affect the outputs, and use them to generate estimated outputs. These inputs and outputs have many names that you may have heard before. Inputs are can also be called independent variables or predictors, while outputs are also known as responses or dependent variables. Simply speaking, models are just functions where the outputs are some function of the inputs.

The linear part of linear regression refers to the fact that a linear regression model is described mathematically in the form:

Understanding Regression Error Metrics

If that looks too mathematical, take solace in that linear thinking is particularly intuitive. If you've ever heard of "practice makes perfect," then you know that more practice means better skills; there is some linear relationship between practice and perfection.

The regression part of linear regression does not refer to some return to a lesser state. Regression here simply refers to the act of estimating the relationship between our inputs and outputs. In particular, regression deals with the modelling of continuous values (think: numbers) as opposed to discrete states (think: categories).

Taken together, a linear regression creates a model that assumes a linear relationship between the inputs and outputs. The higher the inputs are, the higher (or lower, if the relationship was negative) the outputs are.

What adjusts how strong the relationship is and what the direction of this relationship is between the inputs and outputs are our coefficients. The first coefficient without an input is called the intercept, and it adjusts what the model predicts when all your inputs are 0.

We will not delve into how these coefficients are calculated, but know that there exists a method to calculate the optimal coefficients, given which inputs we want to use to predict the output. Given the coefficients, if we plug in values for the inputs, the linear regression will give us an estimate for what the output should be.

As we'll see, these outputs won't always be perfect. Unless our data is a perfectly straight line, our model will not precisely hit all of our data points. One of the reasons for this is the ϵ (named "epsilon") term. This term represents error that comes from sources out of our control, causing the data to deviate slightly from their true position.

Our error metrics will be able to judge the differences between prediction and actual values, but we cannot know how much the error has contributed to the discrepancy. While we cannot ever completely eliminate epsilon, it is useful to retain a term for it in a linear model.

Comparing model predictions against reality

Since our model will produce an output given any input or set of inputs, we can then check these estimated outputs against the actual values that we tried to predict. We call the difference between the actual value and the model's estimate a residual. We can calculate the residual for every point in our data set, and each of these residuals will be of use in assessment. These residuals will play a significant role in judging the usefulness of a model.

If our collection of residuals are small, it implies that the model that produced them does a good job at predicting our output of interest. Conversely, if these residuals are generally large, it implies that model is a poor estimator.

We technically can inspect all of the residuals to judge the model's accuracy, but unsurprisingly, this does not scale if we have thousands or millions of data points. Thus, statisticians have developed summary measurements that take our collection of residuals and condense them into a single value that represents the predictive ability of our model.

There are many of these summary statistics, each with their own advantages and pitfalls. For each, we'll discuss what each statistic represents, their intution and typical use case. We'll cover:

  • Mean Absolute Error
  • Mean Square Error
  • Mean Absolute Percentage Error
  • Mean Percentage Error

Note: Even though you see the word error here, it does not refer to the epsilon term from above! The error described in these metrics refer to the residuals!

Staying rooted in real data

In discussing these error metrics, it is easy to get bogged down by the various acronyms and equations used to describe them. To keep ourselves grounded, we'll use a model that I've created using the Video Game Sales Data Set from Kaggle.

The specifics of the model I've created are shown below.

Understanding Regression Error Metrics

My regression model takes in two inputs (critic score and user score), so it is a multiple variable linear regression. The model took in my data and found that 0.039 and -0.099 were the best coefficients for the inputs. For my model, I chose my intercept to be zero since I'd like to imagine there'd be zero sales for scores of zero. Thus, the intercept term is crossed out. Finally, the error term is crossed out because we do not know its true value in practice. I have shown it because it depicts a more detailed description of what information is encoded in the linear regression equation.

Rationale behind the model

Let's say that I'm a game developer who just created a new game, and I want to know how much money I will make. I don't want to wait, so I developed a model that predicts total global sales (my output) based on an expert critic's judgment of the game and general player judgment (my inputs). If both critics and players love the game, then I should make more money... right? When I actually get the critic and user reviews for my game, I can predict how much glorious money I'll make.

Currently, I don't know if my model is accurate or not, so I need to calculate my error metrics to check if I should perhaps include more inputs or if my model is even any good!

Mean absolute error

The mean absolute error (MAE) is the simplest regression error metric to understand. We'll calculate the residual for every data point, taking only the absolute value of each so that negative and positive residuals do not cancel out. We then take the average of all these residuals. Effectively, MAE describes the typical magnitude of the residuals. If you're unfamiliar with the mean, you can refer back to this article on descriptive statistics. The formal equation is shown below:

Understanding Regression Error Metrics

The picture below is a graphical description of the MAE. The green line represents our model's predictions, and the blue points represent our data.

Understanding Regression Error Metrics

The MAE is also the most intuitive of the metrics since we're just looking at the absolute difference between the data and the model's predictions. Because we use the absolute value of the residual, the MAE does not indicate underperformance or overperformance of the model (whether or not the model under or overshoots actual data). Each residual contributes proportionally to the total amount of error, meaning that larger errors will contribute linearly to the overall error.

Like we've said above, a small MAE suggests the model is great at prediction, while a large MAE suggests that your model may have trouble in certain areas. A MAE of 0 means that your model is a perfect predictor of the ouputs (but this will almost never happen).

While the MAE is easily interpretable, using the absolute value of the residual often is not as desirable as squaring this difference. Depending on how you want your model to treat outliers, or extreme values, in your data, you may want to bring more attention to these outliers or downplay them. The issue of outliers can play a major role in which error metric you use.

Calculating MAE against our model

Calculating MAE is relatively straightforward in Python. In the code below, sales contains a list of all the sales numbers, and X contains a list of tuples of size 2. Each tuple contains the critic score and user score corresponding to the sale in the same index. The lm contains a LinearRegression object from scikit-learn, which I used to create the model itself. This object also contains the coefficients. The predict method takes in inputs and gives the actual prediction based off those inputs.

# Perform the intial fitting to get the LinearRegression object
from sklearn import linear_model
lm = linear_model.LinearRegression()
lm.fit(X, sales)

mae_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mae_sum += abs(sale - prediction)
mae = mae_sum / len(sales)

print(mae)
>>> [ 0.7602603 ]

Our model's MAE is 0.760, which is fairly small given that our data's sales range from 0.01 to about 83 (in millions).

Mean square error

The mean square error (MSE) is just like the MAE, but squares the difference before summing them all instead of using the absolute value. We can see this difference in the equation below.

Understanding Regression Error Metrics

Consequences of the Square Term

Because we are squaring the difference, the MSE will almost always be bigger than the MAE. For this reason, we cannot directly compare the MAE to the MSE. We can only compare our model's error metrics to those of a competing model.

The effect of the square term in the MSE equation is most apparent with the presence of outliers in our data. While each residual in MAE contributes proportionally to the total error, the error grows quadratically in MSE. This ultimately means that outliers in our data will contribute to much higher total error in the MSE than they would the MAE. Similarly, our model will be penalized more for making predictions that differ greatly from the corresponding actual value. This is to say that large differences between actual and predicted are punished more in MSE than in MAE. The following picture graphically demonstrates what an indivdual residual in the MSE might look like.

Understanding Regression Error Metrics

Outliers will produce these exponentially larger differences, and it is our job to judge how we should approach them.

The problem of outliers

Outliers in our data are a constant source of discussion for the data scientists that try to create models. Do we include the outliers in our model creation or do we ignore them? The answer to this question is dependent on the field of study, the data set on hand and the consequences of having errors in the first place.

For example, I know that some video games achieve a superstar status and thus have disproportionately higher earnings. Therefore, it would be foolish of me to ignore these outlier games because they represent a real phenomenon within the data set. I would want to use the MSE to ensure that my model takes these outliers into account more. If I wanted to downplay their significance, I would use the MAE since the outlier residuals won't contribute as much to the total error as MSE.

Ultimately, the choice between is MSE and MAE is application-specific and depends on how you want to treat large errors. Both are still viable error metrics, but will describe different nuances about the prediction errors of your model.

A note on MSE and a close relative

Another error metric you may encounter is the root mean squared error (RMSE). As the name suggests, it is the square root of the MSE. Because the MSE is squared, its units do not match that of the original output. Researchers will often use RMSE to convert the error metric back into similar units, making interpretation easier.

Since the MSE and RMSE both square the residual, they are similarly affected by outliers. The RMSE is analogous to the standard deviation (MSE to variance) and is a measure of how large your residuals are spread out.

Both MAE and MSE can range from 0 to positive infinity, so as both of these measures get higher, it becomes harder to interpret how well your model is performing. Another way we can summarize our collection of residuals is by using percentages so that each prediction is scaled against the value it's supposed to estimate.

Calculating MSE against our model

Like MAE, we'll calculate the MSE for our model. Thankfully, the calculation is just as simple as MAE.

mse_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mse_sum += (sale - prediction)**2
mse = mse_sum / len(sales)

print(mse)
>>> [ 3.53926581 ]

With the MSE, we would expect it to be much larger than MAE due to the influence of outliers. We find that this is the case: the MSE is an order of magnitude higher than the MAE. The corresponding RMSE would be about 1.88, indicating that our model misses actual sale values by about $1.8M.

Mean absolute percentage error

The mean absolute percentage error (MAPE) is the percentage equivalent of MAE. The equation looks just like that of MAE, but with adjustments to convert everything into percentages.

Understanding Regression Error Metrics

Just as MAE is the average magnitude of error produced by your model, the MAPE is how far the model's predictions are off from their corresponding outputs on average. Like MAE, MAPE also has a clear interpretation since percentages are easier for people to conceptualize. Both MAPE and MAE are robust to the effects of outliers thanks to the use of absolute value.

Understanding Regression Error Metrics

However for all of its advantages, we are more limited in using MAPE than we are MAE. Many of MAPE's weaknesses actually stem from use division operation. Now that we have to scale everything by the actual value, MAPE is undefined for data points where the value is 0. Similarly, the MAPE can grow unexpectedly large if the actual values are exceptionally small themselves. Finally, the MAPE is biased towards predictions that are systematically less than the actual values themselves. That is to say, MAPE will be lower when the prediction is lower than the actual compared to a prediction that is higher by the same amount. The quick calculation below demonstrates this point.

Understanding Regression Error Metrics

We have a measure similar to MAPE in the form of the mean percentage error. While the absolute value in MAPE eliminates any negative values, the mean percentage error incorporates both positive and negative errors into its calculation.

Calculating MAPE against our model

mape_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mape_sum += (abs((sale - prediction))/sale)
mape = mape_sum/len(sales)

print(mape)
>>> [ 5.68377867 ]

We know for sure that there are no data points for which there are zero sales, so we are safe to use MAPE. Remember that we must interpret it in terms of percentage points. MAPE states that our model's predictions are, on average, 5.6% off from actual value.

Mean percentage error

The mean percentage error (MPE) equation is exactly like that of MAPE. The only difference is that it lacks the absolute value operation.

Understanding Regression Error Metrics

Even though the MPE lacks the absolute value operation, it is actually its absence that makes MPE useful. Since positive and negative errors will cancel out, we cannot make any statements about how well the model predictions perform overall. However, if there are more negative or positive errors, this bias will show up in the MPE. Unlike MAE and MAPE, MPE is useful to us because it allows us to see if our model systematically underestimates (more negative error) or overestimates (positive error).

Understanding Regression Error Metrics

If you're going to use a relative measure of error like MAPE or MPE rather than an absolute measure of error like MAE or MSE, you'll most likely use MAPE. MAPE has the advantage of being easily interpretable, but you must be wary of data that will work against the calculation (i.e. zeroes). You can't use MPE in the same way as MAPE, but it can tell you about systematic errors that your model makes.

Calculating MPE against our model

mpe_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mpe_sum += ((sale - prediction)/sale)
mpe = mpe_sum/len(sales)

print(mpe)
>>> [-4.77081497]

All the other error metrics have suggested to us that, in general, the model did a fair job at predicting sales based off of critic and user score. However, the MPE indicates to us that it actually systematically underestimates the sales. Knowing this aspect about our model is helpful to us since it allows us to look back at the data and reiterate on which inputs to include that may improve our metrics.

Overall, I would say that my assumptions in predicting sales was a good start. The error metrics revealed trends that would have been unclear or unseen otherwise.

Conclusion

We've covered a lot of ground with the four summary statistics, but remembering them all correctly can be confusing. The table below will give a quick summary of the acronyms and their basic characteristics.

AcroynmFull NameResidual Operation?Robust To Outliers?
MAEMean Absolute ErrorAbsolute ValueYes
MSEMean Squared ErrorSquareNo
RMSERoot Mean Squared ErrorSquareNo
MAPEMean Absolute Percentage ErrorAbsolute ValueYes
MPEMean Percentage ErrorN/AYes

All of the above measures deal directly with the residuals produced by our model. For each of them, we use the magnitude of the metric to decide if the model is performing well. Small error metric values point to good predictive abillity, while large values suggest otherwise. That being said, it's important to consider the nature of your data set in choosing which metric to present. Outliers may change your choice in metric, depending on if you'd like to give them more significance to the total error. Some fields may just be more prone to outliers, while others are may not see them so much.

In any field though, having a good idea of what metrics are available to you is always important. We've covered a few of the most common error metrics used, but there are others that also see use. The metrics we covered use the mean of the residuals, but the median residual also sees use. As you learn other types of models for your data, remember that intution we developed behind our metrics and apply them as needed.

Further Resources

If you'd like to explore the linear regression more, Dataquest offers an excellent course on its use and application! We used scikit-learn to apply the error metrics in this article, so you can read the docs to get a better look at how to use them!

Mike Driscoll: Python 101: Episode #26 – lambdas

Stack Abuse: Creating a Neural Network from Scratch in Python

$
0
0

Introduction

Creating a Neural Network from Scratch in Python

Have you ever wondered how chatbots like Siri, Alexa, and Cortona are able to respond to user queries? Or how the autonomous cars are able to drive themselves without any human help? All of these fancy products have one thing in common: Artificial Intelligence (AI). It is the AI which enables them to perform such tasks without being supervised or controlled by a human. But the question remains: "What is AI?" A simple answer to this question is: "AI is a combination of complex algorithms from the various mathematical domains such as Algebra, Calculus, and Probability and Statistics."

In this article, we will study a simple artificial neural network, which is one of the main building block of artificial intelligence. Different variants of an Artificial Neural Network exists, dedicated to solving a particular problem. For instance Convolutional Neural Networks are commonly used for Image Recognition problems while Recurrent Neural Networks are used to solve sequence problems.

There are many deep learning libraries that can be used to create a neural network in a single line of code. However, if you really want to understand the in-depth working of a neural network, I suggest you learn how to code it from scratch in any programming language. Performing this exercise will really clear up many of the concepts for you. And this is exactly what we will do in this article.

The Problem

Since this is an introductory article, the problem that we are going to solve is pretty simple. Suppose we have some information about obesity, smoking habits, and exercise habits of five people. We also know whether these people are diabetic or not. Our dataset looks like this:

PersonSmokingObesityExerciseDiabetic
Person 10101
Person 20010
Person 31000
Person 41101
Person 51111

In the above table, we have five columns: Person, Smoking, Obesity, Exercise, and Diabetic. Here 1 refers to true and 0 refers to false. For instance, the first person has values of 0, 1, 0 which means that the person doesn't smoke, is obese, and doesn't exercise. The person is also diabetic.

It is clearly evident from the dataset that a person's obesity is indicative of him being diabetic. Our task is to create a neural network that is able to predict whether an unknown person is diabetic or not given data about his exercise habits, obesity, and smoking habits. This is a type of supervised learning problem where we are given inputs and corresponding correct outputs and our task is to find the mapping between the inputs and the outputs.

Note: This is just a fictional dataset, in real life, obese people are not necessarily always diabetic.

The Solution

We will create a very simple neural network with one input layer and one output layer. Before writing any actual code, let's first let's see how our neural network will execute, in theory.

Neural Network Theory

A neural network is a supervised learning algorithm which means that we provide it the input data containing the independent variables and the output data that contains the dependent variable. For instance, in our example our independent variables are smoking, obesity and exercise. The dependent variable is whether a person is diabetic or not.

In the beginning, the neural network makes some random predictions, these predictions are matched with the correct output and the error or the difference between the predicted values and the actual values is calculated. The function that finds the difference between the actual value and the propagated values is called the cost function. The cost here refers to the error. Our objective is to minimize the cost function. Training a neural network basically refers to minimizing the cost function. We will see how we can perform this task.

The neural network that we are going to create has the following visual representation.

Creating a Neural Network from Scratch in Python

A neural network executes in two steps: Feed Forward and Back Propagation. We will discuss both of these steps in details.

Feed Forward

In the feed-forward part of a neural network, predictions are made based on the values in the input nodes and the weights. If you look at the neural network in the above figure, you will see that we have three features in the dataset: smoking, obesity, and exercise, therefore we have three nodes in the first layer, also known as the input layer. We have replaced our feature names with the variable x, for generality in the figure above.

The weights of a neural network are basically the strings that we have to adjust in order to be able to correctly predict our output. For now, just remember that for each input feature, we have one weight.

The following are the steps that execute during the feedforward phase of a neural network:

Step 1: (Calculate the dot product between inputs and weights)

The nodes in the input layer are connected with the output layer via three weight parameters. In the output layer, the values in the input nodes are multiplied with their corresponding weights and are added together. Finally, the bias term is added to the sum. The b in the above figure refers to the bias term.

The bias term is very important here. Suppose if we have a person who doesn't smoke, is not obese, and doesn't exercise, the sum of the products of input nodes and weights will be zero. In that case, the output will always be zero no matter how much we train the algorithms. Therefore, in order to be able to make predictions, even if we do not have any non-zero information about the person, we need a bias term. The bias term is necessary to make a robust neural network.

Mathematically, in step 1, we perform the following calculation:

$$ X.W = x1w1 + x2w2 + x3w3 + b
$$

Step 2: (Pass the result from step 1 through an activation function)

The result from Step 1 can be a set of any values. However, in our output we have the values in the form of 1 and 0. We want our output to be in the same format. To do so we need an activation function, which squashes input values between 1 and 0. One such activation function is the sigmoid function.

The sigmoid function returns 0.5 when the input is 0. It returns a value close to 1 if the input is a large positive number. In case of negative input, the sigmoid function outputs a value close to zero.

Mathematically, the sigmoid function can be represented as:

$$ \theta_{X.W} = \frac{\mathrm{1} }{\mathrm{1} + e^{-X.W} } $$

Let us try to plot the sigmoid function:

input = np.linspace(-10, 10, 100)

def sigmoid(x):  
    return 1/(1+np.exp(-x))

from matplotlib import pyplot as plt  
plt.plot(input, sigmoid(input), c="r")  

In the script above, we first randomly generate 100 linearly-spaced points between -10 and 10. To do so, we use the linspace method from the NumPy library. Next, we define the sigmoid function. Finally, we use the matplotlib library to plot the input values against the values returned by the sigmoid function. The output looks likes this:

Creating a Neural Network from Scratch in Python

You can see that if the input is a negative number, the output is close to zero, otherwise if the input is positive the output is close to 1. However, the output is always between 0 and 1. This is what we want.

This sums up the feedforward part of our neural network. It is pretty straightforward. First we have to find the dot product of the input feature matrix with the weight matrix. Next, pass the result from the output through an activation function, which in this case is the sigmoid function. The result of the activation function is basically the predicted output for the input features.

Back Propagation

In the beginning, before you do any training, the neural network makes random predictions which are far from correct.

The principle behind the working of a neural network is simple. We start by letting the network make random predictions about the output. We then compare the predicted output of the neural network with the actual output. Next, we fine-tune our weights and the bias in such a manner that our predicted output becomes closer to the actual output, which is basically known as "training the neural network".

In the back propagation section, we train our algorithm. Let's take a look at the steps involved in the back propagation section.

Step 1: (Calculating the cost)

The first step in the back propagation section is to find the "cost" of the predictions. The cost of the prediction can simply be calculated by finding the difference between the predicted output and the actual output. The higher the difference, the higher the cost will be.

There are several other ways to find the cost, but we will use the mean squared error cost function. A cost function is simply the function that finds the cost of the given predictions.

The mean squared error cost function can be mathematically represented as:

$$ MSE =
\frac{\mathrm{1} }{\mathrm{n}} \sum\nolimits_{i=1}^{n} (predicted - observed)^{2} $$

Here n is the number of observations.

Step 2: (Minimizing the cost)

Our ultimate purpose is to fine-tune the knobs of our neural network in such a way that the cost is minimized. If your look at our neural network, you'll notice that we can only control the weights and the bias. Everything else is beyond our control. We cannot control the inputs, we cannot control the dot products, and we cannot manipulate the sigmoid function.

In order to minimize the cost, we need to find the weight and bias values for which the cost function returns the smallest value possible. The smaller the cost, the more correct our predictions are.

This is an optimization problem where we have to find the function minima.

To find the minima of a function, we can use the gradient decent algorithm. The gradient decent algorithm can be mathematically represented as follows:

$$ repeat \ until \ convergence: \begin{Bmatrix} w_j := w_j - \alpha \frac{\partial }{\partial w_j} J(w_0,w_1 ....... w_n) \end{Bmatrix} ............. (1) $$

Here in the above equation, J is the cost function. Basically what the above equation says is: find the partial derivative of the cost function with respect to each weight and bias and subtract the result from the existing weight values to get the new weight values.

The derivative of a function gives us its slope at any given point. To find if the cost increases or decreases, given the weight value, we can find the derivative of the function at that particular weight value. If the cost increases with the increase in weight, the derivative will return a positive value which will then be subtracted from the existing value.

On the other hand, if the cost is decreasing with an increase in weight, a negative value will be returned, which will be added to the existing weight value since negative into negative is positive.

In Equation 1, we can see there is an alpha symbol, which is multiplied by the gradient. This is called the learning rate. The learning rate defines how fast our algorithm learns. For more details about how learning rate can be defined, check out this article .

We need to repeat the execution of Equation 1 for all the weights and bias until the cost is minimized to the desirable level. In other words, we need to keep executing Equation 1 until we get such values for bias and weights, for which the cost function returns a value close to zero.

And that's pretty much it. Now is the time to implement what we have studied so far. We will create a simple neural network with one input and one output layer in Python.

Neural Network Implementation in Python

Let's first create our feature set and the corresponding labels. Execute the following script:

import numpy as np  
feature_set = np.array([[0,1,0],[0,0,1],[1,0,0],[1,1,0],[1,1,1]])  
labels = np.array([[1,0,0,1,1]])  
labels = labels.reshape(5,1)  

In the above script, we create our feature set. It contains five records. Similarly, we created a labels set which contains corresponding labels for each record in the feature set. The labels are the answers we're trying to predict with the neural network.

The next step is to define hyper parameters for our neural network. Execute the following script to do so:

np.random.seed(42)  
weights = np.random.rand(3,1)  
bias = np.random.rand(1)  
lr = 0.05  

In the script above we used the random.seed function so that we can get the same random values whenever the script is executed.

In the next step, we initialize our weights with normally distributed random numbers. Since we have three features in the input, we have a vector of three weights. We then initialize the bias value with another random number. Finally, we set the learning rate to 0.05.

Next, we need to define our activation function and its derivative (I'll explain in a moment why we need to find the derivative of the activation). Our activation function is the sigmoid function, which we covered earlier.

The following Python script creates this function:

def sigmoid(x):  
    return 1/(1+np.exp(-x))

And the method that calculates the derivative of the sigmoid function is defined as follows:

def sigmoid_der(x):  
    return sigmoid(x)*(1-sigmoid(x))

The derivative of sigmoid function is simply sigmoid(x) * sigmoid(1-x).

Now we are ready to train our neural network that will be able to predict whether a person is obese or not.

Look at the following script:

for epoch in range(20000):  
    inputs = feature_set

    # feedforward step1
    XW = np.dot(feature_set, weights) + bias

    #feedforward step2
    z = sigmoid(XW)


    # backpropagation step 1
    error = z - labels

    print(error.sum())

    # backpropagation step 2
    dcost_dpred = error
    dpred_dz = sigmoid_der(z)

    z_delta = dcost_dpred * dpred_dz

    inputs = feature_set.T
    weights -= lr * np.dot(inputs, z_delta)

    for num in z_delta:
        bias -= lr * num

Don't get intimidated by this code. I will explain it line by line.

In the first step, we define the number of epochs. An epoch is basically the number of times we want to train the algorithm on our data. We will train the algorithm on our data 20,000 times. I have tested this number and found that the error is pretty much minimized after 20,000 iterations. You can try with a different number. The ultimate goal is to minimize the error.

Next we store the values from the feature_set to the input variable. We then execute the following line:

XW = np.dot(feature_set, weights) + bias  

Here we find the dot product of the input and the weight vector and add bias to it. This is Step 1 of the feedforward section.

In this line:

z = sigmoid(XW)  

We pass the dot product through the sigmoid activation function, as explained in Step 2 of the feedforward section. This completes the feed forward part of our algorithm.

Now is the time to start backpropagation. The variable z contains the predicted outputs. The first step of the backpropagation is to find the error. We do so in the following line:

error = z - labels  

We then print the error on the screen.

Now is the time to execute Step 2 of backpropagation, which is the gist of this code.

We know that our cost function is:

$$ MSE = \frac{\mathrm{1} }{\mathrm{n}} \sum\nolimits_{i=1}^{n} (predicted - observed)^{2}
$$

We need to differentiate this function with respect to each weight. We will use the chain rule of differentiation for this purpose. Let's suppose "d_cost" is the derivate of our cost function with respect to weight "w", we can use chain rule to find this derivative, as shown below:

$$ \frac {d\_cost}{dw} = \frac {d\_cost}{d\_pred} \, \frac {d\_pred}{dz}, \frac {dz}{dw} $$

Here,

$$ \frac {d\_cost}{d\_pred} $$

can be calculated as:

$$ 2 (predicted - observed) $$

Here, 2 is constant and therefore can be ignored. This is basically the error which we already calculated. In the code, you can see the line:

dcost_dpred = error # ........ (2)  

Next we have to find:

$$ \frac {d\_pred}{dz} $$

Here "d_pred" is simply the sigmoid function and we have differentiated it with respect to input dot product "z". In the script, this is defined as:

dpred_dz = sigmoid_der(z) # ......... (3)  

Finally, we have to find:

$$ \frac {d\_z}{dw} $$

We know that:

$$ z = x1w1 + x2w2 + x3w3 + b $$

Therefore, derivative with respect to any weight is simply the corresponding input. Hence, our final derivative of the cost function with respect to any weight is:

slope = input x dcost_dpred x dpred_dz  

Take a look at the following three lines:

z_delta = dcost_dpred * dpred_dz  
inputs = feature_set.T  
weights -= lr * np.dot(inputs, z_delta)  

Here we have the z_delta variable, which contains the product of dcost_dpred and dpred_dz. Instead of looping through each record and multiplying the input with corresponding z_delta, we take the transpose of the input feature matrix and multiply it with the z_delta. Finally, we multiply the learning rate variable lr with the derivative to increase the speed of convergence.

We then looped through each derivative value and update our bias values, as well as shown in this script:

Once the loop starts, you will see that the total error starts decreasing as shown below:

0.001700995120272485  
0.001700910187124885  
0.0017008252625468727  
0.0017007403465365955  
0.00170065543909367  
0.0017005705402162556  
0.0017004856499031988  
0.0017004007681529695  
0.0017003158949647542  
0.0017002310303364868  
0.0017001461742678046  
0.0017000613267565308  
0.0016999764878018585  
0.0016998916574025129  
0.00169980683555691  
0.0016997220222637836  
0.0016996372175222992  
0.0016995524213307602  
0.0016994676336875778  
0.0016993828545920908  
0.0016992980840424554  
0.0016992133220379794  
0.0016991285685766487  
0.0016990438236577712  
0.0016989590872797753  
0.0016988743594415108  
0.0016987896401412066  
0.0016987049293782815  

You can see that error is extremely small at the end of the training of our neural network. At this point of time our weights and bias will have values that can be used to detect whether a person is diabetic or not, based on his smoking habits, obesity, and exercise habits.

You can now try and predict the value of a single instance. Let's suppose we have a record of a patient that comes in who smokes, is not obese, and doesn't exercise. Let's find if he is likely to be diabetic or not. The input feature will look like this: [1,0,0].

Execute the following script:

single_point = np.array([1,0,0])  
result = sigmoid(np.dot(single_point, weights) + bias)  
print(result)  

In the output you will see:

[0.00707584]

You can see that the person is likely not diabetic since the value is much closer to 0 than 1.

Now let's test another person who doesn't, smoke, is obese, and doesn't exercises. The input feature vector will be [0,1,0]. Execute this script:

single_point = np.array([0,1,0])  
result = sigmoid(np.dot(single_point, weights) + bias)  
print(result)  

In the output you will see the following value:

[0.99837029]

You can see that the value is very close to 1, which is likely due to the person's obesity.

Resources

Want to learn more about creating neural networks to solve complex problems? If so, try checking out some other resources, like this online course:

Deep Learning A-Z: Hands-On Artificial Neural Networks

It covers neural networks in much more detail, including convolutional neural networks, recurrent neural networks, and much more.

Conclusion

In this article we created a very simple neural network with one input and one output layer from scratch in Python. Such a neural network is simply called a perceptron. A perceptron is able to classify linearly separable data. Linearly separable data is the type of data which can be separated by a hyperplane in n-dimensional space.

Real-word artificial neural networks are much more complex, powerful, and consist of multiple hidden layers and multiple nodes in the hidden layer. Such neural networks are able to identify non-linear real decision boundaries. I will explain how to create a multi-layer neural network from scratch in Python in an upcoming article.

PyCon: Pycon 2019 Call for Proposals is Open!

$
0
0
The time is upon us again! PyCon 2019’s Call for Proposalshas officially opened for talks, tutorials, posters, education summit presentations, as well as the hatchery program PyCon Charlas. PyCon is made by you, so we need you to share what you’re working on, how you’re working on it, what you’ve learned, what you’re learning, and so much more.

Please make note of important deadlines for submissions:
  • Tutorial proposals are due November 26, 2018.
  • Talk, Charlas, Poster, and Education Summit proposals are due January 3, 2019.

Who should write a proposal? Everyone!

If you’re reading this post, you should write a proposal. PyCon is about uniting and building the Python community, and we won’t advance as an open community if we’re not open with each other about what we’ve learned throughout our time in it. It isn’t about being the smartest one in the room, so we don’t just pick all of the expert talks. It’s about helping everyone move together. “A rising tide lifts all boats,” if you will.

We need beginner, intermediate, and advanced proposals on all sorts of topics. We also need beginner, intermediate, and advanced speakers to give said presentations. You don’t need to be a 20 year veteran who has spoken at dozens of conferences. On all fronts, we need all types of people. That’s what this community is comprised of, so that’s what this conference’s schedule should be made from.

If you can speak Spanish, why not submit a proposal for PyCon Charlas? If you speak Spanish as a second, third or twelfth language, please do not hesitate to participate! The PyCon Charlas call for proposals opens with the rest of the CFP.

When should you write your proposal? As soon as possible!

What we need now is for your submissions to start rolling in. We review proposals as soon as they’re entered, maximizing your time in front of the program committee and before they begin voting to determining the schedule. While we accept proposals right up to the deadline, the longer your proposal has been available for review, the better we can help you make it. That extra help goes a long way when you consider the large volume of proposals we anticipate receiving.

For PyCon 2017, we received 705 talk proposals, which makes for a 14% acceptance rate. The tutorial acceptance rate was at 29%, with 107 submissions.

Who can help you with your proposal? A lot of people!

Outside of our program committee, a great source of assistance with proposals comes from your local community. User groups around the world have had sessions where people bring ideas to the table and walk away with a full fledged proposal. These sessions  are especially helpful if you’re new to the process, and if you’re experienced with the process, it’s a great way for you to reach out and help people level up. We’ll be sure to share these events as we find out about them, and be sure to tell us your plans if you want to host a proposal event of your own!

We’re again going to provide a mechanism to connect willing mentors and those seeking assistance through our site, helping not only with the brainstorming process but about the proposal, slides, and presentation itself.  Our goal is to improve the mentorship program by connecting those interested much sooner. Read on to find out more and checkout out the “Mentoring” section of https://us.pycon.org/2019/speaking/talks/.

Where should you submit your proposal? In your dashboard!

After you have created an account at https://us.pycon.org/2019/account/signup/,  you’ll want to create a speaker profile in your dashboard. While there, enter some details about yourself and check the various boxes about giving or receiving mentorship, as well as grant needs. Like proposals, you can come back and edit this later.

After that’s done, clicking on the “Submit a new proposal” button gives you the choice of proposal type, and from there you enter your proposal. We’ve provided some guidelines on the types of proposals you can submit, so please be sure to check out the following pages for more information:



We look forward to seeing all of your proposals in the coming months!
________________________________________________________________________
*Note: Main content is from post written by Brian Curtin for 2018 launch

Real Python: Color Spaces and How to Use Them With OpenCV and Python

$
0
0

It may be the era of deep learning and big data, where complex algorithms analyze images by being shown millions of them, but color spaces are still surprisingly useful for image analysis. Simple methods can still be powerful.

In this article, you will learn how to simply segment an object from an image based on color in Python using OpenCV. A popular computer vision library written in C/C++ with bindings for Python, OpenCV provides easy ways of manipulating color spaces.

While you don’t need to be already familiar with OpenCV or the other helper packages used in this article, it is assumed that you have at least a basic understanding of coding in Python.

Free Bonus:Click here to get the Python Face Detection & OpenCV Examples Mini-Guide that shows you practical code examples of real-world Python computer vision techniques.

What Are Color Spaces?

In the most common color space, RGB (Red Green Blue), colors are represented in terms of their red, green, and blue components. In more technical terms, RGB describes a color as a tuple of three components. Each component can take a value between 0 and 255, where the tuple (0, 0, 0) represents black and (255, 255, 255) represents white.

RGB is considered an “additive” color space, and colors can be imagined as being produced from shining quantities of red, blue, and green light onto a black background.

Here are a few more examples of colors in RGB:

ColorRGB value
Red255, 0, 0
Orange255, 128, 0
Pink255, 153, 255

RGB is one of the five major color space models, each of which has many offshoots. There are so many color spaces because different color spaces are useful for different purposes.

In the printing world, CMYK is useful because it describes the color combinations required to produce a color from a white background. While the 0 tuple in RGB is black, in CMYK the 0 tuple is white. Our printers contain ink canisters of cyan, magenta, yellow, and black.

In certain types of medical fields, glass slides mounted with stained tissue samples are scanned and saved as images. They can be analyzed in HED space, a representation of the saturations of the stain types—hematoxylin, eosin, and DAB—applied to the original tissue.

HSV and HSL are descriptions of hue, saturation, and brightness/luminance, which are particularly useful for identifying contrast in images. These color spaces are frequently used in color selection tools in software and for web design.

In reality, color is a continuous phenomenon, meaning that there are an infinite number of colors. Color spaces, however, represent color through discrete structures (a fixed number of whole number integer values), which is acceptable since the human eye and perception are also limited. Color spaces are fully able to represent all the colors we are able to distinguish between.

Now that we understand the concept of color spaces, we can go on to use them in OpenCV.

Simple Segmentation Using Color Spaces

To demonstrate the color space segmentation technique, we’ve provided a small dataset of images of clownfish in the Real Python materials repository here for you to download and play with. Clownfish are easily identifiable by their bright orange color, so they’re a good candidate for segmentation. Let’s see how well we can find Nemo in an image.

The key Python packages you’ll need to follow along are NumPy, the foremost package for scientific computing in Python, Matplotlib, a plotting library, and of course OpenCV. This articles uses OpenCV 3.2.0, NumPy 1.12.1, and Matplotlib 2.0.2. Slightly different versions won’t make a significant difference in terms of following along and grasping the concepts.

If you are not familiar with NumPy or Matplotlib, you can read about them in the official NumPy guide and Brad Solomon’s excellent article on Matplotlib.

Color Spaces and Reading Images in OpenCV

First, you will need to set up your environment. This article will assume you have Python 3.x installed on your system. Note that while the current version of OpenCV is 3.x, the name of the package to import is still cv2:

>>>importcv2

If you haven’t previously installed OpenCV on your computer, the import will fail until you do that first. You can find a user-friendly tutorial for installing on different operating systems here, as well as OpenCV’s own installation guide. Once you’ve successfully imported OpenCV, you can look at all the color space conversions OpenCV provides, and you can save them all into a variable:

>>>flags=[iforiindir(cv2)ifi.startswith('COLOR_')]

The list and number of flags may vary slightly depending on your version of OpenCV, but regardless, there will be a lot! See how many flags you have available:

>>>len(flags)258>>>flags[40]'COLOR_BGR2RGB'

The first characters after COLOR_ indicate the origin color space, and the characters after the 2 are the target color space. This flag represents a conversion from BGR (Blue, Green, Red) to RGB. As you can see, the two color spaces are very similar, with only the first and last channels swapped.

You will need matplotlib.pyplot for viewing the images, and NumPy for some image manipulation. If you do not already have Matplotlib or NumPy installed, you will need to pip3 install matplotlib and pip3 install numpy before attempting the imports:

>>>importmatplotlib.pyplotasplt>>>importnumpyasnp

Now you are ready to load and examine an image. Note that if you are working from the command line or terminal, your images will appear in a pop-up window. If you are working in a Jupyter notebook or something similar, they will simply be displayed below. Regardless of your setup, you should see the image generated by the show() command:

>>>nemo=cv2.imread('./images/nemo0.jpg')>>>plt.imshow(nemo)>>>plt.show()

OpenCV uses BGR by default

Hey, Nemo…or Dory? You’ll notice that it looks like the blue and red channels have been mixed up. In fact, OpenCV by default reads images in BGR format. You can use the cvtColor(image, flag) and the flag we looked at above to fix this:

>>>nemo=cv2.cvtColor(nemo,cv2.COLOR_BGR2RGB)>>>plt.imshow(nemo)>>>plt.show()

BGR to RGB

Now Nemo looks much more like himself.

Visualizing Nemo in RGB Color Space

HSV is a good choice of color space for segmenting by color, but to see why, let’s compare the image in both RGB and HSV color spaces by visualizing the color distribution of its pixels. A 3D plot shows this quite nicely, with each axis representing one of the channels in the color space. If you want to know how to make a 3D plot, view the collapsed section:

To make the plot, you will need a few more Matplotlib libraries:

>>>frommpl_toolkits.mplot3dimportAxes3D>>>frommatplotlibimportcm>>>frommatplotlibimportcolors

Those libraries provide the functionalities you need for the plot. You want to place each pixel in its location based on its components and color it by its color. OpenCV split() is very handy here; it splits an image into its component channels. These few lines of code split the image and set up the 3D plot:

>>>r,g,b=cv2.split(nemo)>>>fig=plt.figure()>>>axis=fig.add_subplot(1,1,1,projection="3d")

Now that you have set up the plot, you need to set up the pixel colors. In order to color each pixel according to its true color, there’s a bit of reshaping and normalization required. It looks messy, but essentially you need the colors corresponding to every pixel in the image to be flattened into a list and normalized, so that they can be passed to the facecolors parameter of Matplotlib scatter().

Normalizing just means condensing the range of colors from 0-255 to 0-1 as required for the facecolors parameter. Lastly, facecolors wants a list, not an NumPy array:

>>>pixel_colors=nemo.reshape((np.shape(nemo)[0]*np.shape(nemo)[1],3))>>>norm=colors.Normalize(vmin=-1.,vmax=1.)>>>norm.autoscale(pixel_colors)>>>pixel_colors=norm(pixel_colors).tolist()

Now we have all the components ready for plotting: the pixel positions for each axis and their corresponding colors, in the format facecolors expects. You can build the scatter plot and view it:

>>>axis.scatter(r.flatten(),g.flatten(),b.flatten(),facecolors=colours,marker=".")>>>axis.set_xlabel("Red")>>>axis.set_ylabel("Green")>>>axis.set_zlabel("Blue")>>>plt.show()

Here is the colored scatter plot for the Nemo image in RGB:

3D scatter plot of image in RGB

From this plot, you can see that the orange parts of the image span across almost the entire range of red, green, and blue values. Since parts of Nemo stretch over the whole plot, segmenting Nemo out in RGB space based on ranges of RGB values would not be easy.

Visualizing Nemo in HSV Color Space

We saw Nemo in RGB space, so now let’s view him in HSV space and compare.

As mentioned briefly above, HSV stands for Hue, Saturation, and Value (or brightness), and is a cylindrical color space. The colors, or hues, are modeled as an angular dimension rotating around a central, vertical axis, which represents the value channel. Values go from dark (0 at the bottom) to light at the top. The third axis, saturation, defines the shades of hue from least saturated, at the vertical axis, to most saturated furthest away from the center:

HSV color space cylinderImage: Wikipedia

To convert an image from RGB to HSV, you can use cvtColor():

>>>hsv_nemo=cv2.cvtColor(nemo,cv2.COLOR_RGB2HSV)

Now hsv_nemo stores the representation of Nemo in HSV. Using the same technique as above, we can look at a plot of the image in HSV, generated by the collapsed section below:

The code to show the image in HSV is the same as for RGB. Note that you use the same pixel_colors variable for coloring the pixels, since Matplotlib expects the values to be in RGB:

>>>h,s,v=cv2.split(hsv_nemo)>>>fig=plt.figure()>>>axis=fig.add_subplot(1,1,1,projection="3d")>>>axis.scatter(h.flatten(),s.flatten(),v.flatten(),facecolors=pixel_colors,marker=".")>>>axis.set_xlabel("Hue")>>>axis.set_ylabel("Saturation")>>>axis.set_zlabel("Value")>>>plt.show()

3D scatter plot of image in HSV

In HSV space, Nemo’s oranges are much more localized and visually separable. The saturation and value of the oranges do vary, but they are mostly located within a small range along the hue axis. This is the key point that can be leveraged for segmentation.

Picking Out a Range

Let’s threshold Nemo just based on a simple range of oranges. You can choose the range by eyeballing the plot above or using a color picking app online such as this RGB to HSV tool. The swatches chosen here are a light orange and a darker orange that is almost red:

>>>light_orange=(1,190,200)>>>dark_orange=(18,255,255)

If you want to use Python to display the colors you chose, click on the collapsed section:

A simple way to display the colors in Python is to make small square images of the desired color and plot them in Matplotlib. Matplotlib only interprets colors in RGB, but handy conversion functions are provided for the major color spaces so that we can plot images in other color spaces:

>>>frommatplotlib.colorsimporthsv_to_rgb

Then, build the small 10x10x3 squares, filled with the respective color. You can use NumPy to easily fill the squares with the color:

>>>lo_square=np.full((10,10,3),light_orange,dtype=np.uint8)/255.0>>>do_square=np.full((10,10,3),dark_orange,dtype=np.uint8)/255.0

Finally, you can plot them together by converting them to RGB for viewing:

>>>plt.subplot(1,2,1)>>>plt.imshow(hsv_to_rgb(do_square))>>>plt.subplot(1,2,2)>>>plt.imshow(hsv_to_rgb(lo_square))>>>plt.show()

That produces these images, filled with the chosen colors:

The light and dark orange range

Once you get a decent color range, you can use cv2.inRange() to try to threshold Nemo. inRange() takes three parameters: the image, the lower range, and the higher range. It returns a binary mask (an ndarray of 1s and 0s) the size of the image where values of 1 indicate values within the range, and zero values indicate values outside:

>>>mask=cv2.inRange(hsv_nemo,light_orange,dark_orange)

To impose the mask on top of the original image, you can use cv2.bitwise_and(), which keeps every pixel in the given image if the corresponding value in the mask is 1:

>>>result=cv2.bitwise_and(nemo,nemo,mask=mask)

To see what that did exactly, let’s view both the mask and the original image with the mask on top:

>>>plt.subplot(1,2,1)>>>plt.imshow(mask,cmap="gray")>>>plt.subplot(1,2,2)>>>plt.imshow(result)>>>plt.show()

Mask and original with mask imposed

There you have it! This has already done a decent job of capturing the orange parts of the fish. The only problem is that Nemo also has white stripes… Fortunately, adding a second mask that looks for whites is very similar to what you did already with the oranges:

>>>light_white=(0,0,200)>>>dark_white=(145,60,255)

Once you’ve specified a color range, you can look at the colors you’ve chosen:

White range

To display the whites, you can take the same approach as we did previously with the oranges:

>>>lw_square=np.full((10,10,3),light_white,dtype=np.uint8)/255.0>>>dw_square=np.full((10,10,3),dark_white,dtype=np.uint8)/255.0>>>plt.subplot(1,2,1)>>>plt.imshow(hsv_to_rgb(lw_square))>>>plt.subplot(1,2,2)>>>plt.imshow(hsv_to_rgb(dw_square))>>>plt.show()

The upper range I’ve chosen here is a very blue white, because the white does have tinges of blue in the shadows. Let’s create a second mask and see if it captures Nemo’s stripes. You can build a second mask the same way as you did the first:

>>>mask_white=cv2.inRange(hsv_nemo,light_white,dark_white)>>>result_white=cv2.bitwise_and(nemo,nemo,mask=mask_white)>>>plt.subplot(1,2,1)>>>plt.imshow(mask_white,cmap="gray")>>>plt.subplot(1,2,2)>>>plt.imshow(result_white)>>>plt.show()

Mask and original for white stripes

Not bad! Now you can combine the masks. Adding the two masks together results in 1 values wherever there is orange or white, which is exactly what is needed. Let’s add the masks together and plot the results:

>>>final_mask=mask+mask_white>>>final_result=cv2.bitwise_and(nemo,nemo,mask=final_mask)>>>plt.subplot(1,2,1)>>>plt.imshow(final_mask,cmap="gray")>>>plt.subplot(1,2,2)>>>plt.imshow(final_result)>>>plt.show()

Final combined mask and original

Essentially, you have a rough segmentation of Nemo in HSV color space. You’ll notice there are a few stray pixels along the segmentation border, and if you like, you can use a Gaussian blur to tidy up the small false detections.

A Gaussian blur is an image filter that uses a kind of function called a Gaussian to transform each pixel in the image. It has the result of smoothing out image noise and reducing detail. Here’s what applying the blur looks like for our image:

>>>blur=cv2.GaussianBlur(final_result,(7,7),0)>>>plt.imshow(blur)>>>plt.show()

Final segmented Nemo with blur

Does This Segmentation Generalize to Nemo’s Relatives?

Just for fun, let’s see how well this segmentation technique generalizes to other clownfish images. In the repository, there’s a selection of six images of clownfish from Google, licensed for public use. The images are in a subdirectory and indexed nemoi.jpg, where i is the index from 0-5.

First, load all Nemo’s relatives into a list:

path="./images/nemo"nemos_friends=[]foriinrange(6):friend=cv2.cvtColor(cv2.imread(path+str(i)+".jpg"),cv2.COLOR_BGR2RGB)nemos_friends.append(friend)

You can combine all the code used above to segment a single fish into a function that will take an image as input and return the segmented image. Expand this section to see what that looks like:

Here is the segment_fish() function:

defsegment_fish(image):''' Attempts to segment the clownfish out of the provided image '''# Convert the image into HSVhsv_image=cv2.cvtColor(image,cv2.COLOR_RGB2HSV)# Set the orange rangelight_orange=(1,190,200)dark_orange=(18,255,255)# Apply the orange mask mask=cv2.inRange(hsv_image,light_orange,dark_orange)# Set a white rangelight_white=(0,0,200)dark_white=(145,60,255)# Apply the white maskmask_white=cv2.inRange(hsv_image,light_white,dark_white)# Combine the two masksfinal_mask=mask+mask_whiteresult=cv2.bitwise_and(image,image,mask=final_mask)# Clean up the segmentation using a blurblur=cv2.GaussianBlur(result,(7,7),0)returnblur

With that useful function, you can then segment all the fish:

results=[segment_fish(friend)forfriendinnemos_friends]

Let’s view all the results by plotting them in a loop:

foriinrange(1,6):plt.subplot(1,2,1)plt.imshow(nemos_friends[i])plt.subplot(1,2,2)plt.imshow(results[i])plt.show()

Nemo's friend 1

The foreground clownfish has orange shades darker than our range.

Nemo's friend 2

The shadowed bottom half of Nemo’s nephew is completely excluded, but bits of the purple anemone in the background look awfully like Nemo’s blue tinged stripes…

Nemo's friend 3

Nemo's friend 4

Nemo's friend 5

Overall, this simple segmentation method has successfully located the majority of Nemo’s relatives. It is clear, however, that segmenting one clownfish with particular lighting and background may not necessarily generalize well to segmenting all clownfish.

Conclusion

In this tutorial, you’ve seen what a few different color spaces are, how an image is distributed across RGB and HSV color spaces, and how to use OpenCV to convert between color spaces and segment out ranges.

Altogether, you’ve learned how a basic understanding of how color spaces in OpenCV can be used to perform object segmentation in images, and hopefully seen its potential for doing other tasks as well. Where lighting and background are controlled, such as in an experimental setting or with a more homogeneous dataset, this segmentation technique is simple, fast, and reliable.


[ 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 ]

PyCharm: PyCharm 2018.3 EAP 4

$
0
0

We are working hard on the next version of PyCharm: 2018.3. And we’re looking for some feedback from those of you interested in having a look at the next version already. You can get this week’s Early Access Preview (EAP) version from our website now.

New in This Version

Updated Plugins Repository

Plugins

PyCharm 2018.3 Professional Edition will launch with support for our new and improved plugins repository: JetBrains Marketplace. We’re working hard on making it easier to extend PyCharm’s functionality with additional third-party plugins. For example, there are plugins available for Kubernetes, and HashiCorp Configuration Language (HCL), and much more.

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.

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 2018.3 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 2018.3 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.

Reinout van Rees: Utrecht (NL) python meetup september 2018

$
0
0

Data processing using parser combinators - Werner de Groot

He collaborated with data scientists from Wageningen University. The scientists did lots of cool programming stuff. But they did not use version control, so they introduced git :-) They also had lots and lots of data, so they introduced Apache Spark.

Their data sets were in ascii files, which are huge. So the ascii files need to be parsed. He showed an example of a file with DNA data. Ouch, it turns out to be pretty complex because there are quite some exceptions. Fields (in this example) are separated by semicolons. But some of the values also contain semicolons (quoted, though). So the generic python code they used to parse their DNA data was littered with "if" statements. Unmaintainable.

You probably heard that hard problems need to be split up into smaller problems. Yes, that's true. But the smaller parts also need to be combined again. So: smaller parsers + a combining them again.

A parser takes an input and returns the part that matched and the part that remained.

He showed parsy as a library for "parser combinators". Many languages have such libraries. He demonstrated how to combine those input/match/remainder parsers into a kind of pipeline/sequence. Such a sequence of parsers can be treated as a parser in its own right. This makes designing and nesting them easy.

When combining parsers, you of course need to handle variants: an "or" operator handles that.

Someone asked about "yacc" parsers, which are able to handle robustly handle each and every corner case, "how does it compare to the simpler 'parsy'". The answer: parsy is a simple library, there are more elaborate python libraries. But: parsy is already quite good. A json parser written in parsy takes only 50 lines!

He did a live demo, developing a set of combined parser step by step. Fun! And very understandable. So "parsy" sounds like a nice library for this kind of work.

There were some comparison-questions to regular expressions. Werner's answer was that parsy's kind of parsers are much more readable and debuggable. He was surprised at the amount of attendees that like regular expressions :-)

The nice thing: every individual part of your parser ("just some numbers", "an equals sign") is a piece of python, so you can give it a name. This way, you can give those pieces of parser names from your domain like dnaName, type, customer).

In the end, he live-coded the whole DNA ascii file parser. Quite boring. And that was his whole point: what would be hard or impossible to do in plain python becomes "just boring" with parsy. Exactly what we want!

A practical application of Python metaclasses - Jan-Hein Bührman

(See an earlier summary about metaclasses being used in django)

Apart from metaclasses, he showed some utilities that he likes: pipenv, pylint, mypy.

A nice touch to his presentation: he had his example code all in separate branches. Instead of live coding, he just switched branches all the time. Because he gave his branches clear names, it worked quite well!

The example he build up is impossible to summarize here. The example included a register function that he had to call on certain classes. He didn't like it. That's where metaclasses come in.

Python objects are instances of classes. Classes themselves are instances of type. You can create classes programmatically by doing something like:

>>> B = type('B', (), {})
>>> b = B()
>>> type(b)
<class 'B'>

Normally, when python imports a module (= reads a python file), class statements are executed and the class is created. You can influence that process by adding a __new__ method.

  • __init__() influences the creation of objects from the class (= instantiating the object from the class).
  • __new__() influences the creation of the class (= instantiating the class from type).

He used it to automatically register objects created from classes with the metaclass.

Note: in python 3.6, __init_subclass__() was added that really makes this much easier.


Jean-Paul Calderone: Asynchronous Object Initialization - Patterns and Antipatterns

$
0
0

I caught Toshio Kuratomi's post about asyncio initialization patterns (or anti-patterns) on Planet Python. This is something I've dealt with a lot over the years using Twisted (one of the sources of inspiration for the asyncio developers).

To recap, Toshio wondered about a pattern involving asynchronous initialization of an instance. He wondered whether it was a good idea to start this work in __init__ and then explicitly wait for it in other methods of the class before performing the distinctive operations required by those other methods. Using asyncio (and using Toshio's example with some omissions for simplicity) this looks something like:


class Microblog:
def __init__(self, ...):
loop = asyncio.get_event_loop()
self.init_future = loop.run_in_executor(None, self._reading_init)

def _reading_init(self):
# ... do some initialization work,
# presumably expensive or otherwise long-running ...

@asyncio.coroutine
def sync_latest(self):
# Don't do anything until initialization is done
yield from self.init_future
# ... do some work that depends on that initialization ...

It's quite possible to do something similar to this when using Twisted. It only looks a little bit difference:


class Microblog:
def __init__(self, ...):
self.init_deferred = deferToThread(self._reading_init)

def _reading_init(self):
# ... do some initialization work,
# presumably expensive or otherwise long-running ...

@inlineCallbacks
def sync_latest(self):
# Don't do anything until initialization is done
yield self.init_deferred
# ... do some work that depends on that initialization ...

Despite the differing names, these two pieces of code basical do the same thing:

  • run _reading_init in a thread from a thread pool
  • whenever sync_latest is called, first suspend its execution until the thread running _reading_init has finished running it

Maintenance costs

One thing this pattern gives you is an incompletely initialized object. If you write m = Microblog() then m refers to an object that's not actually ready to perform all of the operations it supposedly can perform. It's either up to the implementation or the caller to make sure to wait until it is ready. Toshio suggests that each method should do this implicitly (by starting with yield self.init_deferred or the equivalent). This is definitely better than forcing each call-site of a Microblog method to explicitly wait for this event before actually calling the method.

Still, this is a maintenance burden that's going to get old quickly. If you want full test coverage, it means you now need twice as many unit tests (one for the case where method is called before initialization is complete and another for the case where the method is called after this has happened). At least. Toshio's _reading_init method actually modifies attributes of self which means there are potentially many more than just two possible cases. Even if you're not particularly interested in having full automated test coverage (... for some reason ...), you still have to remember to add this yield statement to the beginning of all of Microblog's methods. It's not exactly a ton of work but it's one more thing to remember any time you maintain this code. And this is the kind of mistake where making a mistake creates a race condition that you might not immediately notice - which means you may ship the broken code to clients and you get to discover the problem when they start complaining about it.

Diminished flexibility

Another thing this pattern gives you is an object that does things as soon as you create it. Have you ever had a class with a __init__ method that raised an exception as a result of a failing interaction with some other part of the system? Perhaps it did file I/O and got a permission denied error or perhaps it was a socket doing blocking I/O on a network that was clogged and unresponsive. Among other problems, these cases are often difficult to report well because you don't have an object to blame the problem on yet. The asynchronous version is perhaps even worse since a failure in this asynchronous initialization doesn't actually prevent you from getting the instance - it's just another way you can end up with an incompletely initialized object (this time, one that is never going to be completely initialized and use of which is unsafe in difficult to reason-about ways).

Another related problem is that it removes one of your options for controlling the behavior of instances of that class. It's great to be able to control everything a class does just by the values passed in to __init__ but most programmers have probably come across a case where behavior is controlled via an attribute instead. If __init__ starts an operation then instantiating code doesn't have a chance to change the values of any attributes first (except, perhaps, by resorting to setting them on the class - which has global consequences and is generally icky).

Loss of control

A third consequence of this pattern is that instances of classes which employ it are inevitably doing something. It may be that you don't always want the instance to do something. It's certainly fine for a Microblog instance to create a SQLite3 database and initialize a cache directory if the program I'm writing which uses it is actually intent on hosting a blog. It's most likely the case that other useful things can be done with a Microblog instance, though. Toshio's own example includes a post method which doesn't use the SQLite3 database or the cache directory. His code correctly doesn't wait for init_future at the beginning of his post method - but this should leave the reader wondering why we need to create a SQLite3 database if all we want to do is post new entries.

Using this pattern, the SQLite3 database is always created - whether we want to use it or not. There are other reasons you might want a Microblog instance that hasn't initialized a bunch of on-disk state too - one of the most common is unit testing (yes, I said "unit testing" twice in one post!). A very convenient thing for a lot of unit tests, both of Microblog itself and of code that uses Microblog, is to compare instances of the class. How do you know you got a Microblog instance that is configured to use the right cache directory or database type? You most likely want to make some comparisons against it. The ideal way to do this is to be able to instantiate a Microblog instance in your test suite and uses its == implementation to compare it against an object given back by some API you've implemented. If creating a Microblog instance always goes off and creates a SQLite3 database then at the very least your test suite is going to be doing a lot of unnecessary work (making it slow) and at worst perhaps the two instances will fight with each other over the same SQLite3 database file (which they must share since they're meant to be instances representing the same state). Another way to look at this is that inextricably embedding the database connection logic into your __init__ method has taken control away from the user. Perhaps they have their own database connection setup logic. Perhaps they want to re-use connections or pass in a fake for testing. Saving a reference to that object on the instance for later use is a separate operation from creating the connection itself. They shouldn't be bound together in __init__ where you have to take them both or give up on using Microblog.

Alternatives

You might notice that these three observations I've made all sound a bit negative. You might conclude that I think this is an antipattern to be avoided. If so, feel free to give yourself a pat on the back at this point.

But if this is an antipattern, is there a pattern to use instead? I think so. I'll try to explain it.

The general idea behind the pattern I'm going to suggest comes in two parts. The first part is that your object should primarily be about representing state and your __init__ method should be about accepting that state from the outside world and storing it away on the instance being initialized for later use. It should always represent complete, internally consistent state - not partial state as asynchronous initialization implies. This means your __init__ methods should mostly look like this:


class Microblog(object):
def __init__(self, cache_dir, database_connection):
self.cache_dir = cache_dir
self.database_connection = database_connection

If you think that looks boring - yes, it does. Boring is a good thing here. Anything exciting your __init__ method does is probably going to be the cause of someone's bad day sooner or later. If you think it looks tedious - yes, it does. Consider using Hynek Schlawack's excellent attrs package (full disclosure - I contributed some ideas to attrs' design and Hynek ocassionally says nice things about me (I don't know if he means them, I just know he says them)).

The second part of the idea an acknowledgement that asynchronous initialization is a reality of programming with asynchronous tools. Fortunately __init__ isn't the only place to put code. Asynchronous factory functions are a great way to wrap up the asynchronous work sometimes necessary before an object can be fully and consistently initialized. Put another way:


class Microblog(object):
# ... __init__ as above ...

@classmethod
@asyncio.coroutine
def from_database(cls, cache_dir, database_path):
# ... or make it a free function, not a classmethod, if you prefer
loop = asyncio.get_event_loop()
database_connection = yield from loop.run_in_executor(None, cls._reading_init)
return cls(cache_dir, database_connection)

Notice that the setup work for a Microblog instance is still asynchronous but initialization of the Microblog instance is not. There is never a time when a Microblog instance is hanging around partially ready for action. There is setup work and then there is a complete, usable Microblog.

This addresses the three observations I made above:

  • Methods of Microblog never need to concern themselves with worries about whether the instance has been completely initialized yet or not.
  • Nothing happens in Microblog.__init__. If Microblog has some methods which depend on instance attributes, any of those attributes can be set after __init__ is done and before those other methods are called. If the from_database constructor proves insufficiently flexible, it's easy to introduce a new constructor that accounts for the new requirements (named constructors mean never having to overload __init__ for different competing purposes again).
  • It's easy to treat a Microblog instance as an inert lump of state. Simply instantiating one (using Microblog(...) has no side-effects. The special extra operations required if one wants the more convenient constructor are still available - but elsewhere, where they won't get in the way of unit tests and unplanned-for uses.

I hope these points have made a strong case for one of these approaches being an anti-pattern to avoid (in Twisted, in asyncio, or in any other asynchronous programming context) and for the other as being a useful pattern to provide both convenient, expressive constructors while at the same time making object initializers unsurprising and maximizing their usefulness.

Python Insider: Python 3.7.1rc1 and 3.6.7rc1 now available for testing

$
0
0
Python 3.7.1rc1 and 3.6.7rc1 are now available. 3.7.1rc1 is the release preview of the first maintenance release of Python 3.7, the latest feature release of Python. 3.6.7rc1 is the release preview of the next maintenance release of Python 3.6, the previous feature release of Python. Assuming no critical problems are found prior to 2018-10-06, no code changes are planned between these release candidates and the final releases. These release candidates are intended to give you the opportunity to test the new security and bug fixes in 3.7.1 and 3.6.7. We strongly encourage you to test your projects and report issues found to bugs.python.org as soon as possible. Please keep in mind that these are preview releases and, thus, their use is not recommended for production environments.

You can find these releases and more information here:

Semaphore Community: Building and Testing an API Wrapper in Python

$
0
0

This article is brought with ❤ to you by Semaphore.

Introduction

Most websites we use provide an HTTP API to enable developers to access their data from their own applications. For developers utilizing the API, this usually involves making some HTTP requests to the service, and using the responses in their applications. However, this may get tedious since you have to write HTTP requests for each API endpoint you intend to use. Furthermore, when a part of the API changes, you have to edit all the individual requests you have written.

A better approach would be to use a library in your language of choice that helps you abstract away the API's implementation details. You would access the API through calling regular methods provided by the library, rather than constructing HTTP requests from scratch. These libraries also have the advantage of returning data as familiar data structures provided by the language, hence enabling idiomatic ways to access and manipulate this data.

In this tutorial, we are going to write a Python library to help us communicate with The Movie Database's API from Python code.

By the end of this tutorial, you will learn:

  • How to create and test a custom library which communicates with a third-party API and
  • How to use the custom library in a Python script.

Prerequisites

Before we get started, ensure you have one of the following Python versions installed:

  • Python 2.7, 3.3, 3.4, or 3.5

We will also make use of the Python packages listed below:

  • requests - We will use this to make HTTP requests,
  • vcrpy - This will help us record HTTP responses during tests and test those responses, and
  • pytest - We will use this as our testing framework.

Project Setup

We will organize our project as follows:

.
├── requirements.txt
├── tests
│   ├── __init__.py
│   ├── test_tmdbwrapper.py
│   └── vcr_cassettes
└── tmdbwrapper
    └── __init__.py
    └── tv.py

This sets up a folder for our wrapper and one for holding the tests. The vcr_cassettes subdirectory inside tests will store our recorded HTTP interactions with The Movie Database's API.

Our project will be organized around the functionality we expect to provide in our wrapper. For example, methods related to TV functionality will be in the tv.py file under the tmdbwrapper directory.

We need to list our dependencies in the requirements.txt file as follows. At the time of writing, these are the latest versions. Update the version numbers if later versions have been published by the time you are reading this.

requests==2.11.1
vcrpy==1.10.3
pytest==3.0.3

Finally, let's install the requirements and get started:

pip install -r requirements.txt

Test-driven Development

Following the test-driven development practice, we will write the tests for our application first, then implement the functionality to make the tests pass.

For our first test, let's test that our module will be able to fetch a TV show's info from TMDb successfully.

# tests/test_tmdbwrapper.pyfromtmdbwrapperimportTVdeftest_tv_info():"""Tests an API call to get a TV show's info"""tv_instance=TV(1396)response=tv_instance.info()assertisinstance(response,dict)assertresponse['id']==1396,"The ID should be in the response"

In this initial test, we are demonstrating the behavior we expect our complete module to exhibit. We expect that our tmdbwrapper package will contain a TV class, which we can then instantiate with a TMDb TV ID. Once we have an instance of the class, when we call the info method, it should return a dictionary containing the TMDb TV ID we provided under the 'id' key.

To run the test, execute the py.test command from the root directory. As expected, the test will fail with an error message that should contain something similar to the following snippet:

    ImportError while importing test module '/Users/kevin/code/python/tmdbwrapper/tests/test_tmdbwrapper.py'.
    'cannot import name TV'
    Make sure your test modules/packages have valid Python names.

This is because the tmdbwrapper package is empty right now. From now on, we will write the package as we go, adding new code to fix the failing tests, adding more tests and repeating the process until we have all the functionality we need.

Implementing Functionality in Our API Wrapper

To start with, the minimal functionality we can add at this stage is creating the TV class inside our package.

Let's go ahead and create the class in the tmdbwrapper/tv.py file:

# tmdbwrapper/tv.pyclassTV(object):pass

Additionally, we need to import the TV class in the tmdbwrapper/__init__.py file, which will enable us to import it directly from the package.

# tmdbwrapper/__init__.pyfrom.tvimportTV

At this point, we should re-run the tests to see if they pass. You should now see the following error message:

>        tv_instance = TV(1396)
    E       TypeError: object() takes no parameters

We get a TypeError. This is good. We seem to be making some progress. Reading through the error, we can see that it occurs when we try to instantiate the TV class with a number. Therefore, what we need to do next is implement a constructor for the TV class that takes a number. Let's add it as follows:

# tmdbwrapper/tv.pyclassTV(object):def__init__(self,id):pass

As we just need the minimal viable functionality right now, we will leave the constructor empty, but ensure that it receives self and id as parameters. This id parameter will be the TMDb TV ID that will be passed in.

Now, let's re-run the tests and see if we made any progress. We should see the following error message now:

>       response = tv_instance.info()
E       AttributeError: 'TV' object has no attribute 'info'

This time around, the problem is that we are using the info method from the tv_instance, and this method does not exist. Let's add it.

# tmdbwrapper/tv.pyclassTV(object):def__init__(self,id):passdefinfo(self):pass

After running the tests again, you should see the following failure:

>       assert isinstance(response, dict)
    E       assert False
    E        +  where False = isinstance(None, dict)

For the first time, it's the actual test failing, and not an error in our code. To make this pass, we need to make the info method return a dictionary. Let's also pre-empt the next failure we expect. Since we know that the returned dictionary should have an id key, we can return a dictionary with an 'id' key whose value will be the TMDb TV ID provided when the class is initialized.

To do this, we have to store the ID as an instance variable, in order to access it from the info function.

# tmdbwrapper/tv.pyclassTV(object):def__init__(self,id):self.id=iddefinfo(self):return{'id':self.id}

If we run the tests again, we will see that they pass.

Writing Foolproof Tests

You may be asking yourself why the tests are passing, since we clearly have not fetched any info from the API. Our tests were not exhaustive enough. We need to actually ensure that the correct info that has been fetched from the API is returned.

If we take a look at the TMDb documentation for the TV info method, we can see that there are many additional fields returned from the TV info response, such as poster_path, popularity, name, overview, and so on.

We can add a test to check that the correct fields are returned in the response, and this would in turn help us ensure that our tests are indeed checking for a correct response object back from the info method.

For this case, we will select a handful of these properties and ensure that they are in the response. We will use pytest fixtures for setting up the list of keys we expect to be included in the response.

Our test will now look as follows:

# tests/test_tmdbwrapper.pyfrompytestimportfixturefromtmdbwrapperimportTV@fixturedeftv_keys():# Responsible only for returning the test datareturn['id','origin_country','poster_path','name','overview','popularity','backdrop_path','first_air_date','vote_count','vote_average']deftest_tv_info(tv_keys):"""Tests an API call to get a TV show's info"""tv_instance=TV(1396)response=tv_instance.info()assertisinstance(response,dict)assertresponse['id']==1396,"The ID should be in the response"assertset(tv_keys).issubset(response.keys()),"All keys should be in the response"

Pytest fixtures help us create test data that we can then use in other tests. In this case, we create the tv_keys fixture which returns a list of some of the properties we expect to see in the TV response. The fixture helps us keep our code clean, and explicitly separate the scope of the two functions.

You will notice that the test_tv_info method now takes tv_keys as a parameter. In order to use a fixture in a test, the test has to receive the fixture name as an argument. Therefore, we can make assertions using the test data. The tests now help us ensure that the keys from our fixtures are a subset of the list of keys we expect from the response.

This makes it a lot harder for us to cheat in our tests in future, as we did before.

Running our tests again should give us a constructive error message which fails because our response does not contain all the expected keys.

Fetching Data from TMDb

To make our tests pass, we will have to construct a dictionary object from the TMDb API response and return that in the info method.

Before we proceed, please ensure you have obtained an API key from TMDb by registering. All the available info provided by the API can be viewed in the API Overview page and all methods need an API key. You can request one after registering your account on TMDb.

First, we need a requests session that we will use for all HTTP interactions. Since the api_key parameter is required for all requests, we will attach it to this session object so that we don't have to specify it every time we need to make an API call. For simplicity, we will write this in the package's __init__.py file.

# tmdbwrapper/__init__.pyimportosimportrequestsTMDB_API_KEY=os.environ.get('TMDB_API_KEY',None)classAPIKeyMissingError(Exception):passifTMDB_API_KEYisNone:raiseAPIKeyMissingError("All methods require an API key. See ""https://developers.themoviedb.org/3/getting-started/introduction ""for how to retrieve an authentication token from ""The Movie Database")session=requests.Session()session.params={}session.params['api_key']=TMDB_API_KEYfrom.tvimportTV

We define a TMDB_API_KEY variable which gets the API key from the TMDB_API_KEY environment variable. Then, we go ahead and initialize a requests session and provide the API key in the params object. This means that it will be appended as a parameter to each request we make with this session object. If the API key is not provided, we will raise a custom APIKeyMissingError with a helpful error message to the user.

Next, we need to make the actual API request in the info method as follows:

# tmdbwrapper/tv.pyfrom.importsessionclassTV(object):def__init__(self,id):self.id=iddefinfo(self):path='https://api.themoviedb.org/3/tv/{}'.format(self.id)response=session.get(path)returnresponse.json()

First of all, we import the session object that we defined in the package root. We then need to send a GET request to the TV info URL that returns details about a single TV show, given its ID. The resulting response object is then returned as a dictionary by calling the .json() method on it.

There's one more thing we need to do before wrapping this up. Since we are now making actual API calls, we need to take into account some API best practices. We don't want to make the API calls to the actual TMDb API every time we run our tests, since this can get you rate limited.

A better way would be to save the HTTP response the first time a request is made, then reuse this saved response on subsequent test runs. This way, we minimize the amount of requests we need to make on the API and ensure that our tests still have access to the correct data. To accomplish this, we will use the vcr package:

# tests/test_tmdbwrapper.pyimportvcr@vcr.use_cassette('tests/vcr_cassettes/tv-info.yml')deftest_tv_info(tv_keys):"""Tests an API call to get a TV show's info"""tv_instance=TV(1396)response=tv_instance.info()assertisinstance(response,dict)assertresponse['id']==1396,"The ID should be in the response"assertset(tv_keys).issubset(response.keys()),"All keys should be in the response"

We just need to instruct vcr where to store the HTTP response for the request that will be made for any specific test. See vcr's docs on detailed usage information.

At this point, running our tests requires that we have a TMDB_API_KEY environment variable set, or else we'll get an APIKeyMissingError. One way to do this is by setting it right before running the tests, i.e. TMDB_API_KEY='your-tmdb-api-key' py.test.

Running the tests with a valid API key should have them passing.

Adding More Functions

Now that we have our tests passing, let's add some more functionality to our wrapper. Let's add the ability to return a list of the most popular TV shows on TMDb. We can add the following test:

# tests/test_tmdbwrapper.py@vcr.use_cassette('tests/vcr_cassettes/tv-popular.yml')deftest_tv_popular():"""Tests an API call to get a popular tv shows"""response=TV.popular()assertisinstance(response,dict)assertisinstance(response['results'],list)assertisinstance(response['results'][0],dict)assertset(tv_keys).issubset(response['results'][0].keys())

Note that we are instructing vcr to save the API response in a different file. Each API response needs its own file.

For the actual test, we need to check that the response is a dictionary and contains a results key, which contains a list of TV show dictionary objects. Then, we check the first item in the results list to ensure it is a valid TV info object, with a test similar to the one we used for the info method.

To make the new tests pass, we need to add the popular method to the TV class. It should make a request to the popular TV shows path, and then return the response serialized as a dictionary.
Let's add the popular method to the TV class as follows:

# tmdbwrapper/tv.py@staticmethoddefpopular():path='https://api.themoviedb.org/3/tv/popular'response=session.get(path)returnresponse.json()

Also, note that this is a staticmethod, which means it doesn't need the class to be initialized for it to be used. This is because it doesn't use any instance variables, and it's called directly from the class.

All our tests should now be passing.

Taking Our API Wrapper for a Spin

Now that we've implemented an API wrapper, let's check if it works by using it in a script. To do this, we will write a program that lists out all the popular TV shows on TMDb along with their popularity rankings. Create a file in the root folder of our project. You can name the file anything you like — ours is called testrun.py.

# example.pyfrom__future__importprint_functionfromtmdbwrapperimportTVpopular=TV.popular()fornumber,showinenumerate(popular['results'],start=1):print("{num}. {name} - {pop}".format(num=number,name=show['name'],pop=show['popularity']))

If everything is working correctly, you should see an ordered list of the current popular TV shows and their popularity rankings on The Movie Database.

Filtering Out the API Key

Since we are saving our HTTP responses to a file on a disk, there are chances we might expose our API key to other people, which is a Very Bad Idea™, since other people might use it for malicious purposes. To deal with this, we need to filter out the API key from the saved responses. To do this, we need to add a filter_query_parameters keyword argument to the vcr decorator methods as follows:

@vcr.use_cassette('tests/vcr_cassettes/tv-popular.yml',filter_query_parameters=['api_key'])

This will save the API responses, but it will leave out the API key.

Continuous Testing on Semaphore CI

Lastly, let's add continuous testing to our application using Semaphore CI.

We want to ensure that our package works on various platforms and that we don't accidentally break functionality in future versions. We do this through continuous automatic testing.

Ensure you've committed everything on Git, and push your repository to GitHub or Bitbucket, which will enable Semaphore to fetch your code. Next, sign up for a free Semaphore account, if don't have one already. Once you've confirmed your email, it's time to create a new project.

Follow these steps to add the project to Semaphore:

  1. Once you're logged into Semaphore, navigate to your list of projects and click the "Add New Project" button:

    Add New Project Screen

  2. Next, select the account where you wish to add the new project.

    Select Account Screen

  3. Select the repository that holds the code you'd like to build:

    Select Repository Screen

  4. Configure your project as shown below:

    Project Configuration Screen

Finally, wait for the first build to run.

It should fail, since as we recall, the TMDB_API_KEY environment key is required for the tests to run.

Navigate to the Project Settings page of your application and add your API key as an environment variable as shown below:

Add environment variable screen

Make sure to check the Encrypt content checkbox when adding the key to ensure the API key will not be publicly visible. Once you've added that and re-run the build, your tests should be passing again.

Conclusion

We have learned how to write a Python wrapper for an HTTP API by writing one ourselves. We have also seen how to test such a library and what are some best practices around that, such as not exposing our API keys publicly when recording HTTP responses.

Adding more methods and functionality to our API wrapper should be straightforward, since we have set up methods that should guide us if we need to add more. We encourage you to check out the API and implement one or two extra methods to practice. This should be a good starting point for writing a Python wrapper for any API out there.

Please reach out with any questions or feedback that you may have in the comments section below. You can also check out the complete code and contribute on GitHub.

This article is brought with ❤ to you by Semaphore.

Trey Hunner: Stop writing lambda expressions in Python

$
0
0

It’s hard for me to teach an in-depth Python class without discussing lambda expressions. I almost always get questions about them. My students tend to see them in code on StackOverflow or they see them in a coworker’s code (which, realistically, may have also come from StackOverflow).

I get a lot of questions about lambda, I’m hesitant to recommend my students embrace Python’s lambda expressions. I have had an aversion to lambda expressions for many years, and since I started teaching Python more regularly a few years ago, my aversion to lambda expressions has only grown stronger.

I’m going to explain how I see lambda expressions and why I tend to recommend my students avoid using them.

Lambda expressions in Python: what are they?

Lambda expressions a special syntax in Python for creating anonymous functions. I’ll call the lambda syntax itself a lambda expression and the function you get back from this I’ll call a lambda function.

Python’s lambda expressions allow a function to be created and passed around (often into another function) all in one line of code.

Lambda expressions allow us to take this code:

123456
colors=["Goldenrod","Purple","Salmon","Turquoise","Cyan"])defnormalize_case(string):returnstring.casefold()normalized_colors=map(normalize_case,colors)

And turn it into this code:

123
colors=["Goldenrod","Purple","Salmon","Turquoise","Cyan"])normalized_colors=map(lambdas:s.casefold(),colors)

Lambda expressions are just a special syntax for making functions. They can only have one statement in them and they return the result of that statement automatically.

The inherent limitations of lambda expressions are actually part of their appeal. When an experienced Python programmer sees a lambda expression they know that they’re working with a function that is only used in one place and does just one thing.

If you’ve ever used anonymous functions in JavaScript before, you can think of Python’s lambda expressions as the same, except they have more restrictions and use a very different syntax than the traditional function syntax.

Where they’re usually used

You’ll typically see lambda expressions used when calling functions (or classes) that accept a function as an argument.

Python’s built-in sorted function accepts a function as its key argument. This key function is used to compute a comparison key when determining the sorting order of items.

So sorted is a great example of a place that lambda expressions are often used:

123
>>>colors=["Goldenrod","purple","Salmon","turquoise","cyan"]>>>sorted(colors,key=lambdas:s.casefold())['cyan','Goldenrod','purple','Salmon','turquoise']

The above code returns the given colors sorted in a case-insensitive way.

The sorted function isn’t the only use of lambda expressions, but it’s a common one.

The pros and cons of lambda

I frame my thinking around lambda expressions as a constant comparison to using def to define functions. Both of these tools give us functions, but they each have different limitations and use a different syntax.

The main ways lambda expressions are different from def:

  1. They can be immediately passed around (no variable needed)
  2. They can only have a single line of code within them
  3. They return automatically
  4. They can’t have a docstring and they don’t have a name
  5. They use a different and unfamiliar syntax

The fact that lambda expressions can be passed around is their biggest benefit. Returning automatically is neat but not a big benefit in my mind. I find the “single line of code” limitation is neither good nor bad overall. The fact that lambda functions can’t have docstrings and don’t have a name is unfortunate and their unfamiliar syntax can be troublesome for newer Pythonistas.

Overall I feel the cons slightly outweigh the pros of lambda expressions, but my biggest complaint about them is that I find that they tend to be both misused and overused.

Lambda is both misused and overused

When I see a lambda expression in unfamiliar code I immediately become skeptical. When I encounter a lambda expression in the wild, I often find that removing it improves code readability.

Sometimes the issue is that lambda expressions are being misused, meaning they’re used in a way that is nearly always unideal. Other times lambda expressions are simply being overused, meaning they’re acceptable but I’d personally prefer to see the code written a different way.

Let’s take a look at the various ways lambda expressions are misused and overused.

Misuse: naming lambda expressions

PEP8, the official Python style guide, advises never to write code like this:

1
normalize_case=lambdas:s.casefold()

The above statement makes an anonymous function and then assigns it to a variable. The above code ignores the reason lambda functions are useful: lambda functions can be passed around without needing to be assigned to a variable first.

If you want to create a one-liner function and store it in a variable, you should use def instead:

1
defnormalize_case(s):returns.casefold()

PEP8 recommends this because named functions are a common and easily understood thing. This also has the benefit of giving our function a proper name, which could make debugging easier. Unlike functions defined with def, lambda functions never have a name (it’s always <lambda>):

1234567
>>>normalize_case=lambdas:s.casefold()>>>normalize_case<function<lambda>at0x7f264d5b91e0>>>>defnormalize_case(s):returns.casefold()...>>>normalize_case<functionnormalize_caseat0x7f247f68fea0>

If you want to create a function and store it in a variable, define your function using def. That’s exactly what it’s for. It doesn’t matter if your function is a single line of code or if you’re defining a function inside of another function, def works just fine for those use cases.

Misuse: needless function calls

I frequently see lambda expressions used to wrap around a function that was already appropriate for the problem at hand.

For example take this code:

1
sorted_numbers=sorted(numbers,key=lambdan:abs(n))

The person who wrote this code likely learned that lambda expressions are used for making a function that can be passed around. But they missed out on a slightly bigger picture idea: all functions in Python (not just lambda functions) can be passed around.

Since abs (which returns the absolute value of a number) is a function and all functions can be passed around, we could actually have written the above code like this:

1
sorted_numbers=sorted(numbers,key=abs)

Now this example might feel contrived, but it’s not terribly uncommon to overuse lambda expressions in this way. Here’s another example I’ve seen:

12
pairs=[(4,11),(8,8),(5,7),(11,3)]sorted_by_smallest=sorted(pairs,key=lambdax,y:min(x,y))

Because we’re accepting exactly the same arguments as we’re passing into min, we don’t need that extra function call. We can just pass the min function to key instead:

12
pairs=[(4,11),(8,8),(5,7),(11,3)]sorted_by_smallest=sorted(pairs,key=min)

You don’t need a lambda function if you already have another function that does what you want.

Overuse: simple, but non-trivial functions

It’s common to see lambda expressions used to make a function that returns a couple of values in a tuple:

12
colors=["Goldenrod","Purple","Salmon","Turquoise","Cyan"])colors_by_length=sorted(colors,key=lambdac:(len(c),c.casefold()))

That key function here is helping us sort these colors by their length followed by their case-normalized name.

This code is the same as the above code, but I find it more readable:

123456
deflength_and_alphabetical(string):"""Return sort key: length first, then case-normalized string."""return(len(string),string.casefold())colors=["Goldenrod","Purple","Salmon","Turquoise","Cyan"])colors_by_length=sorted(colors,key=length_and_alphabetical)

This code is quite a bit more verbose, but I find the name of that key function makes it clearer what we’re sorting by. We’re not just sorting by the length and we’re not just sorting by the color: we’re sorting by both.

If a function is important, it deserves a name. You could argue that most functions that are used in a lambda expression are so trivial that they don’t deserve a name, but there’s often little downside to naming functions and I find it usually makes my code more readable overall.

Naming functions often makes code more readable, the same way using tuple unpacking to name variables instead of using arbitrary index-lookups often makes code more readable.

Overuse: when multiple lines would help

Sometimes the “just one line” aspect of lambda expressions cause us to write code in convoluted ways. For example take this code:

12
points=[((1,2),'red'),((3,4),'green')]points_by_color=sorted(points,key=lambdap:p[1])

We’re hard-coding an index lookup here to sort points by their color. If we used a named function we could have used tuple unpacking to make this code more readable:

1234567
defcolor_of_point(point):"""Return the color of the given point."""(x,y),color=pointreturncolorpoints=[((1,2),'red'),((3,4),'green')]points_by_color=sorted(points,key=color_of_point)

Tuple unpacking can improve readability over using hard-coded index lookups. Using lambda expressions often means sacrificing some Python language features, specifically those that require multiple lines of code (like an extra assignment statement).

Overuse: lambda with map and filter

Python’s map and filter functions are almost always paired with lambda expressions. It’s common to see StackOverflow questions asking “what is lambda” answered with code examples like this:

123
>>>numbers=[2,1,3,4,7,11,18]>>>squared_numbers=map(lambdan:n**2,numbers)>>>odd_numbers=filter(lambdan:n%2==1,numbers)

I find these examples a bit confusing because I almost never use map and filter in my code.

Python’s map and filter functions are used for looping over an iterable and making a new iterable that either slightly changes each element or filters the iterable down to only elements that match a certain condition. We can accomplish both of those tasks just as well with list comprehensions or generator expressions:

123
>>>numbers=[2,1,3,4,7,11,18]>>>squared_numbers=(n**2forninnumbers)>>>odd_numbers=(nforninnumbersifn%2==1)

Personally, I’d prefer to see the above generator expressions written over multiple lines of code (see my article on comprehensions) but I find even these one-line generator expressions more readable than those map and filter calls.

The general operations of mapping and filtering are useful, but we really don’t need the map and filter functions themselves. Generator expressions are a special syntax that exists just for the tasks of mapping and filtering. So my advice is to use generator expressions instead of the map and filter functions.

Misuse: sometimes you don’t even need to pass a function

What about cases where you need to pass around a function that performs a single operation?

Newer Pythonistas who are keen on functional programming sometimes write code like this:

1234
fromfunctoolsimportreducenumbers=[2,1,3,4,7,11,18]total=reduce(lambdax,y:x+y,numbers)

This code adds all the numbers in the numbers list. There’s an even better way to do this:

12
numbers=[2,1,3,4,7,11,18]total=sum(numbers)

Python’s built-in sum function was made just for this task.

The sum function, along with a number of other specialized Python tools, are easy to overlook. But I’d encourage you to seek out the more specialized tools when you need them because they often make for more readable code.

Instead of passing functions into other functions, look into whether there is a more specialized way to solve your problem instead.

Overuse: using lambda for very simple operations

Let’s say instead of adding numbers up, we’re multiply numbers together:

1234
fromfunctoolsimportreducenumbers=[2,1,3,4,7,11,18]product=reduce(lambdax,y:x*y,numbers,1)

The above lambda expression is necessary because we’re not allowed to pass the * operator around as if it were a function. If there was a function that was equivalent to *, we could pass it into the reduce function instead.

Python’s standard library actually has a whole module meant to address this problem:

12345
fromfunctoolsimportreducefromoperatorimportmulnumbers=[2,1,3,4,7,11,18]product=reduce(mul,numbers,1)

Python’s operator module exists to make various Python operators easy to use as functions. If you’re practicing functional(ish) programming, Python’s operator module is your friend.

In addition to providing functions corresponding to Python’s many operators, the operator module provides a couple common higher level functions for accessing items and attributes and calling methods.

There’s itemgetter for accessing indexes of a list/sequence or keys of a dictionary/mapping:

123456
# Without operator: accessing a key/indexrows_sorted_by_city=sorted(rows,key=lambdarow:row['city'])# With operator: accessing a key/indexfromoperatorimportitemgetterrows_sorted_by_city=sorted(rows,key=itemgetter('city'))

There’s also attrgetter for accessing attributes on an object:

123456
# Without operator: accessing an attributeproducts_by_quantity=sorted(products,key=lambdap:p.quantity)# With operator: accessing an attributefromoperatorimportattrgetterproducts_by_quantity=sorted(products,key=attrgetter('quantity'))

And methodcaller for calling methods on an object:

123456
# Without operator: calling a methodsorted_colors=sorted(colors,key=lambdas:s.casefold())# With operator: calling a methodfromoperatorimportmethodcallersorted_colors=sorted(colors,key=methodcaller('casefold'))

I usually find that using the functions in the operator module makes my code clearer than if I’d used an equivalent lambda expression.

Overuse: when higher order functions add confusion

A function that accepts a function as an argument is called a higher order function. Higher order functions are the kinds of functions that we tend to pass lambda functions to.

The use of higher order functions is common when practicing functional programming. Functional programming isn’t the only way to use Python though: Python is a multi-paradigm language so we can mix and match coding disciplines to make our code more readable.

Compare this:

1234
fromfunctoolsimportreducenumbers=[2,1,3,4,7,11,18]product=reduce(lambdax,y:x*y,numbers,1)

To this:

123456789
defmultiply_all(numbers):"""Return the product of the given numbers."""product=1forninnumbers:product*=nreturnproductnumbers=[2,1,3,4,7,11,18]product=multiply_all(numbers)

The second code is longer, but folks without a functional programming background will often find it easier to understand.

Anyone who has gone through one of my Python training courses can probably understand what that multiply_all function does, whereas that reduce/lambda combination is likely a bit more cryptic for many Python programmers.

In general, passing one function into another function, tends to make code more complex, which can hurt readability.

Should you ever use lambda expressions?

So I find the use of lambda expressions problematic because:

  • lambda expressions are an odd and unfamiliar syntax to many Python programmers
  • lambda functions inherently lack a name or documentation, meaning reading their code is the only way to figure out what they do
  • lambda expressions can have only one statement in them so certain language features that improve readability, like tuple unpacking, can’t be used with them
  • lambda functions can often be replaced with already existing functions in the standard libray or built-in to Python

Lambda expressions are rarely more immediately readable than a well-named function. While a def statement is often more understandable, Python also has a number of features that can be used to replace lambda expressions, including special syntaxes (comprehensions), built-in functions (sum), and standard library functions (in the operators module).

I’d say that using lambda expressions is acceptable only if your situation meets all four of these criteria:

  1. The operation you’re doing is trivial: the function doesn’t deserve a name
  2. Having a lambda expression makes your code more understandable than the function names you can think of
  3. You’re pretty sure there’s not already a function that does what you’re looking for
  4. Everyone on your team understands lambda expressions and you’ve all agreed to use them

If any of those four statements don’t fit your situation, I’d recommend writing a new function using def and (whenever possible) embracing a function that already exists within Python that already does what you’re looking for.

Djangostars: How to Create and Deploy a Telegram Bot?

$
0
0

Introduction or Why You Should Try a Bot

How to Create and Deploy a Telegram Bot?

(you may skip it if you already know what to do with your bot)

Bots are everywhere. It seems that only yesterday we did not even know about their existence; now we can barely imagine our life without them. They’ve become widely popular among numerous active users of messengers since they have various scopes of use - from entertaining content including step-by-step games and collecting bonus points in restaurants, to keeping a diet plan, tracking deliveries, and even making payments for different services.

How to Create and Deploy a Telegram Bot?

Why are they so popular? What’s their secret? I think the more relevant question is why they are more convenient than an app. And there are a few reasons.

1) Minimalistic and simple design. Well, the bot simply cannot have colorful design. But beyond any doubts, in comparison to numerous apps with different design when you have to remember where and what to tap, bot is more universal and easy; it offers a simple communication via texts.

2) Bot has minimum of advertising and focuses on the users’ needs. You do not have to install hundreds of apps for each service if you can receive all necessary assistance from a bot. This is particularly useful for restaurants and shops. Clients are rarely eager to install apps from a bunch of places they’ve visited. Due to this, business owners miss clients’ feedback and lose communication with them. If each of these places had their own bot available in different messengers, it would be more convenient and friendly to users. Nobody likes to fill up the storage of their phones with unnecessary apps that will be used once or twice. However, clients need to engage with the service owners and they will appreciate doing this through their favorite messenger.

3) No need for signing up, authorization, and constant relogin Using a bot, user passes authorization only once when the bot is added to the chat. Client can use the bot as much as it is necessary, and when there is no need in it anymore, user just blocks the bot. That’s all, easy! No password resets anymore.

How to Create and Deploy a Telegram Bot?

No need to remember passwords or logins used. Adding bot to the site or app increases the number of the user audience as it makes communication with clients and providing them with the assistance much easier and user-friendly.

So, we described main advantages of the bots and now you must be willing to create your own bot. Let’s move on to the practice. But first, we’ll take a look at issues that must be addressed in the pre-development stage.

Nuances of Telegram Bot Development

When we’ve already determined reasons for creating the bot, now it’s time to think on how we plan to organize the development process and what tools we will need. Further, we will demonstrate in practice how to create your first bot and how to teach it to turn our message inside out.

In this part, we are planning how to build the application and what development tools to use. Further, we’ll show how to build your first Telegram bot and will teach it to turn our message backwards.
Since it is the manual for beginners, we will run the server with a single endpoint that will receive our telegram messages and will make an answer.

For that, we will use the following tools:

  • bottle - for our server; a simple and lightweight WSGI micro web-framework
  • requests - for sending requests to telegram. request lib does not need to be overrepresented. It is universally used throughout the world in a variety of projects. Note: you have to install these tools on your computer. We will need them later. For that, open your bash console and install it via pip
pip install bottle requests  
  • ngrok - this is an app which provides us with public URLs for our interaction with Telegram WebHook throughout the development phase (look for info on WebHook below). It is useful, as Telegram will not be able to make a connection with our local server because we cannot specify our local address in the Telegram API configuration. You should download ngrok from the official site and put the installed app in the folder with the project.

How About to Create Your First Bot?

If you are keen on this, explore this part where we will provide a detailed Telegram bot development tutorial.

First things first. You need to register on Telegram (obviously). I recommend to use Telegram web client for testing the basic concepts.

Open Telegram app, search for @BotFather and start the chat. Send /newbot command and follow the instructions. After completing the initial steps, you’ll get —

Wells, that’s actually it. At the moment the bot is 100% passive.

You need to initialize a conversation with your bot. Open search and type in the name of your bot. Start a conversation by clicking on /start button. Type in something like “Hello”. This message is important as it’s the first update your bot is going to receive.

If it’s your first experience with building APIs, you can easily get the idea using your web browser. Open a new tab in your browser and use the Telegram api URL -

/getUpdates">/getUpdates">https://api.telegram.org/bot<token>/getUpdates

When you open this URL in your web browser, you make a request to Telegram server, that responds back with JSON. Response resembles Python dictionary. You should see something like this:

{"ok":true,"result":[{"update_id":523349956,
"message":{"message_id":51,"from":{"id":303262877,"first_name":"YourName"},"chat":{"id":303262877,"first_name":"YourName","type":"private"},"date":1486829360,"text":"Hello"}}]}

If you open bots documentation and check /sendMessage method section you’ll notice that this method requires 2 additional parameters chat_id and text. In a browser search bar you can chain parameters using ? for the first one and & for all the consequent. Send message would look like this -

/sendMessage?chat_id=303262877&text=test

Try to get the reply from your bot by substituting chat_id with one you get by calling /getUpdates. In my case it is 303262877. Text parameter is up to you. The request should look like this

https://api.telegram.org/bot<\your-token>/sendMessage?chat_id=&text=<\your-text>

WebHook

(You can skip this part if you’re familiar with WebHook)

To put it short, a WebHook is an API concept that grows in popularity. The concept of a WebHook is simple. A WebHook is an HTTP callback: an HTTP POST that occurs when something happens; a simple event-notification via HTTP POST.

To explain a bit more, sometimes an interaction between apps online requires immediate response to the event, while solutions for constant and continuous connections are mostly cumbersome, exacting and hard to support. In this case, the best and the easiest solution is immediate callback via HTTP (most often POST).

In other words, this solution ensures response to any event inside the one app by means of sending HTTP POST request to other connected app in order to inform it or to make it respond.

This exact concept is called WebHook. It is widely used for:

  • receiving data in real time
  • receiving data and passing it on
  • processing data and giving something in return

Seems that it is the best solution for interaction of the Telegram client (Telegram app) with our project.

How to Create and Deploy a Telegram Bot?

Coding Part

At last, we start the most practical part where you will be able to develop a Telegram bot.

Main task: teach our bot to turn our message backwards

Firstly, create a folder for our bot project.

Secondly, create bot.py file to make a bottle server.

How to Create and Deploy a Telegram Bot?

Next, we develop bot.py

from bottle import run, post


@post('/')  # our python function based endpoint
def main():  
    return 


if __name__ == '__main__':  
    run(host='localhost', port=8080, debug=True)

Let’s try to start our server. For this, open bash in your bot folder.

python bot.py  

In the result, you should see something like this:

How to Create and Deploy a Telegram Bot?

Then, open a new tab. In the next tab, we will start ngrok

./ngrok http <our_server_port>

./ngrok http 8080

After, you’ll see something like this:

How to Create and Deploy a Telegram Bot?

Now, let’s set WebHook.

https://api.telegram.org/bot<\your_token>/setWebHook?url=\your_ngrok_url.ngrok.io/">\your_ngrok_url.ngrok.io/">https://<\your_ngrok_url.ngrok.io/

  • Note: to find ngrok URL, you have to launch ngrok. Then, on the screen similar to the one below, you’ll find URL (it is highlighted on our screenshot). This URL you use in the link for setting WebHook.

Response to following the link should be like:

{"ok":true,"result":true,"description":"Webhook was set"}

Let's check if you succeeded in setting up WebHook. Follow this link using your token:

https://api.telegram.org/bot<\your_token>/getWebhookInfo

If everything is right, you’ll see the same ngrok address value in front of URL key that you specified when configuring.

Congrats, it’s alive!

How to Create and Deploy a Telegram Bot?

Now, we need to implement a message request/response mechanism.
Basically, our endpoint gets data in json format. So, normally, you’ll see data message.

from bottle import run, post, request as bottle_request  # <--- we add bottle request

@post('/')
def main():  
    data = bottle_request.json  # <--- extract all request data
    print(data)

    return 


if __name__ == '__main__':  
    run(host='localhost', port=8080, debug=True)

It should be something like that in your console tab where the server is launched.

{'update_id': <integer>, 'message': {'message_id': <integer>, 'from': {'id': <your telegram id>, 'is_bot': False, 'first_name': '<your telegram name>', 'last_name': '<...>', 'username': '<...>', 'language_code': 'en-En'}, 'chat': {'id': <integer chat id>, 'first_name': '<...>', 'last_name': '<...>', 'username': '<...>', 'type': 'private'}, 'date': 1535022558, 'text': '1'}}

More detailed information on the parameters you may find in the official documentation of Telegram.

Now, we have to extract chat_id and text in order to turn our message backwards and send the answer.

from bottle import (  
    run, post, response, request as bottle_request
)


def get_chat_id(data):  
    """
    Method to extract chat id from telegram request.
    """
    chat_id = data['message']['chat']['id']

    return chat_id


def get_message(data):  
    """
    Method to extract message id from telegram request.
    """
    message_text = data['message']['text']
    return message_text


def change_text_message(text):  
    """
    To turn our message backwards.
    """
    return text[::-1]


@post('/')
def main():  
    data = bottle_request.json
    answer_data = prepare_data_for_answer(data)

    return response  # status 200 OK by default

Now, we’ve already prepared the answer. Let’s send it to Telegram bot.

import requests  
from bottle import (  
    run, post, response, request as bottle_request
)

BOT_URL = 'https://api.telegram.org/bot<YOUR_TOKEN>/' # <--- add your telegram token here; it should be like https://api.telegram.org/bot12345678:SOMErAn2dom/


def get_chat_id(data):  
    """
    Method to extract chat id from telegram request.
    """
    chat_id = data['message']['chat']['id']

    return chat_id

def get_message(data):  
    """
    Method to extract message id from telegram request.
    """
    message_text = data['message']['text']

    return message_text

def send_message(prepared_data):  
    """
    Prepared data should be json which includes at least `chat_id` and `text`
    """ 
    message_url = BOT_URL + 'sendMessage'
    requests.post(message_url, json=prepared_data)  # don't forget to make import requests lib

def change_text_message(text):  
    """
    To enable turning our message inside out
    """
    return text[::-1]

def prepare_data_for_answer(data):  
    answer = change_text_message(get_message(data))

    json_data = {
        "chat_id": get_chat_id(data),
        "text": answer,
    }

    return json_data

@post('/')
def main():  
    data = bottle_request.json

    answer_data = prepare_data_for_answer(data)
    send_message(answer_data)  # <--- function for sending answer

    return response  # status 200 OK by default

if __name__ == '__main__':  
    run(host='localhost', port=8080, debug=True)

After all preparations and coding, if everything works, let’s try to chat with our bot.

How to Create and Deploy a Telegram Bot?

Now, let’s make our code more readable and implement OOP structure.

import requests  
from bottle import Bottle, response, request as bottle_request


class BotHandlerMixin:  
    BOT_URL = None

    def get_chat_id(self, data):
        """
        Method to extract chat id from telegram request.
        """
        chat_id = data['message']['chat']['id']

        return chat_id

    def get_message(self, data):
        """
        Method to extract message id from telegram request.
        """
        message_text = data['message']['text']

        return message_text

    def send_message(self, prepared_data):
        """
        Prepared data should be json which includes at least `chat_id` and `text`
        """       
        message_url = self.BOT_URL + 'sendMessage'
        requests.post(message_url, json=prepared_data)


class TelegramBot(BotHandlerMixin, Bottle):  
    BOT_URL = 'https://api.telegram.org/bot000000000:aaaaaaaaaaaaaaaaaaaaaaaaaa/'

    def __init__(self, *args, **kwargs):
        super(TelegramBot, self).__init__()
        self.route('/', callback=self.post_handler, method="POST")

    def change_text_message(self, text):
        return text[::-1]

    def prepare_data_for_answer(self, data):
        message = self.get_message(data)
        answer = self.change_text_message(message)
        chat_id = self.get_chat_id(data)
        json_data = {
            "chat_id": chat_id,
            "text": answer,
        }

        return json_data

    def post_handler(self):
        data = bottle_request.json
        answer_data = self.prepare_data_for_answer(data)
        self.send_message(answer_data)

        return response


if __name__ == '__main__':  
    app = TelegramBot()
    app.run(host='localhost', port=8080)

That’s pretty much it. Now you have a working Telegram bot that can even spell “racecar” backwards. Congrats!

Viewing all 22625 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>