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

Talk Python to Me: #371: pipx - Installable, Isolated Python Applications

$
0
0
I'm sure you're familiar with package managers for your OS even if you don't use them. On macOS we have Homebrew, Chocolatey on Windows, and apt, yum, and others on Linux. But if you want to install Python applications, you typically have to fallback to managing them with pip. Maybe you install them for your account with the --user flag. But with pipx you get a clean, isolated install for every Python application that you use. And if you distribute Python apps, pipx is a definitely worth considering as a channel.<br/> <br/> <strong>Links from the show</strong><br/> <br/> <div><b>Chad Smith</b>: <a href="https://twitter.com/cs01_software" target="_blank" rel="noopener">@cs01_software</a><br/> <b>Pipx</b>: <a href="https://github.com/pypa/pipx" target="_blank" rel="noopener">github.com</a><br/> <b>Entry Points</b>: <a href="https://dev.to/demianbrecht/entry-points-in-python-34i3" target="_blank" rel="noopener">dev.to</a><br/> <b>Python Packaging Dashboard</b>: <a href="https://chadsmith.dev/python-packaging/" target="_blank" rel="noopener">chadsmith.dev</a><br/> <b>MKDocStrings</b>: <a href="https://mkdocstrings.github.io/" target="_blank" rel="noopener">mkdocstrings.github.io</a><br/> <br/> <b>gdbgui</b>: <a href="https://github.com/cs01/gdbgui" target="_blank" rel="noopener">github.com</a><br/> <b>termpair</b>: <a href="https://github.com/cs01/termpair" target="_blank" rel="noopener">github.com</a><br/> <b>httpie</b>: <a href="https://httpie.io" target="_blank" rel="noopener">httpie.io</a><br/> <b>pls (ls-replacement)</b>: <a href="https://dhruvkb.github.io/pls/" target="_blank" rel="noopener">dhruvkb.github.io</a><br/> <b>Glances</b>: <a href="https://nicolargo.github.io/glances/" target="_blank" rel="noopener">nicolargo.github.io</a><br/> <b>Watch this episode on YouTube</b>: <a href="https://www.youtube.com/watch?v=wZ5icmr7qnE" target="_blank" rel="noopener">youtube.com</a><br/> <b>Episode transcripts</b>: <a href="https://talkpython.fm/episodes/transcript/371/pipx-installable-isolated-python-applications" target="_blank" rel="noopener">talkpython.fm</a><br/> <br/> <b>--- Stay in touch with us ---</b><br/> <b>Subscribe to us on YouTube</b>: <a href="https://talkpython.fm/youtube" target="_blank" rel="noopener">youtube.com</a><br/> <b>Follow Talk Python on Twitter</b>: <a href="https://twitter.com/talkpython" target="_blank" rel="noopener">@talkpython</a><br/> <b>Follow Michael on Twitter</b>: <a href="https://twitter.com/mkennedy" target="_blank" rel="noopener">@mkennedy</a><br/></div><br/> <strong>Sponsors</strong><br/> <a href='https://talkpython.fm/mergify'>Mergify</a><br> <a href='https://talkpython.fm/python-at-scale'>Python at Scale</a><br> <a href='https://talkpython.fm/assemblyai'>AssemblyAI</a><br> <a href='https://talkpython.fm/training'>Talk Python Training</a>

STX Next: Python for Machine Learning: Why Use Python for ML?

$
0
0

Known for its versatility and stability, Python is increasingly becoming an object of interest for those dabbling in machine learning or willing to carry out a machine learning project. As they quickly notice the difference between a standard software development project and an ML one, they search for tools and solutions that will respond to ML-specific needs.

STX Next: Top Resources for Machine Learning in Python: How to Get Started

STX Next: Django vs. Ruby on Rails Framework Comparison: Which Is Better?

$
0
0

Web frameworks are used by developers to create fast, efficient, and easy-to-use websites. But why exactly do we need them? And why is choosing the right one so important to your business?

Tryton News: Newsletter July 2022

$
0
0

Here are some improvements that have already landed in the development tree for the next release 6.6.

Changes for the User

We now show the number of email addresses that are subscribed to a marketing mailing list.

The commission on products can now be included in the stock margin report.

It is now possible to define marketing scenarios from which user can unsubscribe. For example with a scenario that reminds the customer of pending sales, the customer can now unsubscribe for all sales instead of just the one that is pending.

When inactivating a location, Tryton does not check whether there are still consumable products in it.

The types of UPS service have been updated.

It is now possible to define the start and end date for a relationship between parties. Once the relationship has ended those relationships are no longer active.

Changes for the System Administrator

We now use a delay instead of a datetime in UTC for the reset password expiration. This is easier to understand for the user.

Changes for the Developer

The TableHandlers are now a singleton per table. This avoids having to reload the table definitions between instantiations in different modules.

The product lead time is now a MultiValue so it can be customized to depend on the warehouse, the company etc.

1 post - 1 participant

Read full topic

Python Software Foundation: Board Election Results for 2022!

$
0
0

Congratulations to everyone who won a seat on the PSF Board! We’re so excited to work with you. New and returning Board Directors will start their three year terms this month at the next PSF board meeting. Thanks to everyone else who ran this year and added their voice to the conversation about the future of the Python community. It was a tough race with many amazing candidates. 

  • Kushal Das
  • Jannis Leidel
  • Dawn Wages
  • Simon Willison

Thanks to everyone who voted and helped us spread the word! We really appreciate that so many of you took the time to participate in our elections this year. By the numbers, we sent ballots to 1,459 voting PSF members who then chose among 26 nominees. By the end 578 votes were cast, well over the one third required for quorum.

While the board elections are over, there are lots of other ways to get involved with the Python community. Here are a few things you can get started with or you could consider serving with one of our working groups. If you want to start participating on the technical side of things, you might want to check out our forum. Next year’s board elections will happen at approximately the same time in 2023. If you want to make sure you are notified via email, join the psf-vote mailing list.

ItsMyCode: [Solved] TypeError: __init__() missing 2 required positional arguments

$
0
0

If we are instantiating the class that accepts 2 required positional parameters and if we do not pass those 2 required arguments Python interpreter will throw TypeError: __init__() missing 2 required positional arguments

In this tutorial, we will look at what exactly TypeError: __init__() missing 2 required positional arguments and how to resolve this error with examples.

What is TypeError: __init__() missing 2 required positional arguments

Let us take a simple example to demonstrate this issue.

class Student():
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def get_full_name(self):
        return "".join([self.first_name, self.last_name])


# No Positional arguments are passed while instantiating the class
student1 = Student()
print(student1.get_full_name())

Output

TypeError: Student.__init__() missing 2 required positional arguments: 'first_name' and 'last_name'

In the above code, we have a class called Student() and a constructor that takes 2 positional arguments. The class also has a method get_full_name() method that combines the first name, and last name and returns the full name of the Student.

In the following statement, we instantiate the class Student() without passing any required positional arguments, and hence, we get the TypeError: Student.init() missing 2 required positional arguments: ‘first_name’ and ‘last_name’

How to fix TypeError: __init__() missing 2 required positional arguments

There are different ways to resolve this TypeError. Let us look at each of these solutions with examples.

Solution 1 – Pass the required positional arguments

The easy way to resolve the error is to pass the required positional arguments that are specified in the __init__() method while instantiating the class.

Like any other programming language C#, C++, etc Python has its own way of defining the constructor through __init__() method.

The __init__() function is called every time an object is created from a class and it allows the class to initialize the attributes of the class.

If we do not define the __init__() function or the constructor then we do not have to pass any arguments while instantiating the class.

Here in our example, the Student class has __init__() function and it takes two positional arguments, and we can solve the issue by providing the positional arguments while instantiating the class as shown below.

class Student():
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def get_full_name(self):
        return "".join([self.first_name, self.last_name])


student1 = Student("Chandler", "Bing")
print(student1.get_full_name())

Output

Chandler Bing

Solution 2 – Set the default values for the arguments

In Python, the function arguments can have the default values and we can set the default values in the __init__() function too.

After setting the default values we do not have to pass any argument values while instantiating the class, the default values that are defined in the __init__() will be used instead of throwing a TypeError.

The default values are set using the assignment operator (=). The syntax will be in the form of keyword=value.

Let us see how we can implement the default values in our example and resolve the issue.

class Student():
    def __init__(self, first_name="", last_name=""):
        self.first_name = first_name
        self.last_name = last_name

    def get_full_name(self):
        if not self.first_name and not self.last_name:
            return "The first name and last name is empty"

        else:
            return "".join([self.first_name, self.last_name])


student1 = Student("Chandler", "Bing")
print(student1.get_full_name())

student2 = Student()
print(student2.get_full_name())

Output

Chandler Bing
The first name and last name is empty

In the above example, we are not passing the required positional arguments while instantiating the class. However, the default values we have set in the __init__() function args are taken.

Here you need to ensure that the default values are set only on the value types and not on the reference types.

The reference types such as Dictionary, List, Array, Tuple, Set, etc., can cause different issues, as shown below.

Notice that the method takes the empty dictionary as the default value when we do not pass any arguments.

Here both emp1 and emp2 objects hold the reference of the address dictionary object, and changing the emp1 object will implicitly change the emp2 object, as demonstrated in the below code.

class Employee():
    def __init__(self, address={}):
        self.address = address

    def get_full_address(self):
        return self.address


emp1 = Employee()
print("Employee 1 address", emp1.get_full_address())
emp1.address["city"] = "Bangalore"

emp2 = Employee()
print("Employee 2 address", emp2.get_full_address())

Output

Employee 1 address {}
Employee 2 address {'city': 'Bangalore'}

As shown below, we can fix this issue by setting the default argument to None and conditionally returning the empty dictionary if the parameter is None.

class Employee():
    def __init__(self, address=None):
        self.address = address
        if address is None:
            self.address = {}

    def get_full_address(self):
        return self.address


emp1 = Employee()
print("Employee 1 address", emp1.get_full_address())
emp1.address["city"] = "Bangalore"

emp2 = Employee()
print("Employee 2 address", emp2.get_full_address())

Output

Employee 1 address {}
Employee 2 address {}

Conclusion

The TypeError: __init__() missing 2 required positional arguments occurs if we do not pass the 2 required positional arguments while instantiating the class.

We can resolve the issue by passing the required positional arguments while instantiating the class or by setting the default values for the arguments using the assignment operator.

Real Python: The Real Python Podcast – Episode #116: Exploring Functional Programming in Python With Bruce Eckel

$
0
0

Would you like to explore the functional programming side of Python? What are the advantages of this approach, and what tools are built into the language? This week on the show, author Bruce Eckel talks about functional programming in Python.


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


Mike Driscoll: Python 101 - An Intro to Classes (Video)

$
0
0

Learn the basics of Classes and get your first taste of Object Oriented Programming in Python in this tutorial

You will learn about:

  • Class creation
  • self -- what it means
  • Subclass creation
  • Polymorphism

Want to learn more?

Buy my book, Python 101 - 2nd Edition:

The post Python 101 - An Intro to Classes (Video) appeared first on Mouse Vs Python.

Ian Ozsvald: Upcoming discussion calls for Team Structure and Buidling a Backlog for data science leads

$
0
0

I ran another Executives at PyData discussion session for 50+ leaders at our PyDataLondon conference a couple of weeks back. We had great conversation which dug into a lot of topics. I’ve written up notes on my NotANumber newsletter. If you’re a leader of DS and Data Eng teams, you probably want to review those notes.

To follow on the conversations I’m going to run the following two (free) Zoom based discussion sessions. I’ll be recording the calls and adding notes to future newsletters. If you’d like to join, fill in this invite form and I can add you to the calendar invite. You can lurk and listen or – better – join in with questions.

  • Monday July 11, 4pm (UK time), Data Science Team Structure– getting a good structure for your org, hybrid vs fully remote practices, processes that support your team, how to avoid being left out
  • Monday August 8th, 4pm (UK time), Backlog & Derisking & Estimation– how to build a backlog, derisking quickly and estimating the value behind your project

I’m expecting a healthy list of issues and good feedback and discussion for both calls. I’ll be sharing an agenda in advance to those who have contacted me. My goal is to turn these into bigger events in the future.


Ian is a Chief Interim Data Scientist via his Mor Consulting. Sign-up for Data Science tutorials in London and to hear about his data science thoughts and jobs. He lives in London, is walked by his high energy Springer Spaniel and is a consumer of fine coffees.

Python for Beginners: Convert String to Set in Python

$
0
0

Strings are used to manipulate textual data in python. Sometimes, we might need to find the total number of different characters in a text. In such situations, we can convert a string into a set. In this article, we will discuss different ways to convert a string to a set in python. 

Convert String to Set in Python Using set() Function

The set() function is used to create a set in Python. It takes an iterable object as its input argument and returns a set containing the elements in the iterable object. 

As we know that a string is an iterable object, we can obtain a set from a string in python using the set() function. For this, we will pass the string to the set() function as its input argument. After executing the set() function, we will get the set with all the characters of the input string. You can observe this in the following example.

myStr = "pythonforbeginners"
mySet = set(myStr)
print("The input string is:", myStr)
print("The output set is:", mySet)

Output:

The input string is: pythonforbeginners
The output set is: {'f', 'i', 'g', 'y', 'o', 'r', 't', 'h', 'p', 'n', 'b', 'e', 's'}

In the above example, you can observe that we have passed the string pythonforbeginners as the input to the set() function. After execution, it has returned a set containing the characters in the string.

Convert String to Set in Python Using Set Comprehension

Set comprehension is used to create a new set from an existing iterable object. The syntax of set comprehension is as follows.

newSet= { expression for element in  iterable }

To convert the string to a set using set comprehension in Python, we will use the input string as the iterable and the characters of the string as the element as well as the expression. After execution, the above statement will convert the string to set. You can observe this in the following example.

myStr = "pythonforbeginners"
mySet = {character for character in myStr}
print("The input string is:", myStr)
print("The output set is:", mySet)

Output:

The input string is: pythonforbeginners
The output set is: {'i', 'n', 'o', 's', 't', 'h', 'b', 'g', 'e', 'r', 'p', 'y', 'f'}

In the above example, we have used set comprehension to convert pythonforbeginners to a set in Python.

String to Set in Python Using the add() Method

The add() method is used to add an element to a set. When invoked on a set, the add() method takes an element as its input argument. After execution, it adds the element to the set if it doesn’t already exist in the set. If the element already exists in the set, nothing happens.

To convert a string to a set using the add() method in python, we will use the following steps.

  • First, we will create an empty set named mySet. For this, we will use the set() function. The set() function, when executed without any argument, returns an empty set.
  • After creating an empty set, we will iterate through characters of the input string using a for loop.
  • During iteration, we will invoke the add() method on mySet and add each character to mySet.
  • After executing the for loop, we will get the output set in the variable mySet. You can observe this in the following example.
myStr = "pythonforbeginners"
mySet = set()
for character in myStr:
    mySet.add(character)
print("The input string is:", myStr)
print("The output set is:", mySet)

Output:

The input string is: pythonforbeginners
The output set is: {'h', 'n', 'g', 'f', 'i', 'b', 'o', 'p', 't', 'e', 's', 'r', 'y'}

In this example, we have used the add() method to convert the string pythonforbeginners to a set.

Conclusion

In this article, we have discussed three ways to convert a string to a set in Python. Out of all the three methods, you can use the approach using the set() function if you need to include all the characters of the string in the set.

If you need to exclude some of the characters of the input string from the set, you can use the approach with the add() method or set comprehension. To know more about python programming, you can read this article on string concatenation in python. You might also like this article on dictionary comprehension in Python.

I hope you enjoyed reading this article. Stay tuned for more informative articles.

Happy Learning!

The post Convert String to Set in Python appeared first on PythonForBeginners.com.

EuroPython: EuroPython June 2022 Newsletter

$
0
0

Howdy-doody!

Time flies like an arrow! Can you hear them buzzing? Only ten days to go until the conference. Our days right now start with discord pings, are full of zoom meetings and google hangouts and end with “Crap! we forgot to send that email”. Then, repeat it all over again. We’re nervously sprinting towards a cross-timezone, multi-cultural culmination of time, effort and experience at EuroPython.

This will be our last update ahead of the conference. We’ll be back in July with a conference recap and our vision for the newsletter ahead (if you have any suggestions, send us those at news@europython.eu).
Alright, enough said! We hope you enjoy this newsletter as much as we enjoyed penning it down.

📝 EuroPython Society Update


🧠 EuroPython Society discussion at EuroPython

We would like to use the conference days to talk to all of you and other members of the community to gather ideas about our conference series and other EPS initiatives.
In particular, How do you think EPS can serve the needs of our thriving community? How can we make EuroPython more inclusive and diverse? To do so, we’ll organise an informal discussion session on Thursday 14th July in the Open Space at EuroPython. We&aposll announce the exact time soon.

Bring your thoughts, comments, questions and ideas!! Hope to see many of you!

🥗 Organisers’ Lunch @ EuroPython

Are you coming to EuroPython and you are a community organiser of Python or Open Source events? Join us for an informal lunch at the conference! We will have food and discussions on 14 July at 13:00. You can let us know if you are interested in emailing board@europython.eu, or just showing up on the day!

📅 General Assembly postponed

The board has decided to postpone the General Assembly until after the conference to give us more time to prepare and plan. We’ll keep you updated on the new date.

🍀 EuroPython 2022 Conference Update

🐍 EuroPython at a glance!

~115 sessions - Across the Python experience spectrum and through all tracks (featuring a dedicated CPython, PyData, Web, Testing, Community track and much more), we have put together an intriguing set of talks for you to enjoy. Check them all out at https://ep2022.europython.eu/sessions

12 tutorials - From looking at Python under the hood to building production-ready GraphQL API to Train your Graphics card& Build with Audio. No matter what you’re interested in, we’ve got something for you. Piqued your interest? Head over to the full list here: https://ep2022.europython.eu/tutorials

8 Special events - EuroPython is much bigger than *just* talks and tutorials. We’ll have multitudes of events running parallel to the event. Check them out here to get to know more about it and how you can participate:

4 Keynotes - EuroPython wouldn’t be complete without showcasing some of the brightest voices leading our community talking about the current challenges we face and offering their take on them. Interested in knowing more about what they’ll talk about? https://ep2022.europython.eu/keynoters

altMeet our awesome Keynoters!

3 Panels - All three days of the conference will feature star-studded and thought-provoking panel discussions on the topics that matter most. We’ll start with a CPython panel with some of our favourite core devs, followed by the Diversity and Inclusion panel on Thursday with our friends from The PSF and conclude with an Education panel with a Pythonic focus on Friday.

🌍 Join EuroPython from anywhere!

EuroPython is a conference for the community, by the community. In these ever-so-changing times, we know that it might not be feasible for everyone to make it to Dublin and attend the conference in person. That’s why EuroPython is running hybrid this year. We’ll have remote speakers as well as attendees.

⚠️
You can now take advantage of all the talks and panels right at the comfort of your home. Head over to https://ep2022.europython.eu/tickets to get yours now!

P.S. We’re offering financial aid for those who may benefit from them. Head over to https://ep2022.europython.eu/finaid to apply now! The deadline is 3rd July.

💝 Want to engage more deeply with EuroPython & our community?


🌌 Open Space
We believe that conversations flow better when provided with a dedicated space to present your ideas, and host discussions and prototype insights together.

The conference will feature an Open Space from 13th-15th July. Any ticket holder will be able to reserve a time block there and propose an activity for other attendees to participate in.

P.S. Be early, the space tends to get overbooked soon ;)

🖥 OSS Tables
Are you an Open Source library/ organisation and would like a dedicated table to showcase your work and engage with the attendees? Then say no more and send us an email at sponsoring@europython.eu (limited spots, first come first serve basis).

⚡ Lightning Talks
A EuroPython wouldn’t be EuroPython without its goofy, byte-sized 5-minute entertaining Lightning talks. All the 3 conference days (13th-15th July) will feature Lightning Talks. Best part? Anyone can propose one! Get your groove on already and start putting ideas and stitching slides together.
No separate registration is needed, just submit a topic and put your name on the white scratchboard in the forum.

✨ Humans of EuroPython!

EuroPython won’t be what it is without the brilliant people that put in their time and effort. Every little contribution matters, calling vendors during lunch breaks, taking a trip across Europe to inventory our storage, taking team calls from a bar ;) and much more. Being a volunteer is not easy and we want to showcase the people striving to put together a better and more inclusive environment for our thriving community.

Humans of EuroPython is an initiative to showcase such voices. It’ll be a monthly issue (just like this newsletter) with an interview-style chat with our volunteers.

For our inaugural blog post, we spotlight Laís (@lais_bsc), where she tells us about her volunteering, Python Ireland and what she&aposs up to right now.
https://blog.europython.eu/a-chat-with-lais-carve/

Our second edition contains words of wisdom by Naomi (@NaomiCeder), who needs no introduction. She reflects on the work of Trans*Code and thoughtfully reveals a trans perspective on what it&aposs like to be in our community.
https://blog.europython.eu/pythonista-spotlight-naomi-ceder/

🤗 A big thanks to our Sponsors!

EuroPython won’t be possible without the support of our fantastic sponsors. We’re beyond psyched that we’ll be able to hang by their booths at the conference. Grab some cool sponsor swag? Discuss some interesting job offers? And participate in some fun giveaways!

altOur sponsors are what help us make EuroPython affordable for everyone!

🎗️ Upcoming Events

🗓️
If you have a fantastic Python event and want to be featured, hit the reply button and write to us!

Farset Labs (Belfast): Maker Night in the Workshop 🛠 (Mon Jun 27, 19:00 - 21:00 BST)
Come down to Farset Labs Hackerspace this Monday for our Maker Night in the workshop. Bring your own ideas, or just come to see the pretty burning lights from the safety of our Glowforge Pro laser cutter.
RSVP: https://www.meetup.com/farsetlabs/events/glbgtsydcjbkc/

Tog Hackerspace (Dublin): Electronics Night (Mon Jun 27, 19:00 - 21:00 Irish Time)
This is our regular Monday evening electronics night. The space and our electronics room are still a work in progress.
These evenings are free to attend for members and visitors alike. No booking is necessary, just turn up. Everyone from absolute beginners to experienced is welcome. These evenings are not classes or tutorials but a very friendly informal evening.
RSVP: https://www.meetup.com/tog-dublin-hackerspace/events/mpvcvsydcjbkc/

Dublin Data Science: Shorter Talk Sessions (Wed Jun 29, 18:30 - 20:00 Irish Time)
This is an in-person event hosted by Dogpatch Labs, there will be two short talks: “Data science at a health hardware startup” and “How and Why to make Climate Graphs”.
RSVP: https://www.meetup.com/dublin-data-science/events/286019460/

Dublin Linux Developers with Gravitee (Sat Jul 2, 18:00 - 19:00 Irish Time)
This is a remote event. Nico Balestra from Gravitee talks on "Getting Started with Gravitee"
RSVP: https://www.meetup.com/dublin-linux-developers/events/285252292/

Women in AI Ireland Meetup (Sat Jul 9, 13:00 - 16:00 Irish Time)
Celebrating our third year in Ireland, and following nearly two years of online presence, Women in AI are re-launching in-person events in Ireland! Secure your place today for our upcoming event on the 9th of July at Platform X, Harbourmaster Place, KPMG, Dublin proudly sponsored by our long-time supporters KPMG Ireland and Insight SFI Centre for Data Analytics!
RSVP: https://www.eventbrite.ca/e/wai-meet-up-tickets-354820506687

Dublin Linux Community (Sat Jul 9, 15:00 - 17:00 Irish Time)
This is a remote event. will be having an in-person event later in the month.
RSVP: https://www.meetup.com/dublin-linux-community/events/286540864/

Python Ireland July Meetup (Wed Jul 13, 18:30 - 21:00 Irish Time)
In EuroPython, the folks at Python Ireland will have a social meetup with finger food at the pub Against the Grain. Registration required.
More details at: https://www.meetup.com/pythonireland/events/280640457/

PyLadies Dublin July Meetup (Tue Jul 19, 18:30 - 19:30 Irish Time)
We are excited to have 3 excellent speakers from “How to encourage a teenage girl to programming” to “How to start a career in python” and “Supply Chain Security in OSS”. This is a virtual event and will be live-streamed to our Youtube channel.
RSVP: https://www.meetup.com/pyladiesdublin/events/286594152/

Dublin Linux Community (Sat Jul 23, 15:00 - 17:00 Irish Time)
We are trialling coming back to meeting in person. The venue may be subject to change as things settle down. We also have an online event at the start of every month.
RSVP: https://www.meetup.com/dublin-linux-community/events/dbgcssydckbfc/

Women Who Code Belfast: Bytes n&apos Brew Online ☕️💻 - Breakfast (Thu Jul 28, 08:00-09:00 BST)
Bytes n&apos Brew is a fortnightly social event, a relaxed space designed to give our community an opportunity for conversations and connections around busy/work-life schedules. Join online to make new friends in the community, and ask others for career advice, coding help or mentoring.
RSVP: https://www.meetup.com/Women-Who-Code-Belfast/events/jzzgssydckblc/

🤙 Editor&aposs note

Hey hey! If you made it this far, then first of all thanks. I am Vaibhav/ VB (@reach_vb), a volunteer with the EuroPython 2022 team. Throughout this year I’ve had a small role in putting these Newsletters together. Personally, for me, these newsletters are much more than just a conference update, they provide an insight into the structured chaos that EuroPython is.

This is the 6th and the final edition before the conference. From January to today we&aposve gone through quite a transformation. Ahead of the conference, I’d like to highlight and thank everyone who helped put this series together (in alphabetical order):

Artur Czepiel (@artcz)
Diego Russo (@diegor)
Laís Carvalho (@lais_bsc)
Raquel Dou (raquel@europython.eu)
Sebastiaan Zeeff (@SebastiaanZeeff)
Vicky Twomey-Lee (@whykay)
Yasmin Morshed (@yasmin_codes)

They are all a fantastic bunch of people and it was/ is an honour to have the privilege of working with them. Find these people during the conference (or online)  and give them a hug, a cookie and thank them for their hard work!

Alright, enough sentimental stuff! The Newsletter is here to stay, but we’re keen to hear your suggestions and feedback on how we can make it more useful for you. Send us your ideas at news@europython.eu or find us at the conference.

See you at the conference, on social or in your inbox soon,

VB on behalf of the EuroPython Organisers

death and gravity: reader 2.14 released

$
0
0

Hi there!

I'm happy to announce version 2.14 of reader, a Python feed reader library.

What's new?#

Here are the most important changes since reader 2.11!

Twitter support#

You can now use reader to follow Twitter accounts.

You still need a Twitter account to get a bearer token, but after that, https://twitter.com/user works like any other feed.

Each thread corresponds to an entry. Threads are rendered as HTML, but you can access the original JSON too, in case you want to do your own rendering.

Here's what it looks like in the web app:

thread with quote thread with quote
thread with media thread with media

Read time#

The new readtime plugin calculates the read time of an entry during feed update.

This makes available to any reader user a feature that was only available in the web app, and makes the web app faster.

Typing#

You can now type check code that uses reader.

reader has had type annotations for most of its existence, but user code would fail type checking because reader did not explicitly declare it.

Python versions#

reader 2.14 drops support for Python 3.7, and adds support for PyPy 3.9.

Bug fixes#

reader now skips RSS entries that have no <guid> or <link> with a warning, instead of failing the entire feed. Thanks to Mirek Długosz for the pull request.

For more details, see the full changelog.


That's it for now.

Learned something new today? Share this with others, it really helps!

What is reader?#

reader takes care of the core functionality required by a feed reader, so you can focus on what makes yours different.

reader in actionreader allows you to:

  • retrieve, store, and manage Atom, RSS, and JSON feeds
  • mark articles as read or important
  • add arbitrary metadata to feeds and articles
  • filter feeds and articles
  • full-text search articles
  • get statistics on feed and user activity
  • write plugins to extend its functionality

...all these with:

  • a stable, clearly documented API
  • excellent test coverage
  • fully typed Python

To find out more, check out the GitHub repo and the docs, or give the tutorial a try.

Why use a feed reader library?#

Have you been unhappy with existing feed readers and wanted to make your own, but:

  • never knew where to start?
  • it seemed like too much work?
  • you don't like writing backend code?

Are you already working with feedparser, but:

  • want an easier way to store, filter, sort and search feeds and entries?
  • want to get back type-annotated objects instead of dicts?
  • want to restrict or deny file-system access?
  • want to change the way feeds are retrieved by using Requests?
  • want to also support JSON Feed?

... while still supporting all the feed types feedparser does?

If you answered yes to any of the above, reader can help.

Why make your own feed reader?#

So you can:

  • have full control over your data
  • control what features it has or doesn't have
  • decide how much you pay for it
  • make sure it doesn't get closed while you're still using it
  • really, it's easier than you think

Obviously, this may not be your cup of tea, but if it is, reader can help.

Test and Code: 191: Running your own site for fun and absolutely no profit whatsoever - Brian Wisti

$
0
0

Having a personal site is a great playground for learning tons of skills. Brian Wisti discusses the benefits of running a his own blog over the years.

Special Guest: Brian Wisti.

Sponsored By:

Links:

<p>Having a personal site is a great playground for learning tons of skills. Brian Wisti discusses the benefits of running a his own blog over the years.</p><p>Special Guest: Brian Wisti.</p><p>Sponsored By:</p><ul><li><a href="http://rollbar.com/testandcode" rel="nofollow">Rollbar</a>: <a href="http://rollbar.com/testandcode" rel="nofollow">With Rollbar, developers deploy better software faster.</a></li></ul><p>Links:</p><ul><li><a href="https://randomgeekery.org/" title="Random Geekery" rel="nofollow">Random Geekery</a></li><li><a href="https://en.wikipedia.org/wiki/Jamstack" title="Jamstack" rel="nofollow">Jamstack</a></li><li><a href="https://www.11ty.dev/" title="Eleventy" rel="nofollow">Eleventy</a></li><li><a href="https://www.netlify.com/" title="Netlify" rel="nofollow">Netlify</a></li><li><a href="https://plausible.io/" title="Plausible Analytics" rel="nofollow">Plausible Analytics</a></li><li><a href="https://pythontest.com/pytest-book/" title="pytest" rel="nofollow">pytest</a></li><li><a href="https://beautiful-soup-4.readthedocs.io/en/latest/" title="Beautiful Soup" rel="nofollow">Beautiful Soup</a></li><li><a href="https://www.pyinvoke.org/" title="pyinvoke - Invoke!" rel="nofollow">pyinvoke - Invoke!</a></li><li><a href="https://en.wikipedia.org/wiki/Rsync" title="rsync" rel="nofollow">rsync</a></li><li><a href="https://archive.org/" title="Internet Archive : archive.org" rel="nofollow">Internet Archive : archive.org</a></li><li><a href="https://rich.readthedocs.io/en/stable/introduction.html" title="Rich" rel="nofollow">Rich</a></li><li><a href="https://statamic.com/" title="Statamic" rel="nofollow">Statamic</a></li><li><a href="https://jamstack.org/" title="jamstack.org" rel="nofollow">jamstack.org</a></li><li><a href="https://www.slideshare.net/genehackdotorg/a-static-site-generator-should-be-your-next-language-learning-project" title="A static site generator should be your next language learning project" rel="nofollow">A static site generator should be your next language learning project</a></li></ul>

Kay Hayen: Nuitka Release 0.9

$
0
0

This is to inform you about the new stable release of Nuitka. It is the extremely compatible Python compiler, “download now”.

This release has a many optimization improvements, and scalability improvements, while also adding new features, with also some important bug fixes.

Bug Fixes

  • Fix, hard module name lookups leaked a reference to that object. Fixed in 0.8.1 already.

  • Python2: Fix, str.decode with errors as the only argument wasn’t working. Fixed in 0.8.1 already.

  • Fix, could corrupt created uncompiled class objects __init__ functions in case of descriptors being used.

  • Standalone: Added support for newer torch. Fixed in 0.8.1 already.

  • Standalone: Added support for newer torchvision. Fixed in 0.8.1 already.

  • Fix, could compile time crash during initial parsing phase on constant dictionary literals with non-hashable keys.

    {{}:1,}
  • Fix, hard imported sub-modules of hard imports were falsely resolved to their parent. Fixed in 0.8.3 already.

    importlib.resources.read_bytes()# gave importlib has no attribute...
  • Windows: Fix, outputs with --force-stdout-spec or --force-stderr-spec were created with the file system encoding on Python3, but they nee to be utf-8.

  • Fix, didn’t allow zero spaces in Nuitka project options, which is not expected.

  • Modules: Fix, the del__file__ in the top level module in module mode caused crashes at runtime, when trying to restore the original __file__ value, after the loading CPython corrupted it.

  • Python2.6: Fixes for installations without pkg_resources.

  • Windows: Fix for very old Python 2.6 versions, these didn’t have a language assigned that could be used.

  • Security: Fix for CVE-2022-2054 where environment variables used for transfer of information between Nuitka restarting itself, could be used to execute arbitrary code at compile time.

  • Anaconda: Fix, the torch plugin was not working on Linux due to missing DLL dependencies.

  • Fix, static optimization of importlib.import_module with a package given, for an absolute import was optimized into the wrong import, package was not ignored as it should be.

  • Windows: Installed Python scan could crash on trying installation paths from registry that were manually removed in the mean time, but not through an uninstaller.

  • Standalone: Added missing implicit dependency for pyreadstat because parts of standard library it uses are no more automatically included.

  • Windows: Could still crash when no powershell is available with symlinks, handle this more gracefully.

  • Standalone: Added more missing Plotly dependencies, but more work will be needed to complete this.

  • Standalone: Add missing stdlib dependency on multiprocessing by concurrent.futures.process.

  • Standalone: Fix, implicit dependencies assigned to imageio on PIL plugins should actually be assigned to PIL.Image that actually loads them, so it works outside of imageio too.

New Features

  • UI: Added new option --user-package-configuration-file to allow users to provide extra Yaml configuration files for the Nuitka plugin mechanism to add hidden dependencies, anti-bloat, or data files, for packages. This will be useful for developing PRs to the standard file of Nuitka. Currently the schema is available, but it is not documented very well yet, so not really ready for end users just yet.

  • Standalone: Added new no-qt plugin as an easy way to prevent all of the Qt bindings from being included in a compilation.

  • Include module search path in compilation report.

Tests

  • The reflected test was adapted to preserve PYTHONPATH now that module presence influences optimization.

Optimization

  • Faster dictionary iteration with our own replacement for PyDict_Next that avoids the DLL call overhead (in case of non-static libpython) and does less unnecessary checks.

  • Added optimization for str.count and str.format methods as well, this should help in some cases with compile time optimization.

  • The node for dict.update with only an iterable argument, but no keyword arguments, was in fact unused due to wrongly generated code. Also the form with no arguments wasn’t yet handled properly.

  • Scalability: Use specialized nodes for pair values, i.e. the representation of x=y in e.g. dictionary creations. With constant keys, and values, these avoid full constant value nodes, and therefore save memory and compile time for a lot of code.

  • Anti-bloat: Added more scalability work to avoid including modules that make compilation unnecessarily big.

  • Python3.9+: Faster calls in case of mixed code, i.e. compiled code calling uncompiled code.

  • Removing duplicates and non-existent entries from modules search path should improve performance when locating modules.

  • Optimize calls through variables as well. These are needed for the package resource nodes to properly resolve at compile time from their hard imports to the called function.

  • Hard imported names should also be considered very trusted themselves, so they are e.g. also optimized in calls.

  • Anti-bloat: Avoid more useless imports in Pandas, Numba, Plotly, and other packages, improving the scalability some more.

  • Added dedicated nodes for pkg_resources.require, pkg_resources.get_distribution, importlib.metadata.version, and importlib_metadata.version, so we can use compile time optimization to resolve their argument values where possible.

  • Avoid annotating control flow escape for all release statements. Sometimes we can tell that __del__ will not execute outside code ever, so this then avoids marking values as escaped, and taking the time to do so.

  • Calls of methods through variables on str, dict, bytes that have dedicated nodes are now also optimized through variables.

  • Boolean tests through variables now also are optimized when the original assignment is a compile time constant that is not mutable. This is only basic, but will allow tests on TYPE_CHECKING coming from a fromtypingimportTYPE_CHECKING statement to be optimized, avoiding this overhead.

Cleanups

  • Changed to torch plugin to Yaml based configuration, making it obsolete, it only remains there for a few releases, to not crash existing build scripts.

  • Moved older package specific hacks to the Yaml file. Some of these were from hotfixes where the Yaml file wasn’t yet used by default, but now there is no need for them anymore.

  • Removed most of the pkg-resources plugin work. This is now done during optimization phase and rather than being based on source code matches, it uses actual value tracing, so it immediately covers many more cases.

  • Continued spelling improvements, renaming identifiers used in the source that the cspell based extension doesn’t like. This aims at producing more readable and searchable code.

  • Generated attribute nodes no longer do local imports of the operation nodes they refer to. This also avoids compile time penalties during optimization that are not necessary.

  • Windows: Avoid useless bytecode of inline copy used by Python3 when installing for Python2, this spams just a lot of errors.

Organisational

  • Removed MSI installers from the download page. The MSI installers are discontinued as Python has deprecated their support for them, as well as Windows 10 is making it harder for users to install them. Using the PyPI installation is recommended on Windows.

  • Merged our Yaml files into one and added schema description, for completion and checking in Visual Code while editing. Also check the schema in check-nuitka-with-yamllint which is now slightly misnamed. The schema is in no way final and will see improvements in future releases.

  • UI: Nicer progress bar layout that avoids flicker when optimizing modules.

  • UI: When linking, output the total number of object files used, to have that knowledge after the progress bar for C compilation is gone.

  • Quality: Auto-format the package configuration Yaml file for anti-bloat, implicit dependencies, etc.

  • GitHub: Point out the commit hook in the PR template.

  • UI: Nicer output in case of no commercial version is used.

  • Updated the MinGW64 winlibs download used on Windows to the latest version based on gcc 11, the gcc 12 is not yet ready.

  • Git: Make sure we are not affected by core.autocrlf setting, as it interferes with auto-format enforcing Unix newlines.

  • Removed the MSI downloads. Windows 10 has made them harder to install and Python itself is discontinuing support for them, while often it was only used by beginners, for which it was not intended.

  • Anaconda: Make it more clear how to install static libpython with precise command.

  • UI: Warn about using Debian package contents. These can be non-portable to other OSes.

  • Quality: The auto-format now floats imports to the top for consistency. With few exceptions, it was already done like this. But it makes things easier for generated code.

Summary

This release marks a point, that will allow us to open up the compatibility work for implicit dependencies and anti-bloat stuff even further. The Yaml format will need documentation and potentially more refinement, but will open up a future, where latest packages can be supported with just updating this configuration.

The scalability improvements really make a difference for many libraries and are a welcome improvement on both memory usage and compile time. They are achieved by an accord of static optimization of

One optimization aimed at optimizing tuple unpacking, was not finished in time for this release, but will be subject of a future release. It has driven many other improvements though.

Generally, also from the UI, this is a huge step forward. With links to the website for complex topics being started, and the progress bar flicker being removed, the tool has yet again become more user friendly.


ItsMyCode: [Solved] ValueError: I/O operation on closed file

$
0
0

The i/o operations in Python are performed when the file is in an open state. So if we are trying to read or write into a file that is already closed state Python interpreter will raise the ValueError: I/O operation on closed file.

In this article, we will look at what is ValueError: I/O operation on closed file and how to resolve this error with examples.

ValueError: I/O operation on closed file

The best practice during file operations is to close the file as soon as we perform the operations with the file. It helps to free the resources such as memory and computing and also helps improve the overall performance. Once the file is closed, you can no longer access the file and perform read/write operations on that file.

There are three main common reasons why we face the ValueError: I/O operation on closed file.

  • First, when you try, forget to indent the code in the with statement.
  • Second, when you try to read/write to a file in a closed state.
  • Third, when you close the file inside a for loop, accidentally

Let us look at an example where we can reproduce the issue.

Scenario 1 – Improper Indentation

We have a simple code to read the CSV file, which consists of employee data. First, we have imported the CSV library, and then we used a with statement to open the CSV file in a read mode,

Once the file is opened, we call the method csv.reader() to read the file contents and assign the contents into the read_csv variable.

Later, using the for loop, we iterated the file contents and printed each file row.

Let us run this code and see what happens.

import csv

# Open the file in read mode
with open("employee.csv", "r") as employees:
    read_csv = csv.reader(employees)

# iterate and print the rows of csv
for row in read_csv:
    print("Rows: ", row)
Traceback (most recent call last):
  File "c:\Personal\IJS\Code\prgm.py", line 8, in <module>
    for row in read_csv:
ValueError: I/O operation on closed file.

Solution

The above code has returned ValueError because we tried to iterate the read_csv outside the with statement. The with statement acts as a file context, and once it’s executed, it will automatically close the file. Hence any file operation outside the with statement will lead to this error.

We can resolve the code by properly indenting our code and placing it inside the with statement context. But, first, let us look at the revised code.

import csv

# Open the file in read mode
with open("employee.csv", "r") as employees:
    read_csv = csv.reader(employees)

    # iterate and print the rows of csv
    for row in read_csv:
        print("Rows: ", row)

Output

Rows:  ['EmpID', 'Name', 'Age']
Rows:  ['1', 'Chandler Bing', ' 22']
Rows:  ['2', 'Jack', '21']
Rows:  ['3', 'Monica', '33']

Scenario 2 – Accessing the closed file

It is the most common and best practice to use with statements for accessing the files. We can also access the file without using the with statement by just using the open() method as shown below.

Once the file is open, using the csv.reader() method we have read the contents of the file and assigned it to a variable read_csv.

Then, we closed the file as the contents are read and stored to a variable. Next, we iterate the read_csv variable using a for loop and print each row of the CSV file.

Let us execute the code and see what happens.

import csv

# open file in read mode
employees = open("employee.csv", "r")
read_csv = csv.reader(employees)
employees.close()

# iterate and print the eac row
for row in read_csv:
    print("Rows: ", row)

Output

Traceback (most recent call last):
  File "c:\Personal\IJS\Code\prgm.py", line 9, in <module>
    for row in read_csv:
ValueError: I/O operation 

Solution

The fix here is straightforward; we need to ensure that the file is closed after the for loop. The read_csv file holds a reference of the file object, and if we close the file, it will not be able to access the file and perform any I/O operations.

Let us fix the issue and execute the code.

import csv

# open file in read mode
employees = open("employee.csv", "r")
read_csv = csv.reader(employees)

# iterate and print the eac row
for row in read_csv:
    print("Rows: ", row)

# close the file after iteraating and printing
employees.close()

Output

Rows:  ['EmpID', 'Name', 'Age']
Rows:  ['1', 'Chandler Bing', ' 22']
Rows:  ['2', 'Jack', '21']
Rows:  ['3', 'Monica', '33']

Scenario 3 – Closing the file inside a for loop

There could be another scenario where we do not correctly indent the code, which can lead to this issue.

For example, in the below code, we are opening the file and reading the file contents using csv.reader() method.

Next, using the for loop, we are iterating the CSV rows and printing each row. We have placed the file close() method inside the for loop accidentally, which will lead to an issue.

The loop executes and prints the first record successfully, and after that, it will close the file, and it won’t be able to access any other rows in the CSV.

import csv

# open file in read mode
employees = open("employee.csv", "r")
read_csv = csv.reader(employees)

# iterate and print the eac row
for row in read_csv:
    print("Rows: ", row)
    employees.close()

Output

Rows:  ['EmpID', 'Name', 'Age']
Traceback (most recent call last):
  File "c:\Personal\IJS\Code\prgm.py", line 8, in <module>
    for row in read_csv:
ValueError: I/O operation on closed file.

Solution

We can resolve the issue by placing the close() statement outside the for loop. This will ensure that file contents are iterated and printed correctly, and after that, the file is closed.

Let us revise our code and execute it.

import csv

# open file in read mode
employees = open("employee.csv", "r")
read_csv = csv.reader(employees)

# iterate and print the eac row
for row in read_csv:
    print("Rows: ", row)

employees.close()

Output

Rows:  ['EmpID', 'Name', 'Age']
Rows:  ['1', 'Chandler Bing', ' 22']
Rows:  ['2', 'Jack', '21']
Rows:  ['3', 'Monica', '33']

Conclusion

The ValueError: I/O operation on closed file occurs if we are trying to perform the read or write operations on the file when it’s already in a closed state.

We can resolve the error by ensuring that read and write operations are performed when the file is open. If we use the with statement to open the file, we must ensure the code is indented correctly. Once the with statement is executed, the file is closed automatically. Hence any I/O operation performed outside the with statement will lead to a ValueError.

ItsMyCode: ModuleNotFoundError: No module named ‘numpy’

$
0
0

In Python, if you try to import numpy without installing the module using pip, you will get ModuleNotFoundError: No module named ‘numpy’ error.

In this tutorial, let’s look at installing the numpy module correctly in different operating systems and solve no module named numpy error.  

ModuleNotFoundError: No module named ‘numpy’ 

Numpyare not a built-in module (it doesn’t come with the default python installation) in Python; you need to install it explicitly using the pip installer and then use it.

There are various reasons why we get the ModuleNotFoundError: No module named ‘numpy’ error

  • Trying to use NumPy without having the numpy package installed
  • The IDE you are using is not set to the incorrect Python version.
  • You are using the virtual environment and not installed the numpy in a virtual environment
  • Installing the NumPy package in a different version of Python than the one which is used currently.
  • Declaring a variable name as numpy

If you are getting an error installing pip, checkout pip: command not found to resolve the issue.

Install NumPy in OSX/Linux 

The recommended way to install the NumPy module is using pip or pip3 for Python3 if you have installed pip already.

Using Python 2

$ sudo pip install numpy

Using Python 3

$ sudo pip3 install numpy

Alternatively, if you have easy_install in your system, you can install NumPy using the below command.

Using easy install

$ sudo easy_install -U numpy

For CentOs

$ yum install numpy

For Ubuntu

To install the NumPy module on Debian/Ubuntu :

$ sudo apt-get install python-numpy

Install NumPy in Windows

In the case of windows, you can use pip or pip3 based on the Python version; you have to install the NumPy module.

$ pip3 install numpy

If you have not added the pip to the environment variable path, you can run the below command in Python 3, which will install the NumPy module. 

$ py -m pip install numpy

Install NumPy in Anacodna

If you use conda, you can install NumPy from the defaults or conda-forge channels:

# Best practice, use an environment rather than install in the base env
conda create -n my-env
conda activate my-env
# If you want to install from conda-forge
conda config --env --add channels conda-forge
# The actual install command
conda install numpy

The Python Coding Blog: Simulating a Tennis Match Using Object-Oriented Programming in Python—Wimbledon Special Part 1

$
0
0

With Wimbledon underway, I thought of paying homage to the classic tennis tournament with a program simulating a tennis match in Python. I’ll use this program to explore several key concepts in Object-Oriented Programming.

You’ll write a program which will allow you to do two things:

  • Part 1: You can keep the score of a live match by logging who wins each point and letting the program sort out the score
  • Part 2: You can simulate a tennis match point-by-point for players with different ranking points

This article covers Part 1. A separate, shorter article will deal with Part 2.

The key Python topic you’ll explore in this article is Object-Oriented Programming. You’ll learn about:

  • Creating classes in Python using the class keyword
  • Initialising objects using __init__()
  • Defining methods in a class
  • Creating string representations for the class using __str__() and __repr__()
  • Creating classes using inheritance

You don’t need to be familiar with object-oriented programming concepts to follow this tutorial. I’ll assume you’re familiar with Python’s built-in data types and defining functions with input parameters, including parameters with default values.

You can read more about functions in the chapter Functions Revisited in The Python Coding Book. There’s also a chapter on Object-Oriented Programming, which covers the basics of the topic. This tutorial will summarise the key points from that chapter and build on them.

A second article will follow this one in which the code from this article will be used to run simulations of thousands of tennis matches. These simulations will explore how various parameters affect the results of tennis matches.

How to Read This Article

I wrote this article in a way that you can easily choose how to consume its contents. For example, if you’re very familiar with the tennis scoring system, you can skip the next section.

If you’re already familiar with the basics of object-oriented programming and know how to create classes and instances, you can skim through the Object-Oriented Programming section.

You should be able to jump easily from one section to the next and still follow the article if you wish. But if you’re new to object-oriented programming in Python (and to tennis), then you can sit back and enjoy the full article…

The Tennis Scoring System

Tennis is not the most straightforward sport when it comes to scoring. If you’re already familiar with scoring in tennis, you can safely skip this section.

I’ll keep this summary of the tennis scoring system brief.

Match

A tennis match consists of a number of sets. Tennis matches are either best-of-three or best-of-five sets. A tennis match ends when a player wins two sets in a best-of-three match or three sets in a best-of-five match.

Set

Each set consists of several games. The target to win a set is six games. However, it’s not quite as straightforward:

  • If a player reaches six games and the opponent only has four or fewer games, the player with six games wins the set. The scores can therefore be 6-0, 6-1, 6-2, 6-3, or 6-4 under this section of the rules.
  • If both players have won five games each, then there are two options:
    • Either a player reaches seven games while the other is still on five. The player who reaches seven wins the set with a score of 7-5
    • If both players reach six games each, they play a special game called a tiebreak. There are slight variations on when and how the tiebreak is played. However, in this tutorial, I’ll assume that all sets that reach 6-6 will be settled using a tiebreak. When a player wins a set by winning the tiebreak, the set’s score is 7-6. This is the only time when there’s only one game difference between the set’s winner and loser.

Game

The player who starts the point is the server since the first delivery in a point is the serve. The same player serves throughout the entire game. The other player will then serve the following game, and they’ll keep alternating throughout the match.

Each game consists of several points. The first point a player wins is registered as “15” instead of 1 point. The second point is “30”, and the third point is “40”.

In each game, the server’s points are called out first. Therefore, 30-0 means that the server won two points and the receiver—the other player—hasn’t won any points in this game yet. However, 0-30 means that the server hasn’t won any points and the receiver won two points.

Incidentally, the “0” is not referred to as zero but as “love” in tennis scoring. Therefore a score of 30-0 is called out as thirty-love.

If a player is on “40” and wins the next point, they win the game as long as the other player isn’t also on “40”. Therefore, if the score is 40-0, 40-15, or 40-30, the server will win the game if he or she wins the next point. If the score is 0-40, 15-40, or 30-40, the receiver will win the game if he or she wins the next point.

If the score is 40-40, then a player needs to win two successive points to win the game. Incidentally, just to keep you on your toes, 40-40 is called “deuce” and not forty-all!

The player who wins the next point at “40-40” has an “advantage”, and the score is either 40-Ad or Ad-40. If the player with the advantage wins the next point, they win the game.

Tiebreak

We’re almost there. You read earlier that when a set is tied 6-6, a special type of game is played. This is a tiebreak. The points in a tiebreak are scored as 1, 2, 3, and so on. The first person to reach 7 points wins as long as the other player has 5 or fewer points.

If the players are tied on 6 points each in the tiebreak, they’ll keep playing until one player has a two-point advantage.

Writing A Program To Score A Tennis Match In Python

The primary aim of this program is to keep track of the score of a tennis match, point by point. You’ll be able to select who won a point and the program will update the score. The program will show when a game is won, when a set is won, and when the match is won.

The program will also keep a record of the entire match, point by point.

In Part 2, you’ll modify this code to create a simulation of a tennis match by assigning points randomly following specific rules.

In the next section, you’ll read how you’ll use the key concepts in object-oriented programming in Python to plan and write the program for scoring and simulating a tennis match.

Object-Oriented Programming

The simplest way to describe what a computer program does is the following:

  • it stores data
  • it does stuff with the data

Typically, you create data structures to store data, and you use functions to perform actions on the data. In object-oriented programming, you create objects that contain both the data and the tools to do stuff with that data within them.

You’re already very familiar with this concept, even if you don’t know it yet. Let’s assume you create the following string and list:

>>> title = "The Python Coding Book">>> contents = ["Intro", "Chapter 1", "Chapter 2"]

>>> type(title)
<class 'str'>>>> type(contents)
<class 'list'>>>> title.upper()
'THE PYTHON CODING BOOK'

>>> contents.append("Chapter 3")
>>> contents
['Intro', 'Chapter 1', 'Chapter 2', 'Chapter 3']

You can see that Python describes these as classes when you ask for the type of the objects. The object of type str has methods such as upper()attached to it. These are the actions that you can perform on data of type str.

However, lists have a different set of methods. In this case, you use append(), which is a method in the list class.

When you define your own classes, you’re creating a template showing what data you want your objects to have and what you’d like to do with the data.

This will start to make more sense as we look at the examples from the tennis project.

What classes do you need to simulate a tennis match in Python?

One way of looking at object-oriented programming is to think of the problem more from a human being’s point of view instead of trying to modify your planning to suit the computer. What do I mean by this?

Let’s take the tennis example you’re working on. The task is to keep track of the score during a tennis match. You’d like the computer program to do the hard work.

When using an object-oriented mindset, you want to start with the components of the problem that any human being will easily recognise.

You could start with the players and the match in this case. There are certain attributes that each player must have, for example, a name and the number of ranking points. These are the data you need for the player. You also want to be able to update a player’s ranking points.

There are attributes each match has, too. Each match needs to have two players, for example. And each match can be best-of-three sets or best-of-five. You also want to be able to play a match, so play_match() may be a useful function to have linked to each match.

Creating the classes

You can start creating these classes in a file called tennis.py. If you’re not familiar with classes, you’ll find some of the syntax a bit weird initially. However, you’ll read about what everything stands for in the coming paragraphs:

# tennis.py

class Player:
    def __init__(self, name="", ranking_points=0):
        self.name = name
        self.ranking_points = ranking_points

class Match:
    def __init__(
        self,
        player_1=Player(),
        player_2=Player(),
        best_of_5=True,
    ):
        self.players = (player_1, player_2)
        self.best_of_5 = best_of_5
        self.sets_to_play = 5 if best_of_5 else 3

You define a class using the class keyword followed by the name you choose for your class. By convention, class names are capitalised using the UpperCamelCase format.

The first method you define in each class is the initialisation method __init__(). This is a special method, as shown by the leading and trailing double underscores. Often, such methods are called dunder methods because of these double underscores.

When you create an object, the __init__() method is called. Therefore, you can use this method to set up the object. The best way to see what’s happening is by creating some objects using these classes. You’ll do this in the next section.

Testing your classes

You’re defining the classes in tennis.py. You could add code at the end of this script to test the classes. However, it’s often better to create a new script for this. You can call this script play_tennis.py, which you’ll use to score matches and simulate matches later:

# play_tennis.py

from tennis import Player, Match

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

print(nadal.name)
print(nadal.ranking_points)
print(djokovic.name)

This gives the following output:

Rafael Nadal
2000
Novak Djokovic

You start by importing the classes Player and Match from the tennis module, which is the script tennis.py.

You create an instance of a class by using the class name followed by parentheses (). You also include two arguments for each Player instance you create. These arguments are linked to the second and third parameter names in the __init__() method, name and ranking_points.

When you defined __init__() for the Player class, you included default values for name and ranking_points. Therefore, you can create an instance of Player simply by calling Player() with no arguments. This will create a player with no name (empty string) and with 0 ranking points.

What about the first parameter, self?

What about self?

You may have noticed the name self appears several times in the class definitions. Depending on what editor or IDE you’re using to code, you may have also noticed that your IDE auto-filled some of them and colour-coded them differently to other names.

A class is a template for creating objects that share similar attributes. When you define a class, you’re not creating any objects yet. This happens when you create an instance of the class. In the example above, when you created two instances of the Player class you assigned them to the variable names nadal and djokovic. However, when you defined the class, you didn’t have variable names yet as you hadn’t created any instances at that point.

The name self is a placeholder for the name of the object you’ll use later on. It’s a dummy variable name referring to the object itself which you’ll create later.

Therefore, when you define self.name in the Player class’s __init__() method, you’re creating an attribute called name that’s attached to the object itself. However, the object doesn’t exist yet. When you create these objects in play_tennis.py, you can use the actual variable name instead of self. So, instead of writing self.name, you can write nadal.name or djokovic.name.

self is also the first parameter in __init__()‘s signature. You’ll see that this is the case for other methods defined in a class, too. This means that when you use a method, the object itself is always passed as an argument to the method. You’ll look at this point later on.

Defining methods

You can add a method to the Player class which allows you to update the ranking points for a player:

# tennis.py

class Player:
    def __init__(self, name="", ranking_points=0):
        self.name = name
        self.ranking_points = ranking_points

    def update_ranking_points(self, points_change):
        self.ranking_points += points_change

class Match:
    def __init__(
        self,
        player_1=Player(),
        player_2=Player(),
        best_of_5=True,
    ):
        self.players = (player_1, player_2)
        self.best_of_5 = best_of_5
        self.sets_to_play = 5 if best_of_5 else 3

You define a method in the Player class which you call update_ranking_points(). The first parameter is self, which means the object itself will be passed to the function. You also add the parameter points_change, and you use this to increment the value of self.ranking_points.

You can test this method in play_tennis.py:

— 8,10,12-13 —

# play_tennis.py

from tennis import Player, Match

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

# print(nadal.name)
print(nadal.ranking_points)
# print(djokovic.name)

nadal.update_ranking_points(125)
print(nadal.ranking_points)

The output now shows that the ranking points increased from their original value of 2000 to 2125 once you call nadal.update_ranking_points(125):

2000
2125

This call only affects the ranking points for this player. Because the method is attached to the object, you can safely assume it will only affect that object.

Starting To Code The Tennis Scoring Rules

You’re ready to start writing the code to keep track of points in the match. But, before doing so, you can create a couple of new classes. Player and Match aren’t the only entities that matter to us. Each match contains a number of sets, and each set consists of a number of games. Since sets will have similar attributes, you can create a class for them. And you can do the same for games:

# tennis.py

class Player:
    def __init__(self, name="", ranking_points=0):
        self.name = name
        self.ranking_points = ranking_points

    def update_ranking_points(self, points_change):
        self.ranking_points += points_change

class Match:
    def __init__(
        self,
        player_1=Player(),
        player_2=Player(),
        best_of_5=True,
    ):
        self.players = (player_1, player_2)
        self.best_of_5 = best_of_5
        self.sets_to_play = 5 if best_of_5 else 3

class Set:
    def __init__(self, match: Match, set_number=0):
        self.match = match
        self.set_number = set_number

class Game:
    def __init__(self, set: Set, game_number=0):
        self.set = set
        self.game_number = game_number

Since each set is part of a match, you can link a Set object to a Match object when creating a Set. You achieve this by adding the Match object as an argument for the set’s initialisation method. You also create a set_number attribute to keep track of which set within a match you’re dealing with.

The same applies to games which are always part of a set. You use type hinting to show that the match parameter refers to a Match object in Set.__init__() and that the set parameter refers to a Set object in Game.__init__(). It’s not necessary to use type hinting. The main reason I’m using them in this case is that if you’re using an IDE that makes use of these, it makes writing the code easier. Your IDE will be able to offer autocompletion and other checks.

Note that the parameter names are written in lower case, match and set, whereas the class names are in uppercase Match and Set. The naming convention makes it easier to know what you’re referring to in your code.

Refactoring

As you write this code, you’ll be making changes to aspects of the code you’ve already written. I could guide you through the final code step by step. However, that’s not how anyone writes code. The process of writing a program almost always requires refactoring. Refactoring is the process of changing your program’s design without changing what it does. As you write more of your program, you’ll start to realise that you can do things differently.

Sometimes, refactoring is as simple as changing the name of a variable to make your code neater and more readable. Sometimes, it means making more significant changes.

Later, you’ll create yet another class, and you’ll need to make changes to the classes you’ve already written.

Scoring Points In A Game

You’ll start working on the manual version of scoring points. In this version, the program’s user will select one of the two players at the end of every point to indicate who won the point. The code will work out the score.

Therefore, you’ll need a method called score_point() in the Game class. A player can only score points in games, so this class is the only one that needs this method.

Let’s see what else you need to store in each instance of Game:

  • You need to have access to information about the players. Since the Game is linked to a Set and the Set is linked to a Match, you can always access the players’ information by using self.set.match.players in Game. This refers to the tuple containing the two Player objects. However, it’s easier to create a new reference pointing to the players within Game:
    self.players = self.set.match.players
    You could think ahead and plan to do the same in the Set class. Therefore, you would only need to access self.set.players in that case. But I won’t make that leap yet
  • You need to keep track of each player’s points in the game. There are several options for this. In this program, you’ll use a dictionary where the key is a Player object, and the value is the score for that player
  • You can also create a winner attribute to store the winner of the game
  • The Game class also needs access to the strange system of points in tennis games

You can add these attributes and start writing score_point():

# tennis.py

class Player:
    def __init__(self, name="", ranking_points=0):
        self.name = name
        self.ranking_points = ranking_points

    def update_ranking_points(self, points_change):
        self.ranking_points += points_change

class Match:
    def __init__(
        self,
        player_1=Player(),
        player_2=Player(),
        best_of_5=True,
    ):
        self.players = (player_1, player_2)
        self.best_of_5 = best_of_5
        self.sets_to_play = 5 if best_of_5 else 3

class Set:
    def __init__(self, match: Match, set_number=0):
        self.match = match
        self.set_number = set_number

class Game:
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):
        self.set = set
        self.game_number = game_number
        self.players = self.set.match.players
        self.score = {
            self.players[0]: 0,  # The key is of type Player
            self.players[1]: 0,
        }

    def score_point(self, player: Player):
        current_point = self.score[player]
        self.score[player] = Game.points[
            Game.points.index(current_point) + 1
        ]

You define a class attribute called points. This is not specific to each instance of the class, but it’s common to all class instances. The points used to score a game are the same for every game. You can access this class attribute when you need it in the class definition using Game.points.

What about the latter parts of a game?

The algorithm in score_point() still needs lots of work. At the moment, the method will assign the next item in Game.points as a value for the player’s score. For example, if the player is currently on “15”, then current_point will be 15 and Game.points.index(current_point) returns 1, which is the index corresponding to 15 in the tuple Game.points. You add 1 to this index to access the next item in the tuple.

This works fine in the early parts of a game. However, if you remember the scoring rules, things can get a bit more complex in the latter parts of a game.

You can test this version first by updating play_tennis.py:

# play_tennis.py

from tennis import Player, Match, Set, Game

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)
test_game = Game(test_set)

print(test_game.score)
test_game.score_point(nadal)
print(test_game.score)

test_game.score_point(nadal)
print(test_game.score)

test_game.score_point(djokovic)
print(test_game.score)

You create a Match, Set, and Game instance and show the score before and after several score_points() calls. This gives the output:

{<tennis.Player object at 0x10b897eb0>: 0, <tennis.Player object at 0x10b897e50>: 0}
{<tennis.Player object at 0x10b897eb0>: 15, <tennis.Player object at 0x10b897e50>: 0}
{<tennis.Player object at 0x10b897eb0>: 30, <tennis.Player object at 0x10b897e50>: 0}
{<tennis.Player object at 0x10b897eb0>: 30, <tennis.Player object at 0x10b897e50>: 15}

If you look closely, you’ll see the game points for each player on each line. The score correctly changes from 0-0 to 15-0, 30-0 and then 30-15. So far, score_points() is working for the early parts of a game.

There is one other issue we may want to fix. When you print test_game.score, the dictionary values show the score as expected—0, 15, 30 and so on. However, the keys show a rather obscure printout.

The keys in the score dictionary are objects of type Player. The representation of these objects shows that these are tennis.Player objects, and it also shows the unique id for the objects. This is not very instructive. Later in this article, you’ll read about the options you have to change how the object is represented when you print it.

Dealing with game scores in the latter parts of the game

Let’s recap what the possible outcomes are towards the end of a game:

  • If a player who’s on “40” wins the point and the other player’s score is not “40” or “Ad”, then the player who won the point wins the game
  • If a player who’s on “Ad” wins the point, then he or she wins the game
  • If both players are on “40”, the player who wins the point moves to “Ad”
  • If a player who’s on “40” wins the point and the other player is on “Ad”, both players return to “40”

You can update score_point() to reflect these options. Note that I’m truncating sections of the code that haven’t changed for display purposes. I’m using ellipsis (...) to show truncated classes or functions. This is similar to how some IDEs display collapsed code blocks to avoid lots of vertical scrolling:

# tennis.py

class Player:...

class Match:...

class Set:...

class Game:
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):
        self.set = set
        self.game_number = game_number
        self.players = self.set.match.players
        self.score = {
            self.players[0]: 0,  # The key is of type Player
            self.players[1]: 0,
        }

    def score_point(self, player: Player):
        current_point = self.score[player]
        # Player who wins point was on 40
        if self.score[player] == 40:
            # Other player is on Ad
            if "Ad" in self.score.values():
                # Update both players' scores to 40
                for each_player in self.players:
                    self.score[each_player] = 40
            # Other player is also on 40 (deuce)
            elif list(self.score.values()) == [40, 40]:
                # Point winner goes to Ad
                self.score[player] = "Ad"
            # Other player is on 0, 15, or 30
            else:
                # player wins the game
                self.score[player] = "Game"
        # Player who wins point was on Ad
        elif self.score[player] == "Ad":
            # player wins the game
            self.score[player] = "Game"
        # Player who wins point is on 0, 15, or 30
        else:
            self.score[player] = Game.points[
                Game.points.index(current_point) + 1
            ]

You include all possible options in score_point(). When the player wins the game, his or her score changes to “Game” to show the final game score.

You can test this code by manually calling score_point() several times for different players in play_tennis.py. You’ll need to test all possible outcomes to ensure everything works as you expect. Here’s one version testing several outcomes:

# play_tennis.py

from tennis import Player, Match, Set, Game

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)
test_game = Game(test_set)

print(test_game.score)
test_game.score_point(nadal)
print(test_game.score)

test_game.score_point(nadal)
print(test_game.score)

test_game.score_point(djokovic)
print(test_game.score)

test_game.score_point(djokovic)
print(test_game.score)

test_game.score_point(djokovic)
print(test_game.score)

test_game.score_point(nadal)
print(test_game.score)

test_game.score_point(nadal)
print(test_game.score)

test_game.score_point(djokovic)
print(test_game.score)

test_game.score_point(djokovic)
print(test_game.score)

test_game.score_point(nadal)
print(test_game.score)

test_game.score_point(nadal)
print(test_game.score)

test_game.score_point(nadal)
print(test_game.score)

The output from this code is:

{<tennis.Player object at 0x10b52feb0>: 0, <tennis.Player object at 0x10b52fe50>: 0}
{<tennis.Player object at 0x10b52feb0>: 15, <tennis.Player object at 0x10b52fe50>: 0}
{<tennis.Player object at 0x10b52feb0>: 30, <tennis.Player object at 0x10b52fe50>: 0}
{<tennis.Player object at 0x10b52feb0>: 30, <tennis.Player object at 0x10b52fe50>: 15}
{<tennis.Player object at 0x10b52feb0>: 30, <tennis.Player object at 0x10b52fe50>: 30}
{<tennis.Player object at 0x10b52feb0>: 30, <tennis.Player object at 0x10b52fe50>: 40}
{<tennis.Player object at 0x10b52feb0>: 40, <tennis.Player object at 0x10b52fe50>: 40}
{<tennis.Player object at 0x10b52feb0>: 'Ad', <tennis.Player object at 0x10b52fe50>: 40}
{<tennis.Player object at 0x10b52feb0>: 40, <tennis.Player object at 0x10b52fe50>: 40}
{<tennis.Player object at 0x10b52feb0>: 40, <tennis.Player object at 0x10b52fe50>: 'Ad'}
{<tennis.Player object at 0x10b52feb0>: 40, <tennis.Player object at 0x10b52fe50>: 40}
{<tennis.Player object at 0x10b52feb0>: 'Ad', <tennis.Player object at 0x10b52fe50>: 40}
{<tennis.Player object at 0x10b52feb0>: 'Game', <tennis.Player object at 0x10b52fe50>: 40}

This verifies several scenarios, but not all of them. I’ll leave it as an exercise for you to test the other options.

Tidying up score_point() in the Game class

You can add a new attribute to the Game class to store the winner of the game and assign the Player object corresponding to the winner to this new attribute when the game ends. Then, you can also use this winner attribute to ensure that score_point() can’t be used when a game has already ended.

You may have noticed that there are two parts in the algorithm corresponding to the player winning the game. And you’re about to add another line to each of these cases. You need to store the winning Player in an attribute named winner. Since we like to avoid repetition, you can add a Boolean flag to determine when a player wins the game:

# tennis.py

class Player:...

class Match:...

class Set:...

class Game:
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):
        self.set = set
        self.game_number = game_number
        self.players = self.set.match.players
        self.score = {
            self.players[0]: 0,  # The key is of type Player
            self.players[1]: 0,
        }
        self.winner = None

    def score_point(self, player: Player):
        if self.winner:
            print(
              "Error: You tried to add a point to a completed game"
            )
            return
        game_won = False
        current_point = self.score[player]
        # Player who wins point was on 40
        if self.score[player] == 40:
            # Other player is on Ad
            if "Ad" in self.score.values():
                # Update both players' scores to 40
                for each_player in self.players:
                    self.score[each_player] = 40
            # Other player is also on 40 (deuce)
            elif list(self.score.values()) == [40, 40]:
                # Point winner goes to Ad
                self.score[player] = "Ad"
            # Other player is on 0, 15, or 30
            else:
                # player wins the game
                game_won = True
        # Player who wins point was on Ad
        elif self.score[player] == "Ad":
            # player wins the game
            game_won = True
        # Player who wins point is on 0, 15, or 30
        else:
            self.score[player] = Game.points[
                Game.points.index(current_point) + 1
            ]

        if game_won:
            self.score[player] = "Game"
            self.winner = player

String Representation Of Objects

Before you move on to writing the Set and Match classes, let’s get back to an issue you’ve encountered earlier.

Try and print out the values of objects you create:

# play_tennis.py

from tennis import Player, Match, Set, Game

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)
test_game = Game(test_set)

print(nadal)
print(test_game)

The output from this code is:

<tennis.Player object at 0x10d07beb0><tennis.Game object at 0x10d07b3a0>

These are the representations of the objects you’ve seen earlier. They’re not very informative. However, you can change how objects are represented when you print them out.

The __str__() dunder method

You can add another dunder method called __str__() to the class definitions, which defines a string representation for the object. Once again, I’m truncating parts of the code in the display below:

# tennis.py

class Player:
    def __init__(self, name="", ranking_points=0):...

    def update_ranking_points(self, points_change):...

    def __str__(self):
        return self.name

class Match:...

class Set:...

class Game:
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):...

    def score_point(self, player: Player):...

    def __str__(self):
        score_values = list(self.score.values())
        return f"{score_values[0]} - {score_values[1]}"

The __str__() method is called when a user-friendly string representation is needed, such as when you use print(). You choose to display only the player’s name when you print out Player. In the Game class, you choose to show the score when printing the object.

You can run the script in play_tennis.py again, and the output will now be:

Rafael Nadal
0 - 0

This is great. But let’s return to printing the dictionary containing the score, as you did earlier:

# play_tennis.py

from tennis import Player, Match, Set, Game

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)
test_game = Game(test_set)

test_game.score_point(nadal)
print(test_game.score)

The output is:

{<tennis.Player object at 0x108e1feb0>: 15, <tennis.Player object at 0x108e1fe50>: 0}

The code still displays the somewhat obscure representation despite the fact you defined __str__() for the Player class.

The __repr__() dunder method

The reason is that there are two kinds of string representations. The one you’ve taken care of is the user-friendly one. It’s meant for the user of a program. This string representation should show information the user will find relevant, such as the player’s name and the game score.

You sometimes want a string representation that’s meant for the programmer rather than the user. This should have information relevant to a Python-literate programmer. To define this, you need another dunder method called __repr__():

# tennis.py

class Player:
    def __init__(self, name="", ranking_points=0):...

    def update_ranking_points(self, points_change):...

    def __str__(self):
        return self.name

    def __repr__(self):
        return (
            f"Player(name='{self.name}', "
            f"ranking_points={self.ranking_points})"
        )

class Match:...

class Set:...

class Game:
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):...

    def score_point(self, player: Player):...

    def __str__(self):
        score_values = list(self.score.values())
        return f"{score_values[0]} - {score_values[1]}"

    def __repr__(self):
        return (
            f"Game(set={self.set!r}, "
            f"game_number={self.game_number})"
        )

Well done if you spotted the !r in Game.__repr__(). We’ll get back to this very soon.

When you run play_tennis.py now, the output shows the string representations returned by __repr__() when you print the dictionary:

{Player(name='Rafael Nadal', ranking_points=2000): 15, Player(name='Novak Djokovic', ranking_points=2000): 0}

You can use Python’s built-in repr() to return this string representation:

# play_tennis.py

from tennis import Player, Match, Set, Game

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)
test_game = Game(test_set)

test_game.score_point(nadal)

print(test_game)
print(repr(test_game))

print(test_game) displays the string returned by __str__() whereas print(repr(test_game)) shows the representation from the __repr__() dunder method:

15 - 0
Game(set=<tennis.Set object at 0x10d567430>, game_number=0)

Note that the Set object is still displayed using the default representation since you haven’t defined the string representation dunder methods for Set yet.

When you use f-strings, the string from __str__() is used by default. However, you can replace this with the string from __repr__ by adding a !r in the f-string:

# play_tennis.py

from tennis import Player, Match, Set, Game

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)
test_game = Game(test_set)

print(f"{nadal}")
print(f"{nadal!r}")

The output from the code shows that the !r forces the __repr__() string representation to be used:

Rafael Nadal
Player(name='Rafael Nadal', ranking_points=2000)

The __str__() representation is meant to be user-friendly while the __repr__() representation is aimed to be informative for a programmer. Often, the __repr__() dunder method returns a string which can be used to recreate the object. You can see that this is the case for the string returned by Player.__repr__() which represents valid Python code to create the object.

Planning The Set Class

You can now shift your attention to the Set class. You’ve already created the match and set_number attributes.

A Set object will also need:

  • A reference to the players
  • An attribute to keep the score in the set. This can be a dictionary just like the one you used in Game
  • An attribute to store the winner of the set once the set is complete
  • A list containing references to all the games in the set

The first three of these are attributes that are common with the Game class, too. Both classes need a players attribute, a score attribute, and a winner attribute.

You can also plan ahead, and you’ll realise that the Match class also needs the same three attributes.

We don’t like repetition in programming, and we want to be efficient by reusing code as much as possible. Therefore, you can refactor your code and extract elements common to all three and place them in a separate class.

You can start by defining this new class that sits above Match, Set, and Game. You can name this class using a generic name such as Unit:

# tennis.py

class Player:...

class Unit:
    def __init__(self, players=(Player(), Player())):
        self.players = players
        self.score = {
            self.players[0]: 0,  # The key is of type Player
            self.players[1]: 0,
        }
        self.winner = None

    def get_winner(self):
        return self.winner

    def get_score(self):
        return self.score

    def is_running(self):
        return self.winner == None

class Match:...

class Set:...

class Game:...

The __init__() method in the Unit class contains attributes that Match, Set, and Game all require. Note that none of the code in __init__() is new. It’s code you wrote elsewhere already.

You also define three methods that will be useful for all the classes. get_winner() and get_score() return the value of the attributes self.winner and self.score. These functions are not necessary, but it’s good practice to have getter methods to get the values of attributes.

is_running() returns a Boolean value to indicate whether that unit of the game is still running.

Before working on the Set class, you can return to the Game class and refactor your code to use the new Unit class.

Inheritance

This leads us to inheritance. You can create a class which inherits the attributes and methods from another class. All the attributes and methods in the parent class will also be present in the child class. Then, you can add more attributes and methods to the new class to make it more specific to your needs.

Game can inherit all the attributes and methods from Unit. Therefore, you no longer need to define these in Game. You can change Game into a class which inherits from Unit:

# tennis.py

class Player:...

class Unit:
    def __init__(self, players=(Player(), Player())):
        self.players = players
        self.score = {
            self.players[0]: 0,  # The key is of type Player
            self.players[1]: 0,
        }
        self.winner = None

    def get_winner(self):
        return self.winner

    def get_score(self):
        return self.score

    def is_running(self):
        return self.winner == None

class Match:...

class Set:...

class Game(Unit):
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):
        super().__init__(set.match.players)
        self.set = set
        self.game_number = game_number

    def score_point(self, player: Player):...

    def __str__(self):...

    def __repr__(self):...

You show that Game inherits from Unit when you define the class:

class Game(Unit):

If you compare the __init__() method in Game to the one you wrote earlier, you’ll notice that the definitions of the players, score, and winner attributes are missing. However, you add a call to super().__init__().

super() gives you access to the methods in the superclass, or parent class. Therefore, when you initialise Game, you’re also initialising Unit. Since super().__init__() calls the initialisation method for Unit, you need to pass the arguments needed by Unit.

You can access the tuple containing the players via set.match.players. In reality, when writing this code, you can look ahead and realise that Set will also inherit from Unit. Therefore, it will also have a players attribute. You’ll be able to use set.players instead. However, let’s take this one step at a time. You’ll return to this line and refactor it later once you’ve completed the Set class.

Game now has access to the attributes and methods in Unit and the additional ones you define within Game. You can test this in play_tennis.py:

# play_tennis.py

from tennis import Player, Match, Set, Game

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)
test_game = Game(test_set)

test_game.score_point(nadal)

print(test_game.players)
print(test_game.is_running())
print(test_game.get_winner())

You don’t import Unit in this script. However, Game inherits from it. Therefore, test_game has the attribute players and the methods is_running() and get_winner(). This script gives the following output:

(Player(name='Rafael Nadal', ranking_points=2000), Player(name='Novak Djokovic', ranking_points=2000))
True
None

As the game is still in progress—there has only been one point played—is_running() returns True and get_winner() returns None.

You can try to comment out the line with super().__init__() in the class definition and re-run the script to see what happens.

Completing The Set Class

Now, you can shift your attention to writing the Set class you planned earlier. Set will also inherit from Unit, and it will also have a games attribute to store all the games played within the set:

# tennis.py

class Player:...

class Unit:
    def __init__(self, players=(Player(), Player())):
        self.players = players
        self.score = {
            self.players[0]: 0,  # The key is of type Player
            self.players[1]: 0,
        }
        self.winner = None

    def get_winner(self):
        return self.winner

    def get_score(self):
        return self.score

    def is_running(self):
        return self.winner == None

class Match:...

class Set(Unit):
    def __init__(self, match: Match, set_number=0):
        super().__init__(match.players)
        self.match = match
        self.set_number = set_number
        self.games = []

class Game(Unit):
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):
        super().__init__(set.players)
        self.set = set
        self.game_number = game_number

    def score_point(self, player: Player):...

    def __str__(self):...

    def __repr__(self):...

Once you write Set.__init__(), including the call to super().__init__(), you can also return to Game and refactor the argument in its super().__init__(). Instead of using set.match.players you can use set.players. You don’t need to do this, but it’s neater this way!

Play a game in the set

Next, you need to be able to play games within a set. Therefore, you can create a method in Set called play_game():

# tennis.py

class Player:...

class Unit:...

class Match:...

class Set(Unit):
    def __init__(self, match: Match, set_number=0):
        super().__init__(match.players)
        self.match = match
        self.set_number = set_number
        self.games = []

    def play_game(self):
        # Creat a Game object and append to .games list
        game = Game(self, len(self.games) + 1)
        self.games.append(game)

        # Ask for user input to record who won point
        print(
            f"\nRecord point winner: "
            f"Press 1 for {self.players[0]} | "
            f"Press 2 for {self.players[1]}"
        )
        while game.is_running():
            point_winner_idx = (
                int(input("\nPoint Winner (1 or 2) ->")) - 1
            )
            game.score_point(self.players[point_winner_idx])
            print(game)

        # Game over - update set score
        self.score[game.winner] += 1
        print(f"\nGame {game.winner.name}")
        print(f"\nCurrent score: {self}")

class Game(Unit):
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):...

    def score_point(self, player: Player):...

    def __str__(self):...

    def __repr__(self):...

The play_game() method does the following:

  1. Creates a Game object and appends it to the games attribute for the Set object
  2. Asks the user to record which player won the point and converts to zero-index by subtracting 1. You can add some code to check that the input is 1 or 2 if you wish.
  3. Calls game.score_point()
  4. Prints the game score, which is defined by Game.__str__()
  5. Repeats steps 2-4 until the game ends
  6. Determine and store the game-winner
  7. Update the score in the set by adding 1 to the winning player’s current score
  8. Print the game-winner and the current set score

You can now play an entire game of a set by calling play_game() on a Set object:

# play_tennis.py

from tennis import Player, Match, Set

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)

test_set.play_game()

You no longer need to create a Game object as this is created within test_set.play_game(). Therefore, you longer need to import Game, either.

You’ll be able to record all the points in a game when you run this script:

Record point winner: Press 1 for Rafael Nadal | Press 2 for Novak Djokovic

Point Winner (1 or 2) -> 1
15 - 0

Point Winner (1 or 2) -> 2
15 - 15

Point Winner (1 or 2) -> 1
30 - 15

Point Winner (1 or 2) -> 1
40 - 15

Point Winner (1 or 2) -> 1
Game - 15

Game Rafael Nadal

Current score: <tennis.Set object at 0x10ac6faf0>

This works as expected. However, the current set score is not displayed in the final line. This happens because you haven’t yet defined __str__() for the Set class. Let’s sort this out now and also define __repr__():

# tennis.py

class Player:...

class Unit:...

class Match:...

class Set(Unit):
    def __init__(self, match: Match, set_number=0):
        super().__init__(match.players)
        self.match = match
        self.set_number = set_number
        self.games = []

    def play_game(self):
        # Creat a Game object and append to .games list
        game = Game(self, len(self.games) + 1)
        self.games.append(game)

        # Ask for user input to record who won point
        print(
            f"\nRecord point winner: "
            f"Press 1 for {self.players[0]} | "
            f"Press 2 for {self.players[1]}"
        )
        while game.is_running():
            point_winner_idx = (
                int(input("\nPoint Winner (1 or 2) ->")) - 1
            )
            game.score_point(self.players[point_winner_idx])
            print(game)

        # Game over - update set score
        self.score[game.winner] += 1
        print(f"\nGame {game.winner.name}")
        print(f"\nCurrent score: {self}")

    def __str__(self):
        return "-".join(
            [str(value) for value in self.score.values()]
        )

    def __repr__(self):
        return (
            f"Set(match={self.match!r}, "
            f"set_number={self.set_number})"
        )

class Game(Unit):...

When you run play_tennis.py now, the final line looks like this:

Current score: 1-0

You can test the __repr__() method too by adding print(repr(test_set)) in play_tennis.py.

Determine what stage in the set you’re at

The code you wrote so far works for the early stages of a set. The program adds each game a player wins to his or her set score. However, as you approach the end of a set, you’ll need to start looking out for different scenarios.

When a player reaches 6 games in a set, one of three things can happen:

  • If the other player has 4 or fewer games in the set, then the player who reached 6 wins the set. This accounts for the scores 6-4, 6-3, 6-2, 6-1, and 6-0
  • If the other player has 5 games in the set, and therefore, the score is currently 6-5, the set carries on as normal
  • If the other player also has 6 games in the set, then the current set score is 6-6 and the set moves to a tiebreak

You can code these rules in play_game():

# tennis.py

class Player:...

class Unit:...

class Match:...

class Set(Unit):
    def __init__(self, match: Match, set_number=0):
        super().__init__(match.players)
        self.match = match
        self.set_number = set_number
        self.games = []

    def play_game(self):
        # Creat a Game object and append to .games list
        game = Game(self, len(self.games) + 1)
        self.games.append(game)

        # Ask for user input to record who won point
        print(
            f"\nRecord point winner: "
            f"Press 1 for {self.players[0]} | "
            f"Press 2 for {self.players[1]}"
        )
        while game.is_running():
            point_winner_idx = (
                int(input("\nPoint Winner (1 or 2) ->")) - 1
            )
            game.score_point(self.players[point_winner_idx])
            print(game)

        # Game over - update set score
        self.score[game.winner] += 1
        print(f"\nGame {game.winner.name}")
        print(f"\nCurrent score: {self}")

        # Check stage within set
        # If it's an early stage of the set and no one
        # reached 6 or 7 games, there's nothing else to do
        # and method can return
        if (
            6 not in self.score.values()
            and 7 not in self.score.values()
        ):
            return
        # Rest deals with latter stages of set when at least
        # one player is on 6 games
        # Check for 6-6 score
        if list(self.score.values()) == [6, 6]:
            # ToDo: Deal with tiebreak scenario later
            ...
        # …7-5 or 7-6 score (if tiebreak was played, score
        # will be 7-6)
        for player in self.players:
            # player reaches 7 games
            if self.score[player] == 7:
                self.winner = player
                return
            # player reaches 6 games
            # and 6-6 and 7-6 already ruled out
            if self.score[player] == 6:
                # Exclude 6-5 scenario
                if 5 not in self.score.values():
                    self.winner = player

    def __str__(self):
        return "-".join(
            [str(value) for value in self.score.values()]
        )

    def __repr__(self):
        return (
            f"Set(match={self.match!r}, "
            f"set_number={self.set_number})"
        )

class Game(Unit):...

) def __repr__(self): return ( f”Set(match={self.match!r}, ” f”set_number={self.set_number})” ) class Game(Unit):…

The steps you take to check which stage of the set you’ve reached are:

  • If neither player’s number of games is 6 or 7, then the set just carries on, and you exit the method early using return
  • If both players have 6 games, then it’s a tiebreak. You left a to-do note in your code to get back to this later. Note that you also added an ellipsis (...) since you have to add at least one statement after an if statement
  • Next, you check if either player has reached 7 games. This means this player has won the set with a 7-5 or a 7-6 score. You’ll deal with 7-6 score later when you account for the tiebreak
  • If either player has 6 games, then the set is either on 6-5, and it should just carry on, or the player on 6 games won the set

You can test all these scenarios, except the tiebreak case, using play_tennis.py again:

# play_tennis.py

from tennis import Player, Match, Set

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)
test_set = Set(test_match)

while test_set.is_running():
    test_set.play_game()
    print(str(test_set))
print(test_set.winner)

You use a while loop to keep playing games until the set is over, showing the set score after each game and displaying the winner of the set at the end. I’ll leave this as an exercise for you to test all the options, except for the 6-6 scenario.

Adding The Tiebreak Option

A tiebreak is a type of game. However, it has different rules from normal games. Because it’s a game, a tiebreak will share many attributes with a standard game. Therefore, you can create a new class called Tiebreak which inherits from Game. However, you need score_point() to perform a different task to its counterpart in Game. You can do this by overriding the score_point() method:

# tennis.py

class Player:...

class Unit:...

class Match:...

class Set(Unit):...

class Game(Unit):
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):
        super().__init__(set.players)
        self.set = set
        self.game_number = game_number

    def score_point(self, player: Player):...

class Tiebreak(Game):
    def __init__(self, set: Set, game_number=0):
        super().__init__(set, game_number)

    def score_point(self, player: Player):
        if self.winner:
            print(
              "Error: You tried to add a point to a completed game"
            )
            return
        # Add point to player
        self.score[player] += 1
        # Tiebreak over only if player has 7 or more points
        # and there's at least a 2 point-gap
        if (
            self.score[player] >= 7
            and self.score[player] - min(self.score.values()) >= 2
        ):
            self.winner = player

The __init__() method calls super().__init__() and nothing else. This makes Tiebreak identical to Game. However, you redefine score_point() in Tiebreak. Since the method has the same name, it overrides the version in the parent class. Therefore Tiebreak behaves like Game except for score_point().

In score_point(), you’re using the tiebreak rules to add points and determine when the game ends.

Now, you can go back to Set.play_game() to complete this method. When you detect a tiebreak, you can recursively call self.play_game() again. However, you’ll need to ensure that a Tiebreak game is created rather than a standard Game.

You can do this by refactoring play_game() so that it takes an argument to determine whether it’s a tiebreak or a normal game:

# tennis.py

class Player:...

class Unit:...

class Match:...

class Set(Unit):
    def __init__(self, match: Match, set_number=0):
        super().__init__(match.players)
        self.match = match
        self.set_number = set_number
        self.games = []

    def play_game(self, tiebreak=False):
        # Creat a Game object and append to .games list
        if tiebreak:
            game = Tiebreak(self, len(self.games) + 1)
        else:
            game = Game(self, len(self.games) + 1)
        self.games.append(game)

        # Ask for user input to record who won point
        print(
            f"\nRecord point winner: "
            f"Press 1 for {self.players[0]} | "
            f"Press 2 for {self.players[1]}"
        )
        while game.is_running():
            point_winner_idx = (
                int(input("\nPoint Winner (1 or 2) ->")) - 1
            )
            game.score_point(self.players[point_winner_idx])
            print(game)

        # Game over - update set score
        self.score[game.winner] += 1
        print(f"\nGame {game.winner.name}")
        print(f"\nCurrent score: {self}")

        # Check stage within set
        # If it's an early stage of the set and no one
        # reached 6 or 7 games, there's nothing else to do
        # and method can return
        if (
            6 not in self.score.values()
            and 7 not in self.score.values()
        ):
            return
        # Rest deals with latter stages of set when at least
        # one player is on 6 games
        # Check for 6-6 score
        if list(self.score.values()) == [6, 6]:
            self.play_game(tiebreak=True)
            return
        # …7-5 or 7-6 score (if tiebreak was played, score
        # will be 7-6)
        for player in self.players:
            # player reaches 7 games
            if self.score[player] == 7:
                self.winner = player
                return
            # player reaches 6 games
            # and 6-6 and 7-6 already ruled out
            if self.score[player] == 6:
                # Exclude 6-5 scenario
                if 5 not in self.score.values():
                    self.winner = player

    def __str__(self):
        return "-".join(
            [str(value) for value in self.score.values()]
        )

    def __repr__(self):
        return (
            f"Set(match={self.match!r}, "
            f"set_number={self.set_number})"
        )

class Game(Unit):...
    
class Tiebreak(Game):...

The variable game in Set.play_game() will either be an instance of Game or of Tiebreak. When the code detects a 6-6 score, it recursively calls play_game() with the tiebreak=True argument. This call runs the tiebreak and updates the set’s score and winner, since there will always be a set winner after a tiebreak is played.

Your job is to test the tiebreak scenario now, using the same play_tennis.py you have from the previous section.

Updating Game.__repr__()

Tiebreak inherits everything from Game except for the method you overrode. There’s a minor issue with this. The __repr__() dunder method in Game uses the word “Game” in its string representation. This means that Tiebreak.__repr__() will also use the substring “Game” in its representation.

You can override __repr__() in Tiebreak if you wish, and copy-paste the code from Game, replacing a single word. Instead, you can make Game.__repr__() more generic:

# tennis.py

# ...

class Game(Unit):
 # ...

    def __repr__(self):
        return (
            f"{self.__class__.__name__}(set={self.set!r}, "
            f"game_number={self.game_number})"
        )

# ...

You use self.__class__.__name__ to refer to the instance’s class name. This will be “Game” when the instance is of type Game and “Tiebreak” when the instance is of type Tiebreak.

Completing The Match Class

You’re almost there. All that’s left is to complete the Match class. You’ll see many patterns you’re familiar with from earlier parts of this article.

You can refactor Match to inherit from Unit and write methods play_set() and play_match():

# tennis.py

class Player:...

class Unit:
    def __init__(self, players=(Player(), Player())):
        self.players = players
        self.score = {
            self.players[0]: 0,  # The key is of type Player
            self.players[1]: 0,
        }
        self.winner = None

    def get_winner(self):
        return self.winner

    def get_score(self):
        return self.score

    def is_running(self):
        return self.winner == None

class Match(Unit):
    def __init__(
        self,
        player_1=Player(),
        player_2=Player(),
        best_of_5=True,
    ):
        super().__init__(players=(player_1, player_2))
        self.best_of_5 = best_of_5
        self.sets_to_play = 5 if best_of_5 else 3
        self.sets = []

    def play_set(self):
        set = Set(self, len(self.sets) + 1)
        self.sets.append(set)

        while set.is_running():
            set.play_game()
        set_winner = set.get_winner()
        # Update set score for player who won set
        self.score[set_winner] += 1

        # If player has won 2 sets if best-of-three
        # or 3 sets if best-of-five, match is over
        if self.score[set_winner] == self.sets_to_play // 2 + 1:
            self.winner = set_winner

class Set(Unit):...

class Game(Unit):...

class Tiebreak(Game):...

Match now inherits from Unit. Both Player objects are bundled into a tuple when passed to super().__init__(), as required by the initialistion of Unit.

You define play_set(), which creates a Set and appends it to the sets attribute. You keep playing games until the set is over. These tasks are taken care of by the Set and Game classes you wrote earlier.

The rules are much simpler when it comes to when the match ends compared to when sets and games end. The first player to win 2 sets in a best-of-three match or 3 sets in a best-of-five match wins the match.

You can test play_set() by updating the script in play_tennis.py:

# play_tennis.py

from tennis import Player, Match

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)

while test_match.is_running():
    test_match.play_set()

There’s no longer a need to import Set and create a set manually as the Match object will take care of this. You can test this out. It’s a bit tedious to do so. In Part 2 of this project, where you’ll be simulating a tennis match in Python, you’ll automate this process to simulate a match. However, it’s still worthwhile to test the code this way.

When you do, you’ll notice a slight issue. At the end of each game, the program displays the score of the current set. But it doesn’t show the previous sets. You can fix this in Set.play_game() when you print the current score. Instead of printing the set score, you can print the match score. You can achieve this by replacing self with self.match in the f-string in Set.play_game().

However, you’ll need to write the string representations for Match first:

# tennis.py

class Player:...

class Unit:...

class Match(Unit):
    def __init__(
        self,
        player_1=Player(),
        player_2=Player(),
        best_of_5=True,
    ):
        super().__init__(players=(player_1, player_2))
        self.best_of_5 = best_of_5
        self.sets_to_play = 5 if best_of_5 else 3
        self.sets = []

    def play_set(self):
        set = Set(self, len(self.sets) + 1)
        self.sets.append(set)

        while set.is_running():
            set.play_game()
        set_winner = set.get_winner()
        # Update set score for player who won set
        self.score[set_winner] += 1

        # If player has won 2 sets if best-of-three
        # or 3 sets if best-of-five, match is over
        if self.score[set_winner] == self.sets_to_play // 2 + 1:
            self.winner = set_winner

    def __str__(self):
        return "".join([str(set) for set in self.sets])

    def __repr__(self):
        return (
            f"Match("
            f"player_1={self.players[0]}, "
            f"player_2={self.players[1]}, "
            f"best_of_5={self.best_of_5})"
        )

class Set(Unit):
    def __init__(self, match: Match, set_number=0):...

    def play_game(self, tiebreak=False):
        # Creat a Game object and append to .games list
        if tiebreak:
            game = Tiebreak(self, len(self.games) + 1)
        else:
            game = Game(self, len(self.games) + 1)
        self.games.append(game)

        # Ask for user input to record who won point
        print(
            f"\nRecord point winner: "
            f"Press 1 for {self.players[0]} | "
            f"Press 2 for {self.players[1]}"
        )
        while game.is_running():
            point_winner_idx = (
                int(input("\nPoint Winner (1 or 2) ->")) - 1
            )
            game.score_point(self.players[point_winner_idx])
            print(game)

        # Game over - update set score
        self.score[game.winner] += 1
        print(f"\nGame {game.winner.name}")
        print(f"\nCurrent score: {self.match}")

        # Check stage within set
        # If it's an early stage of the set and no one
        # reached 6 or 7 games, there's nothing else to do
        # and method can return
        if (
            6 not in self.score.values()
            and 7 not in self.score.values()
        ):
            return
        # Rest deals with latter stages of set when at least
        # one player is on 6 games
        # Check for 6-6 score
        if list(self.score.values()) == [6, 6]:
            self.play_game(tiebreak=True)
            return
        # …7-5 or 7-6 score (if tiebreak was played, score
        # will be 7-6)
        for player in self.players:
            # player reaches 7 games
            if self.score[player] == 7:
                self.winner = player
                return
            # player reaches 6 games
            # and 6-6 and 7-6 already ruled out
            if self.score[player] == 6:
                # Exclude 6-5 scenario
                if 5 not in self.score.values():
                    self.winner = player

    def __str__(self):...

    def __repr__(self):...

class Game(Unit):...

class Tiebreak(Game):...

Finally, there’s one last method to define in Match:

# tennis.py

class Player:...

class Unit:...

class Match(Unit):
    def __init__(
        self,
        player_1=Player(),
        player_2=Player(),
        best_of_5=True,
    ):
        super().__init__(players=(player_1, player_2))
        self.best_of_5 = best_of_5
        self.sets_to_play = 5 if best_of_5 else 3
        self.sets = []

    def play_set(self):
        set = Set(self, len(self.sets) + 1)
        self.sets.append(set)

        while set.is_running():
            set.play_game()
        set_winner = set.get_winner()
        # Update set score for player who won set
        self.score[set_winner] += 1

        # If player has won 2 sets if best-of-three
        # or 3 sets if best-of-five, match is over
        if self.score[set_winner] == self.sets_to_play // 2 + 1:
            self.winner = set_winner

    def play_match(self):
        while self.is_running():
            self.play_set()
        print(f"\nWinner: {self.winner}")
        print(f"Score: {self}")

    def __str__(self):
        return "".join([str(set) for set in self.sets])

    def __repr__(self):
        return (
            f"Match("
            f"player_1={self.players[0]}, "
            f"player_2={self.players[1]}, "
            f"best_of_5={self.best_of_5})"
        )

class Set(Unit):...

class Game(Unit):...

class Tiebreak(Game):...

And play_tennis.py can now be simplified further to:

# play_tennis.py

from tennis import Player, Match

nadal = Player("Rafael Nadal", 2000)
djokovic = Player("Novak Djokovic", 2000)

test_match = Match(nadal, djokovic)

test_match.play_match()

Now, you can run an entire tennis match, assigning one point at a time until one of the players wins the match.

Note: Final, full version of the code is available at the end of this article.

Simulating A Tennis Match in Python

This article is already rather long. So, instead of adding more, I’ll wrap up here and publish Part 2 as a separate, much shorter article.

In Part 2, you’ll add a bit more code to your classes to add the option to simulate a tennis match. You won’t need to assign points to players manually, but you’ll let the code do this for you. The code will assign points with a likelihood which depends on the players’ ranking points.

This will enable you to run simulations of hundreds or thousands of matches and analyse how certain parameters affect tennis match results. Here are two previews of the kind of results you’ll obtain from these simulations:

Final Words

In this article, you’ve explored the world of object-oriented programming in Python by simulating a tennis match. The key aspects of this topic you learnt about are:

  • Creating classes in Python using the class keyword
  • Initialising objects using __init__()
  • Defining methods in a class
  • Creating string representations for the class using __str__() and __repr__()
  • Creating classes using inheritance

There’s still more to explore in object-oriented programming. But the key point to remember is not so much the technical detail—that’s important too—but the philosophy behind this programming paradigm.

That’s game, set, and match for this article…

Further Reading

Final Full Version Of tennis.py

# tennis.py

class Player:
    def __init__(self, name="", ranking_points=0):
        self.name = name
        self.ranking_points = ranking_points

    def update_ranking_points(self, points_change):
        self.ranking_points += points_change

    def __str__(self):
        return self.name

    def __repr__(self):
        return (
            f"Player(name='{self.name}', "
            f"ranking_points={self.ranking_points})"
        )

        
class Unit:
    def __init__(self, players=(Player(), Player())):
        self.players = players
        self.score = {
            self.players[0]: 0,  # The key is of type Player
            self.players[1]: 0,
        }
        self.winner = None

    def get_winner(self):
        return self.winner

    def get_score(self):
        return self.score

    def is_running(self):
        return self.winner == None

      
class Match(Unit):
    def __init__(
        self,
        player_1=Player(),
        player_2=Player(),
        best_of_5=True,
    ):
        super().__init__(players=(player_1, player_2))
        self.best_of_5 = best_of_5
        self.sets_to_play = 5 if best_of_5 else 3
        self.sets = []

    def play_set(self):
        set = Set(self, len(self.sets) + 1)
        self.sets.append(set)

        while set.is_running():
            set.play_game()
        set_winner = set.get_winner()
        # Update set score for player who won set
        self.score[set_winner] += 1

        # If player has won 2 sets if best-of-three
        # or 3 sets if best-of-five, match is over
        if self.score[set_winner] == self.sets_to_play // 2 + 1:
            self.winner = set_winner

    def play_match(self):
        while self.is_running():
            self.play_set()
        print(f"\nWinner: {self.winner}")
        print(f"Score: {self}")

    def __str__(self):
        return "".join([str(set) for set in self.sets])

    def __repr__(self):
        return (
            f"Match("
            f"player_1={self.players[0]}, "
            f"player_2={self.players[1]}, "
            f"best_of_5={self.best_of_5})"
        )
        
        
class Set(Unit):
    def __init__(self, match: Match, set_number=0):
        super().__init__(match.players)
        self.match = match
        self.set_number = set_number
        self.games = []

    def play_game(self, tiebreak=False):
        # Creat a Game object and append to .games list
        if tiebreak:
            game = Tiebreak(self, len(self.games) + 1)
        else:
            game = Game(self, len(self.games) + 1)
        self.games.append(game)

        # Ask for user input to record who won point
        print(
            f"\nRecord point winner: "
            f"Press 1 for {self.players[0]} | "
            f"Press 2 for {self.players[1]}"
        )
        while game.is_running():
            point_winner_idx = (
                int(input("\nPoint Winner (1 or 2) ->")) - 1
            )
            game.score_point(self.players[point_winner_idx])
            print(game)

        # Game over - update set score
        self.score[game.winner] += 1
        print(f"\nGame {game.winner.name}")
        print(f"\nCurrent score: {self.match}")

        # Check stage within set
        # If it's an early stage of the set and no one
        # reached 6 or 7 games, there's nothing else to do
        # and method can return
        if (
            6 not in self.score.values()
            and 7 not in self.score.values()
        ):
            return
        # Rest deals with latter stages of set when at least
        # one player is on 6 games
        # Check for 6-6 score
        if list(self.score.values()) == [6, 6]:
            self.play_game(tiebreak=True)
            return
        # …7-5 or 7-6 score (if tiebreak was played, score
        # will be 7-6)
        for player in self.players:
            # player reaches 7 games
            if self.score[player] == 7:
                self.winner = player
                return
            # player reaches 6 games
            # and 6-6 and 7-6 already ruled out
            if self.score[player] == 6:
                # Exclude 6-5 scenario
                if 5 not in self.score.values():
                    self.winner = player

    def __str__(self):
        return "-".join(
            [str(value) for value in self.score.values()]
        )

    def __repr__(self):
        return (
            f"Set(match={self.match!r}, "
            f"set_number={self.set_number})"
        )

        
class Game(Unit):
    points = 0, 15, 30, 40, "Ad"  # Class attribute

    def __init__(self, set: Set, game_number=0):
        super().__init__(set.players)
        self.set = set
        self.game_number = game_number

    def score_point(self, player: Player):
        if self.winner:
            print(
              "Error: You tried to add a point to a completed game"
            )
            return
        game_won = False
        current_point = self.score[player]
        # Player who wins point was on 40
        if self.score[player] == 40:
            # Other player is on Ad
            if "Ad" in self.score.values():
                # Update both players' scores to 40
                for each_player in self.players:
                    self.score[each_player] = 40
            # Other player is also on 40 (deuce)
            elif list(self.score.values()) == [40, 40]:
                # Point winner goes to Ad
                self.score[player] = "Ad"
            # Other player is on 0, 15, or 30
            else:
                # player wins the game
                game_won = True
        # Player who wins point was on Ad
        elif self.score[player] == "Ad":
            # player wins the game
            game_won = True
        # Player who wins point is on 0, 15, or 30
        else:
            self.score[player] = Game.points[
                Game.points.index(current_point) + 1
            ]

        if game_won:
            self.score[player] = "Game"
            self.winner = player

    def __str__(self):
        score_values = list(self.score.values())
        return f"{score_values[0]} - {score_values[1]}"

    def __repr__(self):
        return (
            f"{self.__class__.__name__}(set={self.set!r}, "
            f"game_number={self.game_number})"
        )

        
class Tiebreak(Game):
    def __init__(self, set: Set, game_number=0):
        super().__init__(set, game_number)

    def score_point(self, player: Player):
        if self.winner:
            print(
              "Error: You tried to add a point to a completed game"
            )
            return
        # Add point to player
        self.score[player] += 1
        # Tiebreak over only if player has 7 or more points
        # and there's at least a 2 point-gap
        if (
            self.score[player] >= 7
            and self.score[player] - min(self.score.values()) >= 2
        ):
            self.winner = player

Get the latest blog updates

No spam promise. You’ll get an email when a new blog post is published


The post Simulating a Tennis Match Using Object-Oriented Programming in Python—Wimbledon Special Part 1 appeared first on The Python Coding Book.

Mike Driscoll: An Intro to Python's Package Installer: pip (Video)

Armin Ronacher: A Non Fungible Future

$
0
0
Through some unfortunate stream of events I ended up being the recipient to a lot of replies on Twitter that tried to sell the future potential of NFTs on me. So I figured I take their pitches to the logical conclusion and dream up the crypto people's NFT utopia.

NFTs and blockchains have now long been mainstream. It's no longer the early days of the web as they used to say. NFTs originally started out as a novel way to pay for digital art, and in many ways that is still where they are rooted, but they have some much farther.

They have three very vital properties: they are largely freely and globally trade-able, whoever owns them does in fact own them as a record on the blockchain overrides anything else, and revenue sharing can be baked right into the contract. The revenue sharing property is what allows the original creator of the NFT to receive a fraction of any future sale.

Everything turned into an NFT and because everything is now trade-able, there is a market for everything. This very advanced form of capitalism has also created many novel financial products that were previously unheard of. You can in fact create an NFT of a trade on the blockchain and you can create any financial product on the blockchain you might desire. Asset backed securities can now target the lives of people, the outcomes of their life choices as well as any artwork in existence.

NFTs came out of the art world and they have definitely revolutionized it. Aspiring artists, enrolling into arts programs, no longer have to deal with student loans or similar. Where previous generations had expensive loans, modern students and pupils have blockchain traded lifestyle smart lending contracts. These are different than loans on the blockchain but they are intrinsically linked to future earnings of that person. Since every person has a digital identity on the blockchain that is linked to their wallets and NFTs (obviously cryptographically secure and anonymous through proof-of-birth). With these digital identities, students can enroll into for instance a 12 months arts bootcamp. This arts school then can offer to pay their students living expenses for 12 months and their education is completely free. This is enabled because this arrangement is backed by a smart contract on the blockchain. Artists who enter a prestigious bootcamp will give 2% of all future proceeds of future NFT based artwork with the bootcamp. All powered by the blockchain.

Concerts now also often have artwork on their tickets which are NFTs and these tickets are often developing a life on their own. Unfortunately actually getting hold of tickets has become very hard as scalpers now control the entirety of the ticket market. Major artists are impossible to enjoy for the average person. Since artists receive a cut of any future ticket sale, the market is now completely dominated by the reselling process and dynamic pricing. Music tickets now sell for a month's salary even minutes after they go on sale. Financial firms also use machine learning models to predict the best prices for these tickets in real time and are both assisting in determining the initial sales prices and also actively trade them on exchanges in the time leading up to the concert. If one has to cancel their ticket last minute, these tickets become available on the spot market again. Specialized services buy them for a fraction and give out via last minute booking portals to eager customers which has become the main way in which poorer people get to enjoy popular concerts.

The largest musicians now also have complex smart contract deals where the tickets act both as a mean of conveying marketing messages in the form of ad placement — and as mentioned as a place to display unique artwork. Venues are also taking a cut of every sale and so do the publishers and organizers behind the scenes. Everything has turned into a revenue sharing model. Some famous venues where artists in the mid 1900s played take up to 40% of the gross ticket sale. Some painters became popular by artwork they put on these tickets and some of the used up tickets sell for many times their original value even years later.

Taxation has almost entirely disappeared since governments were completely unable to keep up with the ever growing world of smart contracts and blockchain businesses. The replacement for governments have become decentralized services people vote on with utility tokens. This is why almost every single service is now operated by private companies with smart contract based billing. Districts now put their services on the market for companies to bid on in real-time. A less well off district is paying a premium over a safe neighborhood for police and fire fighting services. The cost of this is varying a lot from day to day. To combat this, various methods of hedging are now also available. Various kinds of business models have appeared for these services. Since property value is obvious from trading history on the blockchain, firefighting departments are now often charging a percent of the property value for saving it.

Insurances also have greatly changed. The biggest form of modern fraud are in fact the abuse of bugs in smart contracts and identity or wallet theft. What is on the blockchain is what matters. Since that even goes to real estate it has become a common occurrence for people to lose their homes through this type of theft. A solution to this is forming where more and more property ownership records are smart contracts that loops in an independent authority as a form of notary. These have the power to repossess in case of unauthorized title transfer and non payment. They are also getting a cut of the sale of a property. Thanks to these, ownership of house records being NFTs themselves there are many more new and exciting derivatives. Houses of famous people now permanently carry that record on the blockchain which obviously controls future prices as well.

Some houses got built under smart contracts that guarantee them a cut from future sales. Some clever builders found ways to even take a cut from future NFTs created by inhabitants living in these houses. These schemes are becoming quite popular among students as they offer cheap housing for 25% of future earnings from any NFT created.

Not only are tickets and ownership records now NFTs, so are transactions and type of smart contract operation themselves. Not only is the ticket of a flight an NFT, but so is the flight itself. One would think that after a plane landed, their NFT value goes to zero but in fact a lot of people started collecting NFTs of crashed flights. The NFT for the deadliest airplane disaster is one of the most highly valued tokens today. Thanks to the blockchain and the associated smart contracts, relatives of the deceased got and continue to receive a cut from sales of the crashed flight's NFT.

The latest and greatest innovation are smart contracts on digital identities. Actors are now compensated by screen air time directly through the smart contract of the movie. The audience can further support their favorite actors by using the smart contracts to control which percentage of their streaming service subscription goes where. This also has made "cancelling" individuals much more efficient. No longer does someone have to vote with their (digital) wallet on the entire movie, they can buy the movie but refuse that their money goes to an individual they dislike.

This also works in other ways. The medical insurance industry is no more. The middle man was cut out. Now you can pay for your medical operations through smart financial products on the blockchain as well. Doctors and medical centers can directly put a record on the blockchain to recuperate the cost of the operation from future earnings or in case of risky operations, put a contract on the blockchain that others bet on. The "future NFT" of the operation to come can be traded similar to a future. Traders can now run trading algorithms to determine the likelihood of death and gamble on the outcome of that operation for a chance of future earnings of the person. Likewise they can be cut into proceedings of future blockchain run malpractice evaluations of the doctor in case of a problematic outcome. Finance being finance obviously also creates trade-able bundles of multiple of such operations. You can thus invest your future retirement on other people's health outcomes if you so desire by investing into these surgery backed securities.

The future is bright and full of potential.

Viewing all 23318 articles
Browse latest View live


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