Planet Python
The Python Coding Blog: Anatomy of a 2D Game using Python’s turtle and Object-Oriented Programming
When I was young, we played arcade games in their original form on tall rectangular coin-operated machines with buttons and joysticks. These games had a resurgence as smartphone apps in recent years, useful to keep one occupied during a long commute. In this article, I’ll resurrect one as a 2D Python game and use it to show the “anatomy” of such a game.
You can follow this step-by-step tutorial even if you’re unfamiliar with all the topics. In particular, this tutorial will rely on Python’s turtle module and uses the object-oriented programming (OOP) paradigm. However, you don’t need expertise in either topic, as I’ll explain the key concepts you’ll need. However, if you’re already familiar with OOP, you can easily skip the clearly-marked OOP sections.
This is the game you’ll write:
The rules of the game are simple. Click on a ball to bat it up. How long can you last before you lose ten balls?
In addition to getting acquainted with OOP principles, this tutorial will show you how such games are built step-by-step.
Note about content in this article: If you’re already familiar with the key concepts in object-oriented programming, you can skip blocks like this one. If you’re new or relatively new to the topic, I recommend you read these sections as well.
The Anatomy of a 2D Python Game | SummaryI’ll break this game down into eight key steps:
- Create a class named Ball and set up what should happen when you create a ball
- Make the ball move forward
- Add gravity to pull the ball downwards
- Add the ability to bat the ball upwards
- Create more balls, with each ball created after a certain time interval
- Control the speed of the game by setting a frame rate
- Add a timer and an end to the game
- Add finishing touches to the game
Are you ready to start coding?
A visual summary of the anatomy of a 2D Python gameHere’s another summary of the steps you’ll take to create this 2D Python game. This is a visual summary:
1. Create a Class Named BallYou’ll work on two separate scripts to create this game:
- juggling_ball.py
- juggling_balls_game.py
You’ll use the first one, juggling_ball.py, to create a class called Ball which will act as a template for all the balls you’ll use in the game. In the second script, juggling_balls_game.py, you’ll use this class to write the game.
It’s helpful to separate the class definitions into a standalone module to provide a clear structure and to make it easier to reuse the class in other scripts.
Let’s start working on the class in juggling_ball.py. If you want to read about object-oriented programming in Python in more detail before you dive into this project, you can read Chapter 7 | Object-Oriented Programming. However, I’ll also summarise the key points in this article in separate blocks. And remember that if you’re already familiar with the key concepts in OOP, you can skip these additional note blocks, like the one below.
A class is like a template for creating many objects using that template. When you define a class, such as the class Ball in this project, you’re not creating a ball. Instead, you’re defining the instructions needed to create a ball.
The __init__() special method is normally the first method you define in a class and includes all the steps you want to execute each time you create an object using this class.
Let’s start this 2D Python game. You can define the class and its __init__() special method:
# juggling_ball.py import random import turtle class Ball(turtle.Turtle): def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), )The class Ball inherits from turtle.Turtle which means that an object which is a Ball is also a Turtle. You also call the initialisation method for the Turtle class when you call super().__init__().
The __init__() method creates two data attributes, .width and .height. These attributes set the size in pixels of the area in which the program will create the ball.
self is the name you use as a placeholder to refer to the object that you’ll create. Recall that when you define a class, you’re not creating any objects. At this definition stage, you’re creating the template. Therefore, you need a placeholder name to refer to the objects you’ll create in the future. The convention is to use self for this placeholder name.
You create two new data attributes when you write:
self.width = width
self.height = height
An attribute belongs to an object in a class. There are two types of attributes:
– Data attributes
– Methods
You can think of data attributes as variables attached to an object. Therefore, an object “carries” its data with it. The data attributes you create are self.width and self.height.
The other type of attribute is a method. You’ll read more about methods shortly.
The rest of the __init__() method calls Turtle methods to set the initial state of each ball. Here’s a summary of the four turtle.Turtle methods used:
- .shape() changes the shape of the turtle
- .color() sets the colour of the turtle (and anything it draws)
- .penup() makes sure the turtle will not draw a line when it moves
- .setposition() places the turtle at a specific _xy-_coordinate on the screen. The centre is (0, 0).
You set the shape of the turtle to be a circle (it’s actually a disc, but the name of the shape in turtle is “circle”). You use a random value between 0 and 1 for each of the red, green, and blue colour values when you call self.color(). And you set the turtle’s position to a random integer within the bounds of the region defined by the arguments of the __init__() method. You use the floor division operator // to ensure you get an integer value when dividing the width and height by 2.
It’s a good idea to define the .__repr__() special method for a class. As you won’t use this explicitly in this project, I won’t add it to the code in the main article. However, it’s included in the final code in the appendix.
Test the classYou can test the class you just created in the second script you’ll work on as you progress through this project, juggling_balls_game.py:
# juggling_balls_game.py import turtle from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 window = turtle.Screen() window.setup(WIDTH, HEIGHT) Ball(WIDTH, HEIGHT) Ball(WIDTH, HEIGHT) turtle.done()You create a screen and set its size to 600 x 600 pixels. Next, you create two instances of the Ball class. Even though you define Ball in another script, you import it into the scope of your game program using from juggling_ball import Ball.
Here’s the output from this script:
You call Ball() twice in the script. Therefore, you create two instances of the class Ball. Each one has a random colour and moves to a random position on the screen as soon as it’s created.
An instance of a class is each object you create using that class. The class definition is the template for creating objects. You only have one class definition, but you can have several instances of that class.
Data attributes, which we discussed earlier, are sometimes also referred to as instance variables since they are variables attached to an instance. You’ll also read about instance methods soon.
You may have noticed that when you create the two balls, you can see them moving from the centre of the screen where they’re created to their ‘starting’ position. You want more control on when to display the objects on the screen.
To achieve this, you can set window.tracer(0) as soon as you create the screen and then use window.update() when you want to display the turtles on the screen. Any changes that happen to the position and orientation of Turtle objects (and Ball objects, too) will occur “behind the scenes” until you call window.update():
# juggling_balls_game.py import turtle from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) Ball(WIDTH, HEIGHT) Ball(WIDTH, HEIGHT) window.update() turtle.done()When you run the script now, the balls will appear instantly in the correct starting positions. The final call to turtle.done() runs the main loop of a turtle graphics program and is needed at the end of the script to keep the program running once the final line is reached.
You’re now ready to create a method in the Ball class to make the ball move forward.
2. Make Ball Move ForwardLet’s shift back to juggling_ball.py where you define the class. You’ll start by making the ball move upwards at a constant speed.
You can set the maximum velocity that a ball can have as a class attribute .max_velocity and then create a data attribute .velocity which will be different for each instance. The value of .velocity is a random number that’s limited by the maximum value defined at the class level:
# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity)You also change the ball’s heading using the Turtle method .setheading() so that the object is pointing upwards.
A class attribute is defined for the class overall, not for each instance. This can be used when the same value is needed for every instance you’ll create of the class. You can access a class attribute like you access instance attributes. For example, you use self.max_velocity in the example above.
Next, you can create the method move() which moves the ball forward by the value stored in self.velocity.
A method is a function that’s part of a class. You’ll only consider instance methods in this project. You can think of an instance method as a function that’s attached to an instance of the class.
In Python, you access these using the dot notation. For example, if you have a list called numbers, you can call numbers.append() or numbers.pop(). Both .append() and .pop() are methods of the class list, and every instance of a list carries these methods with it.
The method .move() is an instance method, which means the object itself is passed to the method as its first argument. This is why the parameter self is within the parentheses when you define .move().
We’re not using real world units such as metres in this project. For now, you can think of this velocity as a value measured in pixels per frame instead of metres per second. The duration of each frame is equal to the time it takes for one iteration of the while loop to complete.
Therefore, if you call .move() once per frame in the game, you want the ball to move by the number of pixels in .velocity during that frame. Let’s add the .move() method:
# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity)You can test the new additions to the class Ball in juggling_balls_game.py:
# juggling_balls_game.py import turtle from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) ball = Ball(WIDTH, HEIGHT) while True: ball.move() window.update() turtle.done()You test your code using a single ball for now and you call ball.move() within a while True loop.
Recall that .move() is a method of the class Ball. In the class definition in juggling_ball.py, you use the placeholder name self to refer to any future instance of the class you create. However, now you’re creating an instance of the class Ball, and you name it ball. Therefore, you can access all the attributes using this instance’s name, for example by calling ball.move().
Here’s the output from this script so far:
Note: the speed at which your while loop will run depends on your setup and operating system. We’ll deal with this later in this project. However, if your ball is moving too fast, you can slow it down by dividing its velocity by 10 or 100, say, when you define self.velocity in the __init__() method. If you can’t see any ball when you run this script, the ball may be moving so quickly out of the screen that you need to slow it down significantly.
You can create a ball that moves upwards with a constant speed. The next step in this 2D Python game is to account for gravity to pull the ball down.
3. Add Gravity to Pull Ball DownwardsLast time I checked, when you toss a ball upward, it will slow down, reach a point when, it’s stationary in the air for the briefest of moments, and then starts falling down towards the ground.
Let’s add the effect of gravity to the game. Gravity is a force that accelerates an object. This acceleration is given in metres per second squared (m/s^2). The acceleration due to the Earth’s gravity is 9.8m/s^2, and this is always a downward acceleration. Therefore, when an object is moving upwards, gravity will decelerate the object until its velocity is zero. Then it will start accelerating downwards.
In this project, we’re not using real-world units. So you can think of the acceleration due to gravity as a value in pixels per frame squared. “Frame” is a unit of time in this context, as it refers to the duration of the frame.
Acceleration is the rate of change of velocity. In the real world, we use the change of velocity per second. However, in the game you’re using change of velocity per frame. Later in this article, you’ll consider the time it takes for the while loop to run so you can set the frame time.
You can add a class attribute to define the acceleration due to gravity and define a method called .fall():
# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 gravity = 0.07 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity) self.fall() def fall(self): self.velocity -= self.gravityThe method .fall() changes the value of the data attribute .velocity by subtracting the value stored in the class attribute .gravity from the current .velocity. You also call self.fall() within .move() so that each time the ball moves in a frame, it’s also pulled back by gravity. In this example, the value of .gravity is 0.07. Recall that you’re measuring velocity in pixels per frame. Therefore, gravity reduces the velocity by 0.07 pixels per frame in each frame.
You could merge the code in .fall() within .move(). However, creating separate methods gives you more flexibility in the future. Let’s assume you want a version of the game in which something that happens in the game suspends gravity. Having separate methods will make future modifications easier.
You could also choose not to call self.fall() within .move() and call the method directly within the game loop in juggling_balls_game.py.
You need to consider another issue now that the balls will be pulled down towards the ground. At some point, the balls will leave the screen from the bottom edge. Once this happens, you want the program to detect this so you can deal with this. You can create another method is_below_lower_edge():
# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 gravity = 0.07 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity) self.fall() def fall(self): self.velocity -= self.gravity def is_below_lower_edge(self): if self.ycor() < -self.height // 2: self.hideturtle() return True return FalseThe method .is_below_lower_edge() is an instance method which returns a Boolean value. The method hides the turtle object and returns True if the ball has dipped below the lower edge of the screen. Otherwise, it returns False.
Methods are functions. Therefore, like all functions, they always return a value. You’ll often find methods such as .move() and .fall() that don’t have an explicit return statement. These methods change the state of one or more of the object’s attributes. These methods still return a value. As with all functions that don’t have a return statement, these methods return None.
The purpose of .is_below_lower_edge() is different. Although it’s also changing the object’s state when it calls self.hideturtle(), its main purpose is to return True or False to indicate whether the ball has dropped below the lower edge of the screen.
It’s time to check whether gravity works. You don’t need to change juggling_balls_game.py since the call to ball.move() in the while loop remains the same. Here’s the result of running the script now:
You can see the ball is tossed up in the air. It slows down. Then it accelerates downwards until it leaves the screen. You can also temporarily add the following line in the while loop to check that .is_below_lower_edge() works:
print(ball.is_below_lower_edge())Since this method returns True or False, you’ll see its decision printed out each time the loop iterates.
This 2D Python game is shaping up nicely. Next, you need to add the option to bat the ball upwards.
4. Add the Ability to Bat Ball UpwardsThere are two steps you’ll need to code to bat a ball upwards:
- Create a method in Ball to bat the ball upwards by adding positive (upward) velocity
- Call this method each time the player clicks somewhere close to the ball
You can start adding another class attribute .bat_velocity_change and the method .bat_up() in Ball:
# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 gravity = 0.07 bat_velocity_change = 8 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity) self.fall() def fall(self): self.velocity -= self.gravity def is_below_lower_edge(self): if self.ycor() < -self.height // 2: self.hideturtle() return True return False def bat_up(self): self.velocity += self.bat_velocity_changeEach time you call the method .bat_up(), the velocity of the ball increases by the value in the class attribute .bat_velocity_change.
If the ball’s velocity is, say, -10, then “batting it up” will increase the velocity to -2 since .bat_velocity_change is 8. This means the ball will keep falling but at a lower speed.
Suppose the ball’s velocity is -3, then batting up changes this to 5 so the ball will start moving upwards. And if the ball is already moving upwards when you bat it, its upward speed will increase.
You can now shift your attention to juggling_balls_game.py. You need to make no further significant changes to the class Ball itself.
In the game, you need to call the ball’s .bat_up() method when the player clicks within a certain distance of the ball. You can use .onclick() from the turtle module for this:
# juggling_balls_game.py import turtle from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 batting_tolerance = 40 window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) ball = Ball(WIDTH, HEIGHT) while True: ball.move() window.update() turtle.done()The variable batting_tolerance determines how close you need to be to the centre of the ball for the batting to take effect.
You define the function click_ball(x, y) with two parameters representing xy-coordinates. If the location of these coordinates is within the batting tolerance, then the ball’s .bat_up() method is called.
The call to window.onclick(click_ball) calls the function click_ball() and passes the xy-coordinates to it.
When you run this script, you’ll get the following output. You can test the code by clicking close to the ball:
Now, juggling one ball is nice and easy. How about juggling many balls?
5. Create More Instances of Ball Using a TimerYou can make a few changes to juggling_balls_game.py to have balls appear every few seconds. To achieve this, you’ll need to:
- Create a list to store all the Ball instances
- Create a new Ball instance every few seconds and add it to the list
- Move each of the Ball instances in the while loop
- Check all the Ball instances within click_ball() to see if the player clicked the ball
Start by tackling the first two of these steps. You define a tuple named spawn_interval_range. The program will create a new Ball every few seconds and add it to the list balls. The code will choose a new time interval from the range set in spawn_interval_range.
Since all the Ball instances are stored in the list balls, you’ll need to:
- Add a for loop in the game loop so that all Ball instances move in each frame
- Add a for loop in bat_up() to check all Ball instances for proximity to the click coordinates
You can update the code with these changes:
# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 batting_tolerance = 40 spawn_interval_range = (1, 5) window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] spawn_timer = time.time() spawn_interval = 0 while True: if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() window.update() turtle.done()You start with a spawn_interval of 0 so that a Ball is created in the first iteration of the while loop. The instance of Ball is created and placed directly in the list balls. Each time a ball is created, the code generates a new spawn_interval from the range set at the top of the script.
You then loop through all the instances of Ball in balls to call their .move() method. You use a similar loop in click_ball()
This is where we can see the benefit of OOP and classes for this type of program. You create each ball using the same template: the class Ball. This means all Ball instances have the same data attributes and can access the same methods. However, the values of their data attributes can be different.
Each ball starts at a different location and has a different colour. The .velocity data attribute for each Ball has a different value, too. And therefore, each ball will move independently of the others.
By creating all balls from the same template, you ensure they’re all similar. But they’re not identical as they have different values for their data attributes.
You can run the script to see a basic version of the game which you can test:
The program creates balls every few seconds, and you can click any of them to bat them up. However, if you play this version long enough, you may notice some odd behaviour. In the next section, you’ll see why this happens and how to fix it.
6. Add a Frame Rate to Control the Game SpeedHave a look at this video of balls created by the current script. In particular, look at what happens to those balls that rise above the top edge of the screen. Does their movement look realistic?
Did you notice how, when the ball leaves the top of the screen, it immediately reappears at the same spot falling at high speed? It’s as though the ball is bouncing off the top of the screen. However, it’s not meant to do this. This is different from what you’d expect if the ball was still rising and falling normally while it was out of sight.
Let’s first see why this happens. Then you’ll fix this problem.
How long does one iteration of the while loop take?Earlier, we discussed how the ball’s velocity is currently a value in pixels per frame. This means the ball will move by a certain number of pixels in each frame. You’re using the time it takes for each frame of the game as the unit of time.
However, there’s a problem with this logic. There are no guarantees that each frame takes the same amount of time. At the moment, the length of each frame is determined by how long it takes for the program to run one iteration of the while loop.
Look at the lines of code in the while loop. Which one do you think is the bottleneck that’s taking up most of the time?
Let’s try a small experiment. To make this a fair test, you’ll initialise the random number generator using a fixed seed so that the same “random” values are picked each time you run the program.
Let’s time 500 iterations of the while loop:
# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball random.seed(0) WIDTH = 600 HEIGHT = 600 batting_tolerance = 40 spawn_interval_range = (1, 5) window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] spawn_timer = time.time() spawn_interval = 0 start = time.time() count = 0 while count < 500: count += 1 if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() window.update() print(time.time() - start) turtle.done()You run the while loop until count reaches 500 and print out the number of seconds it takes. When I run this on my system, the output is:
8.363317966461182It took just over eight seconds to run 500 iterations of the loop.
Now, you can comment the line with window.update() at the end of the while loop. This will prevent the turtles from being displayed on the screen. All the remaining operations are still executed. The code now outputs the following:
0.004825115203857422The same 500 iterations of the while loop now take around 5ms to run. That’s almost 2,000 times faster!
Updating the display on the screen is by far the slowest part of all the steps which occur in the while loop. Therefore, if there’s only one ball on the screen and it leaves the screen, the program no longer needs to display it. The loop speeds up significantly. This is why using pixels per frame as the ball’s velocity is flawed. The same pixels per frame value results in a much faster speed when the frame time is a lot shorter!
And even if this extreme change in frame time wasn’t an issue, for example, if you’re guaranteed to always have at least one ball on the screen at any one time, you still have no control over how fast the game runs or whether the frame rate will be constant as the game progresses.
How long do you want one iteration of the while loop to take?To fix this problem and have more control over how long each frame takes, you can set your desired frame rate and then make sure each iteration of the while loop lasts for as long as needed. This is the fixed frame rate approach for running a game and works fine as long as an iteration in the while loop performs all its operations quicker than the frame time.
You can set the frame rate to 30 frames per second (fps), which is fine on most computers. However, you can choose a slower frame rate if needed. A frame rate of 30fps means that each frame takes 1/30 seconds—that’s 0.0333 seconds per frame.
Now that you’ve set the time each frame of the game should take, you can time how long the operations in the while loop take and add a delay at the end of the loop for the remaining time. This ensures each frame lasts 1/30 seconds.
You can implement the fixed frame rate in juggling_balls_game.py:
# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] spawn_timer = time.time() spawn_interval = 0 while True: frame_start_time = time.time() if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() window.update() loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) turtle.done()You start the frame timer at the beginning of the while loop. Once all frame operations are complete, you assign the amount of time taken to the variable loop_time. If this is less than the required frame time, you add a delay for the remaining time.
When you run the script now, the game will run more smoothly as you have a fixed frame rate. The velocity of the balls, measured in pixels per frame, is now a consistent value since the frame time is fixed.
You’ve completed the main aspects of the game. However, you need to have a challenge to turn this into a proper game. In the next section, you’ll add a timer and an aim in the game.
7. Add a Timer and an End to the GameTo turn this into a game, you need to:
- Add a timer
- Keep track of how many balls are lost
You can start by adding a timer and displaying it in the title bar:
# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] game_timer = time.time() spawn_timer = time.time() spawn_interval = 0 while True: frame_start_time = time.time() if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() window.title(f"Time: {time.time() - game_timer:3.1f}") window.update() loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) turtle.done()You start the game timer just before the game loop, and you display the time elapsed in the title bar in each frame of the game. The format specifier :3.1f in the f-string sets the width of the float displayed to three characters and the number of values after the decimal point to one.
Next, you can set the limit of balls you can lose before it’s ‘Game Over’! You must check whether a ball has left the screen through the bottom edge. You’ll recall you wrote the method .is_below_lower_edge() for the class Ball. This method returns a Boolean. Therefore, you can use it directly within an if statement:
# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) balls_lost_limit = 10 window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] game_timer = time.time() spawn_timer = time.time() spawn_interval = 0 balls_lost = 0 while balls_lost < balls_lost_limit: frame_start_time = time.time() if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() if ball.is_below_lower_edge(): window.update() balls.remove(ball) turtle.turtles().remove(ball) balls_lost += 1 window.title( f"Time: {time.time() - game_timer:3.1f} | Balls lost: {balls_lost}" ) window.update() loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) turtle.done()You check whether each instance of Ball has left the screen through the bottom edge as soon as you move the ball. If the ball fell through the bottom of the screen:
- You update the screen so that the ball is no longer displayed. Otherwise, you may still see the top part of the ball at the bottom of the screen
- You remove the ball from the list of all balls
- You also need to remove the ball from the list of turtles kept by the turtle module to ensure the objects you no longer need don’t stay in memory
- You add 1 to the number of balls you’ve lost
You also show the number of balls lost by adding this value to the title bar along with the amount of time elapsed. The while loop will stop iterating once you’ve lost ten balls, which is the value of balls_lost_limit.
You now have a functioning game. But you can add some finishing touches to make it better.
8. Complete the Game With Finishing TouchesWhen writing these types of games, the “finishing touches” can take as little or as long as you want. You can always do more refining and further tweaks to make the game look and feel better.
You’ll only make a few finishing touches in this article, but you can refine your game further if you wish:
- Change the background colour to dark grey
- Add a final screen to show the time taken in the game
- Ensure the balls are not created too close to the sides of the screen
You can change the background colour using .bgcolor(), which is one of the methods in the turtle module.
To add a final message on the screen, you can update the screen after the while loop and use .write(), which is a method of the Turtle class:
# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball # Game parameters WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) balls_lost_limit = 10 # Setup window window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.bgcolor(0.15, 0.15, 0.15) window.tracer(0) # Batting function def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] # Game loop game_timer = time.time() spawn_timer = time.time() spawn_interval = 0 balls_lost = 0 while balls_lost < balls_lost_limit: frame_start_time = time.time() # Spawn new ball if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() # Move balls for ball in balls: ball.move() if ball.is_below_lower_edge(): window.update() balls.remove(ball) turtle.turtles().remove(ball) balls_lost += 1 # Update window title window.title( f"Time: {time.time() - game_timer:3.1f} | Balls lost: {balls_lost}" ) # Refresh screen window.update() # Control frame rate loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) # Game over final_time = time.time() - game_timer # Hide balls for ball in balls: ball.hideturtle() window.update() # Show game over text text = turtle.Turtle() text.hideturtle() text.color("white") text.write( f"Game Over | Time taken: {final_time:2.1f}", align="center", font=("Courier", 20, "normal") ) turtle.done()After the while loop, when the game ends, you stop the game timer and clear all the remaining balls from the screen. You create a Turtle object to write the final message on the screen.
To add a border at the edge of the screen to make sure no balls are created to close too the edge, you can go back to juggling_ball.py and modify the region where a ball can be created:
# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 gravity = 0.07 bat_velocity_change = 8 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint( (-self.width // 2) + 20, (self.width // 2) - 20 ), random.randint( (-self.height // 2) + 20, (self.height // 2) - 20 ), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity) self.fall() def fall(self): self.velocity -= self.gravity def is_below_lower_edge(self): if self.ycor() < -self.height // 2: self.hideturtle() return True return False def bat_up(self): self.velocity += self.bat_velocity_changeAnd this completes the game! Unless you want to keep tweaking and adding more features. Here’s what the game looks like now:
Final WordsIn this project, you created a 2D Python game. You started by creating a class called Ball, which inherits from turtle.Turtle. This means that you didn’t have to start from scratch to create Ball. Instead, you built on top of an existing class.
Here’s a summary of the key stages when writing this game:
- Create a class named Ball and set up what should happen when you create a ball
- Make the ball move forward
- Add gravity to pull the ball downwards
- Add the ability to bat the ball upwards
- Create more balls, with each ball created after a certain time interval
- Control the speed of the game by setting a frame rate
- Add a timer and an end to the game
- Add finishing touches to the game
You first create the class Ball and add data attributes and methods. When you create an instance of Ball, the object you create already has all the properties and functionality you need a ball to have.
Once you defined the class Ball, writing the game is simpler because each Ball instance carries everything you need it to do with it.
And now, can you beat your high score in the game?
Get the latest blog updatesNo spam promise. You’ll get an email when a new blog post is published
AppendixHere are the final versions of juggling_ball.py and juggling_balls_game.py:
juggling_ball.py # juggling_ball.py import random import turtle class Ball(turtle.Turtle): """Create balls to use for juggling""" max_velocity = 5 gravity = 0.07 bat_velocity_change = 8 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint( (-self.width // 2) + 20, (self.width // 2) - 20 ), random.randint( (-self.height // 2) + 20, (self.height // 2) - 20 ), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def __repr__(self): return f"{type(self).__name__}({self.width}, {self.height})" def move(self): """Move the ball forward by the amount required in a frame""" self.forward(self.velocity) self.fall() def fall(self): """Take the effect of gravity into account""" self.velocity -= self.gravity def is_below_lower_edge(self): """ Check is object fell through the bottom :return: True if object fell through the bottom. False if object is still above the bottom edge """ if self.ycor() < -self.height // 2: self.hideturtle() return True return False def bat_up(self): """Bat the ball upwards by increasing its velocity""" self.velocity += self.bat_velocity_change juggling_balls_game.py # juggling_balls_game.py import turtle import time import random from juggling_ball import Ball # Game parameters WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) balls_lost_limit = 10 # Setup window window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.bgcolor(0.15, 0.15, 0.15) window.tracer(0) # Batting function def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] # Game loop game_timer = time.time() spawn_timer = time.time() spawn_interval = 0 balls_lost = 0 while balls_lost < balls_lost_limit: frame_start_time = time.time() # Spawn new ball if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() # Move balls for ball in balls: ball.move() if ball.is_below_lower_edge(): window.update() balls.remove(ball) turtle.turtles().remove(ball) balls_lost += 1 # Update window title window.title( f"Time: {time.time() - game_timer:3.1f} | Balls lost: {balls_lost}" ) # Refresh screen window.update() # Control frame rate loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) # Game over final_time = time.time() - game_timer # Hide balls for ball in balls: ball.hideturtle() window.update() # Show game over text text = turtle.Turtle() text.hideturtle() text.color("white") text.write( f"Game Over | Time taken: {final_time:2.1f}", align="center", font=("Courier", 20, "normal") ) turtle.done()The post Anatomy of a 2D Game using Python’s turtle and Object-Oriented Programming appeared first on The Python Coding Book.
Brett Cannon: How virtual environments work
After needing to do a deep dive on the venv module (which I will explain later in this blog post as to why), I thought I would explain how virtual environments work to help demystify them.
Why do virtual environments exist?Back in my the day, there was no concept of environments in Python: all you had was your Python installation and the current directory. That meant when you installed something you either installed it globally into your Python interpreter or you just dumped it into the current directory. Both of these approaches had their drawbacks.
Installing globally meant you didn&apost have any isolation between your projects. This led to issues like version conflicts between what one of your projects might need compared to another one. It also meant you had no idea what requirements your project actually had since you had no way of actually testing your assumptions of what you needed. This was an issue if you needed to share you code with someone else as you didn&apost have a way to test that you weren&apost accidentally wrong about what your dependencies were.
Installing into your local directory didn&apost isolate your installs based on Python version or interpreter version (or even interpreter build type, back when you had to compile your extension modules differently for debug and release builds of Python). So while you could install everything into the same directory as your own code (which you did, and thus didn&apost use src directory layouts for simplicity), there wasn&apost a way to install different wheels for each Python interpreter you had on your machine so you could have multiple environments per project (I&aposm glossing over the fact that back in my the day you also didn&apost have wheels or editable installs).
Enter virtual environments. Suddenly you had a way to install projects as a group that was tied to a specific Python interpreter. That got us the isolation/separation of only installing things you depend on (and being able to verify that through your testing), as well has having as many environments as you want to go with your projects (e.g. an environment for each version of Python that you support). So all sorts of wins! It&aposs an important feature to have while doing development (which is why it can be rather frustrating for users when Python distributors leave venv out).
How do virtual environments work?💡Virtual environments are different than conda environments (in my opinion; some people disagree with me on this view). The key difference is that conda environments allow projects to install arbitrary shell scripts which are run when you activate a conda environment (which is done implicitly when you use conda run). This is why you are always expected to activate a conda environment, as some conda packages require those those shell scripts run. I won&apost be covering conda environments in this post.Their structureThere are two parts to virtual environments: their directories and their configuration file. As a running example, I&aposm going to assume you ran the command py -m venv --without-pip .venv in some directory on a Unix-based OS (you can substitute py with whatever Python interpreter you want, including the Python Launcher for Unix).
❗For simplicity I&aposm going to focus on the Unix case and not cover Windows in depth.A virtual environment has 3 directories and potentially a symlink in the virtual environment directory (i.e. within .venv):
- bin ( Scripts on Windows)
- include ( Include on Windows)
- lib/pythonX.Y/site-packages where X.Y is the Python version (Lib/site-packages on Windows)
- lib64 symlinked to lib if you&aposre using a 64-bit build of Python that&aposs on a POSIX-based OS that&aposs not macOS
The Python executable for the virtual environment ends up in bin as various symlinks back to the original interpreter (e.g. .venv/bin/python is a symlink; Windows has a different story). The site-packages directory is where projects get installed into the virtual environment (including pip if you choose to have it installed into the virtual environment). The include directory is for any header files that might get installed for some reason from a project. The lib64 symlink is for consistency on those Unix OSs where they have such directories.
The configuration file is pyvenv.cfg and it lives at the top of your virtual environment directory (e.v. .venv/pyvenv.cfg). As of Python 3.11, it contains a few entries:
- home (the directory where the executable used to create the virtual environment lives; os.path.dirname(sys._base_executable))
- include-system-packages (should the global site-packages be included, effectively turning off isolation?)
- version (the Python version down to the micro version, but not with the release level, e.g. 3.12.0, but not 3.12.0a6)
- executable (the executable used to create the virtual environment; os.path.realpath(sys._base_executable))
- command (the CLI command that could have recreated the virtual environment)
On my machine, the pyvenv.cfg contents are:
home = /home/linuxbrew/.linuxbrew/opt/python@3.11/bin include-system-site-packages = false version = 3.11.2 executable = /home/linuxbrew/.linuxbrew/Cellar/python@3.11/3.11.2_1/bin/python3.11 command = /home/linuxbrew/.linuxbrew/opt/python@3.11/bin/python3.11 -m venv --without-pip /tmp/.venvExample pyvenv.cfgOne interesting thing to note is pyvenv.cfg is not a valid INI file according to the configparser module due to lacking any sections. To read fields in the file you are expected to use line.partition("=") and to strip the resulting key and value.
And that&aposs all there is to a virtual environment! When you don&apost install pip they are extremely fast to create: 3 files, a symlink, and a single file. And they are simple enough you can probably create one manually.
One point I would like to make is how virtual environments are designed to be disposable and not relocatable. Because of their simplicity, virtual environments are viewed as something you can throw away and recreate quickly (if it takes your OS a long time to create 3 directories, a symlink, and a file consisting of 292 bytes like on my machine, you have bigger problems to worry about than virtual environment relocation 😉). Unfortunately, people tend to conflate environment creation with package installation, when they are in fact two separate things. What projects you choose to install with which installer is actually separate from environment creation and probably influences your "getting started" time the most.
How Python uses a virtual environmentDuring start-up, Python automatically calls the site.main() function (unless you specify the -S flag). That function calls site.venv() which handles setting up your Python executable to use the virtual environment appropriately. Specifically, the site module:
- Looks for pyvenv.cfg in either the same or parent directory as the running executable (which is not resolved, so the location of the symlink is used)
- Looks for include-system-site-packages in pyvenv.cfg to decide whether the system site-packages ends up on sys.path
- Sets sys._home if home is found in pyvenv.cfg (sys._home is used by sysconfig)
That&aposs it! It&aposs a surprisingly simple mechanism for what it accomplishes.
When thing to notice here about how all of this works is virtual environment activation is optional. Because the site module works off of the symlink to the executable in the virtual environment to resolve everything, activation is just a convenience. Honestly, all the activation scripts do are:
- Puts the bin/ (or Scripts/) directory at the front of your PATH environment variable
- Sets VIRTUAL_ENV to the directory containing your virtual environment
- Tweaks your shell prompt to let you know your PATH has been changed
- Registers a deactivate shell function which undoes the other steps
In the end, whether you type python after activation or .venv/bin/python makes no difference to Python. Some tooling like the Python extension for VS Code or the Python Launcher for Unix may check for VIRTUAL_ENV to pick up on your intent to use a virtual environment, but it doesn&apost influence Python itself.
Introducing microvenvIn the Python extension for VS Code, we have an issue where Python beginners end up on Debian or a Debian-based distro like Ubuntu and want to create a virtual environment. Due to Debian removing venv from the default Python install and beginners not realizing there was more to install than python3, they often end up failing at creating a virtual environment (at least initially as you can install python3-venv separately; in the next version of Debian there will be a python3-full package you can install which will include venv and pip, but it will probably take a while for all the instructions online to be updated to suggest that over python3). We believe the lack of venv is a problem as beginners should be using environments, but asking them to install yet more software can be a barrier to getting started (I&aposm also ignoring the fact pip isn&apost installed by default on Debian either which also complicates the getting started experience for beginners).
But venv is not shipped as a separate part of Python&aposs stdlib, so we can&apost simply install it from PyPI somehow or easily ship it as part of the Python extension to work around this. Since venv is in the stdlib, it&aposs developed along with the version of Python it ships with, so there&aposs no single copy which is fully compatible with all maintained versions of Python (e.g. Python 3.11 added support to use sysconfig to get the directories to create for a virtual environment, various fields in pyvenv.cfg have been added over time, use new language features may be used, etc.). While we could ship a copy of venv for every maintained version of Python, we potentially would have to ship for every micro release to guarantee we always had a working copy, and that&aposs a lot of upstream tracking to do. And even if we only shipped copies from minor release of Python, we would still have to track every micro release in case a bug in venv was fixed.
Hence I have created microvenv. It is a project which provides a single .py file which you use to create a minimal virtual environment. You can either execute it as a script or call its create() function that is analogous to venv.create(). It&aposs also compatible with all maintained versions of Python. As I (hopefully) showed above, creating a virtual environment is actually straight-forward, so I was able to replicate the necessary bits in less than 100 lines of Python code (specifically 87 lines in the 2023.1.1 release). That actually makes it small enough to pass in via python -c, which means it could be embedded in a binary as a string constant and passed as an argument when executing a Python executable as a subprocess. Hopefully that means a tool could guarantee it can always construct a virtual environment somehow.
To keep it microvenv simple, small, and maintainable, it does not contain any activation scripts. I personally don&apost want to be a shell script expert for multiple shells, nor do I want to track the upstream activation scripts (and they do change in case you were thinking "it shouldn&apost be that hard to track"). Also, in VS Code we are actually working towards implicitly activating virtual environments by updating your environment variables directly instead of executing any activation shell scripts, so the shell scripts aren&apost needed for our use case (we are actively moving away from using any activation scripts where we can as we have run into race condition problems with them when sending the command to the shell; thank goodness of conda run, but we also know people still want an activated terminal).
I&aposm also skipping Windows support because we have found the lack of venv to be a unique problem for Linux in general, and Debian-based distros specifically.
I honestly don&apost expect anyone except tool providers to use microvenv, but since it could be useful to others beyond VS Code, I decided it was worth releasing on its own. I also expect anyone using the project to only use it as a fallback when venv is not available (which you can deduce by running py -c "from importlib.util import find_spec; print(find_spec(&aposvenv&apos) is not None)"). And before anyone asks why we don&apost just use virtualenv, its wheel is 8.7MB compared to microvenv at 3.9KB; 0.05% the size, or 2175x smaller. Granted, a good chunk of what makes up virtualen&aposs wheel is probably from shipping pip and setuptools in the wheel for fast installation of those projects after virtual environment creation, but we also acknowledge our need for a small, portable, single-file virtual environment creator is rather niche and something virtualenv currently doesn&apost support (for good reason).
Our plan for the Python extension for VS Code is to use microvenv as a fallback mechanism for our Python: Create Environment command (FYI we also plan to bootstrap pip via its pip.pyz file from bootstrap.pypa.io by downloading it on-demand, which is luckily less than 2MB). That way we can start suggesting to users in various UX flows to create and use an environment when one isn&apost already being used (as appropriate, of course). We want beginners to learn about environments if they don&apost already know about them and also remind experienced users when they may have accidentally forgotten to create an environment for their workspace. That way people get the benefit of (virtual) environments with as little friction as possible.
Kushal Das: Everything Curl from Daniel
Everything Curl is a book about everything related to Curl. I read the book before online. But, now I am proud to have a physical copy signed by the author :)
It was a good evening, along with some amazing fish in dinner and wine and lots of chat about life in general.
Talk Python to Me: #406: Reimagining Python's Packaging Workflows
ListenData: Complete Guide to Visual ChatGPT
In this post, we will talk about how to run Visual ChatGPT in Python with Google Colab. ChatGPT has garnered huge popularity recently due to its capability of human style response. As of now, it only provides responses in text format, which means it cannot process, generate or edit images. Microsoft recently released a solution for the same to handle images. Now you can ask ChatGPT to generate or edit the image for you.
Demo of Visual ChatGPTIn the image below, you can see the final output of Visual ChatGPT - how it looks like.
READ MORE »Matt Layman: Learn Django or Ruby on Rails?
CodersLegacy: Python Tkinter Project with MySQL Database
In this Python Project, we will be discussing how to integrate a MySQL Database into our Tkinter application.
Why do we need a MySQL Database?But first, let us discuss “why” we need a MySQL Database. The need for a database arises when we need to permanently store data somewhere. Larger applications, such as Reporting software, Graphing applications, etc. need some place to store relevant data, from where it can be retrieved at a later date.
A common alternative is the use of “text files” to store data. Although the text file approach is simpler (in the short term), databases have several advantages.
- Scalability
- Performance
- Security
- Backups
However, Databases might be a bit over-kill for simpler applications. It’s really a case-by-case thing, where you need to evaluate which would be more suitable for your application.
Let’s begin with the tutorial.
Pre-requisitesWe are assuming you already have a working MySQL installation setup. If not, kindly complete that step before attempting to use any of the code in this tutorial. IT IS IMPORTANT THAT YOU REMEMBER THE USERNAME AND PASSWORD YOU USED DURING ITS INSTALLATION. DON’T FORGET.
Once you have installed MySQL, the next thing we need to do is install the “mysql-connector-python” library in Python.
pip show mysql-connector-pythonThis library will allow us to connect with the MySQL database, from our Python code, and execute SQL queries.
Initializing a MySQL Connection for our Tkinter ProjectSince this a large application, I will take a proper approach than what I usually do. We will be creating two files, one called database.py, and one called UI.py. First, we will create define some basic functions inside the database.py file, before we proceed to the UI.py file.
Here is the first function, used to initialize a connection to the MySQL Database.
def initialize_connection(): conn = mysql.connector.connect( host = "localhost", user = "root", password = "db1963" ) cursor = conn.cursor() return conn, cursorHere we call the “connect” method with three arguments – host, user, and password.
The “host” parameter specifies the location of the MySQL database server. In this code, the location is set to “localhost”, which means that the MySQL server is running on the same machine where the Python code is being executed.
The “user” parameter specifies the username that is used to log in to the MySQL database. The “password” parameter is used to specify the password associated with the user account. Enter your own username and password here, which you used to setup your MySQL database.
After the connection object is created, the next line of code creates a cursor object. The cursor object is used to execute SQL queries and interact with the MySQL database. We then return both the cursor object, and the connection object from this function.
We will later call this function from the UI.py file.
Creating a MySQL DatabaseAlthough we have initialized a MySQL connection, we still need to create a Database for our project. There is already a default database created, called “sys”, but we will be creating a new one called “tutorial”. for this application.
The below code creates a new database in a safe manner. It first checks to ensure that there already isn’t a database called “tutorial”. If it does not exist, it will execute the “CREATE DATABASE tutorial” command to create it.
def create_database(cursor): cursor.execute("SHOW DATABASES") temp = cursor.fetchall() databases = [item[0] for item in temp] if "tutorial" not in databases: cursor.execute("CREATE DATABASE tutorial") cursor.execute("USE tutorial")At the very end it calls the “USE tutorial” command, to switch from the default database to the new one. This line must be called regardless of whether our database exists already or not.
We still aren’t done with our setup.
Next, we have to create a “Table”. A Table is basically where we are going to be storing data for a certain entity/purpose. In larger applications, a database consists of multiple tables. For example, in a game, there might be a table for “Players”, one table for “NPC’s”, one for “Enemies”, one for “Quests”, and so on.
In our application, we will storing the records for Users. So we will only have one table for “Users”. The below code creates this table, in the same manner as we created the database.
def create_table(cursor): cursor.execute("SHOW TABLES") temp = cursor.fetchall() tables = [item[0] for item in temp] if "users" not in tables: cursor.execute("""CREATE TABLE users( id INT AUTO_INCREMENT PRIMARY KEY, firstName VARCHAR(100), lastName VARCHAR(100), password VARCHAR(30), email VARCHAR(100) UNIQUE, gender VARCHAR(1), age INT, address VARCHAR(200) )""")The above code is a little bit SQL-heavy due to the large table, with several fields. Each field has a name, a datatype, and some optional constraints. We have used three such constraints here. First we have “PRIMARY KEY” which designates the “id” field as a unique identifier we can use to query a specific user.
Likewise, we also declare the “email” as a unique column. These constraints will automatically prevent duplicates from being entered. We also have AUTO_INCREMENT for “id”, which means I will not be specifying an id, rather it will be auto generated in the numeric sequence.
We can also define lengths for each text-based field. The password has a limit of 30, names and email have a limit of 100, and gender has a limit of 1 character.
Finally after all this, we need to call these two methods somewhere.
We will place them both into the initialize_connection() function we created earlier.
def initialize_connection(): conn = mysql.connector.connect( host = "localhost", user = "root", password = "db1963" ) cursor = conn.cursor() create_database(cursor) create_table(cursor) return conn, cursorOur setup is now complete!
Creating the Tkinter ProjectThe below code features a small Tkinter project we put together, which will we integrate the MySQL Database code into. There is nothing extra in the application, just a register and login window. The main window is blank, as it doesn’t really have anything to do with this tutorial. Our only concern is registering a user into our database, and logging them in later with their credentials.
Most of this is just standard tkinter code, so take your time going through it. The only thing of note right now, is the Login and Register windows, along with the imports we made from the database.py file (second line). We called the initialize_connection() method, which returned the connection and cursor object. We will be needing these later, when calling the login and register and functions.
(Screenshot of the application shown after the below code)
import tkinter as tk from database import * conn, cursor = initialize_connection() def center_window(width, height): x = (root.winfo_screenwidth() // 2) - (width // 2) y = (root.winfo_screenheight() // 2) - (height // 2) root.geometry(f'{width}x{height}+{x}+{y}') class WelcomeWindow(tk.Frame): def __init__(self, master): super().__init__() self.master = master self.master.title("Welcome") center_window(240, 120) login_button = tk.Button(self, text="Login", width=10, command=self.open_login_window) login_button.pack(padx=20, pady=(20, 10)) register_button = tk.Button(self, text="Register", width=10, command=self.open_register_window) register_button.pack(pady=10) self.pack() def open_login_window(self): for widget in self.winfo_children(): widget.destroy() self.destroy() LoginWindow(self.master) def open_register_window(self): for widget in self.winfo_children(): widget.destroy() self.destroy() RegisterWindow(self.master) class LoginWindow(tk.Frame): def __init__(self, master): super().__init__() self.master = master self.master.title("Login") self.master.resizable(False, False) center_window(240, 150) tk.Label(self, text="Username:").grid(row=0, column=0) self.username_entry = tk.Entry(self) self.username_entry.grid(row=0, column=1, padx=10, pady=10) tk.Label(self, text="Password:").grid(row=1, column=0) self.password_entry = tk.Entry(self, show="*") self.password_entry.grid(row=1, column=1, padx=10, pady=10) submit_button = tk.Button(self, text="Submit", width=8, command=self.submit) submit_button.grid(row=2, column=1, sticky="e", padx=10, pady=(10, 0)) submit_button = tk.Button(self, text="Back", width=8, command=self.back) submit_button.grid(row=2, column=0, sticky="w", padx=10, pady=(10, 0)) self.pack() def submit(self): pass def back(self): for widget in self.winfo_children(): widget.destroy() self.destroy() WelcomeWindow(self.master) class RegisterWindow(tk.Frame): def __init__(self, master): super().__init__() self.master = master self.master.title("Register") self.master.resizable(False, False) center_window(320, 350) tk.Label(self, text="First Name:").grid(row=0, column=0, sticky="w") self.first_name_entry = tk.Entry(self, width=26) self.first_name_entry.grid(row=0, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Last Name:").grid(row=1, column=0, sticky="w") self.last_name_entry = tk.Entry(self, width=26) self.last_name_entry.grid(row=1, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Password:").grid(row=2, column=0, sticky="w") self.password_entry = tk.Entry(self, show="*", width=26) self.password_entry.grid(row=2, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Email:").grid(row=3, column=0, sticky="w") self.email_entry = tk.Entry(self, width=26) self.email_entry.grid(row=3, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Gender:").grid(row=4, column=0, sticky="w") self.gender_entry = tk.Entry(self, width=10) self.gender_entry.grid(row=4, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Age:").grid(row=5, column=0, sticky="w") self.age_entry = tk.Entry(self, width=10) self.age_entry.grid(row=5, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Address:").grid(row=6, column=0, sticky="w") self.address_entry = tk.Text(self, width=20, height=3) self.address_entry.grid(row=6, column=1, padx=10, pady=10, sticky="e") submit_button = tk.Button(self, text="Submit", width=8, command=self.submit) submit_button.grid(row=7, column=1, padx=10, pady=10, sticky="e") submit_button = tk.Button(self, text="Back", width=8, command=self.back) submit_button.grid(row=7, column=0, sticky="w", padx=10, pady=(10, 10)) self.pack() def submit(self): pass def back(self): for widget in self.winfo_children(): widget.destroy() self.destroy() WelcomeWindow(self.master) class MainWindow(tk.Frame): def __init__(self, master): super().__init__() self.master = master center_window(600, 400) self.pack() root = tk.Tk() root.eval('tk::PlaceWindow . center') WelcomeWindow(root) root.mainloop()Welcome Page:
Login Page:
Register Page:
It is important to note, that in the above code, the “submit” functions for both the Login and Register window are empty. We will developing them in the next two sections.
Registering a UserLet’s implement the registration functionality into our code. The first thing we need to do is extract the required data from our tkinter widgets (in the register window) and then pass this data to a “register” function in our database.py file, which we will create soon.
Here is the submit function in the Register window that we have filled out. We just extract all the data into a dictionary, then pass this into a register function, along with the connection and cursor we created earlier with the initialize_connection() function.
def submit(self): data = {} data["firstName"]= self.first_name_entry.get() data["lastName"]= self.last_name_entry.get() data["password"]= self.password_entry.get() data["email"]= self.email_entry.get() data["gender"]= self.gender_entry.get() data["age"]= self.age_entry.get() data["address"]= self.address_entry.get(1.0, tk.END) register(cursor, conn, data)Next, we will implement this register function in the database.py file.
We have made use of string formatting here, to insert all the values from the dictionary into a SQL INSERT INTO command, which inserts values into a Table. The values must be in the sequence that we defined the tables. (The first value is NULL, because that is the id, which will be assigned automatically)
def register(cursor, conn, data): cursor.execute(f"""INSERT INTO users values( NULL, '{data["firstName"]}', '{data["lastName"]}', '{data["password"]}', '{data["email"]}', '{data["gender"]}', '{data["age"]}', '{data["address"]}' )""") conn.commit()Lastly, we need to call the “commit” method, which basically implements the change we made. Think of it like “saving” changes to the database. The concept of commits is important when it comes to transaction control, and recovery in databases, which is a whole separate topic.
Our register function is now complete! We can begin registering users, but let’s wait until we have implemented the login functionality.
Logging a User inNext, we will work on the two functions for login functionality, the submit() function inside the Login Window, and login() function inside the database.py file.
Here is the submit() function. We haven’t defined the login() function yet, but we have already decided beforehand that it will be returning either True, or False, depending on whether the login was successful or not.
def submit(self): data = {} data["email"] = self.username_entry.get() data["password"] = self.password_entry.get() if login(cursor, data) == True: print("successful login") for widget in self.winfo_children(): widget.destroy() self.destroy() MainWindow(self.master) else: print("unsuccessful login")Here is the login() function, where we execute a “SELECT” command by filtering out users based on the given “email” and “password” using the “WHERE” clause. If the login was successful, we destroy the current window, and open the main window.
def login(cursor, data): cursor.execute(f"""SELECT * FROM users WHERE email = '{data["email"]}' AND password = '{data["password"]}' """) if cursor.fetchone() != None: return True return FalseWe then return a True or False value, depending on the whether a record was successfully return or not. A “None” result means no records were found.
We decided to use “email” as the “username”, since it was a unique field. It’s not a good idea to expect the user to use a numerical digit as their login, and no other field is unique to be used as a username. You may wish to create a separate field for “username” during registration if you want.
Testing our Tkinter Project + MySQL DatabaseLet’s fire up our application now and try it out. The first thing to do is go and register a new user, in the register window.
Next, I will hit submit to register the user, then navigate to the login page to try and login. If the login succeeds, we should be re-directed to the main window.
Here is the complete code so that you can try it out for yourself.
The UI.py file.
import tkinter as tk from database import * conn, cursor = initialize_connection() def center_window(width, height): x = (root.winfo_screenwidth() // 2) - (width // 2) y = (root.winfo_screenheight() // 2) - (height // 2) root.geometry(f'{width}x{height}+{x}+{y}') class WelcomeWindow(tk.Frame): def __init__(self, master): super().__init__() self.master = master self.master.title("Welcome") center_window(240, 120) login_button = tk.Button(self, text="Login", width=10, command=self.open_login_window) login_button.pack(padx=20, pady=(20, 10)) register_button = tk.Button(self, text="Register", width=10, command=self.open_register_window) register_button.pack(pady=10) self.pack() def open_login_window(self): for widget in self.winfo_children(): widget.destroy() self.destroy() LoginWindow(self.master) def open_register_window(self): for widget in self.winfo_children(): widget.destroy() self.destroy() RegisterWindow(self.master) class LoginWindow(tk.Frame): def __init__(self, master): super().__init__() self.master = master self.master.title("Login") self.master.resizable(False, False) center_window(240, 150) tk.Label(self, text="Username:").grid(row=0, column=0) self.username_entry = tk.Entry(self) self.username_entry.grid(row=0, column=1, padx=10, pady=10) tk.Label(self, text="Password:").grid(row=1, column=0) self.password_entry = tk.Entry(self, show="*") self.password_entry.grid(row=1, column=1, padx=10, pady=10) submit_button = tk.Button(self, text="Submit", width=8, command=self.submit) submit_button.grid(row=2, column=1, sticky="e", padx=10, pady=(10, 0)) submit_button = tk.Button(self, text="Back", width=8, command=self.back) submit_button.grid(row=2, column=0, sticky="w", padx=10, pady=(10, 0)) self.pack() def submit(self): data = {} data["email"] = self.username_entry.get() data["password"] = self.password_entry.get() if login(cursor, data) == True: print("successful login") for widget in self.winfo_children(): widget.destroy() self.destroy() MainWindow(self.master) else: print("unsuccessful login") def back(self): for widget in self.winfo_children(): widget.destroy() self.destroy() WelcomeWindow(self.master) class RegisterWindow(tk.Frame): def __init__(self, master): super().__init__() self.master = master self.master.title("Register") self.master.resizable(False, False) center_window(320, 350) tk.Label(self, text="First Name:").grid(row=0, column=0, sticky="w") self.first_name_entry = tk.Entry(self, width=26) self.first_name_entry.grid(row=0, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Last Name:").grid(row=1, column=0, sticky="w") self.last_name_entry = tk.Entry(self, width=26) self.last_name_entry.grid(row=1, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Password:").grid(row=2, column=0, sticky="w") self.password_entry = tk.Entry(self, show="*", width=26) self.password_entry.grid(row=2, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Email:").grid(row=3, column=0, sticky="w") self.email_entry = tk.Entry(self, width=26) self.email_entry.grid(row=3, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Gender:").grid(row=4, column=0, sticky="w") self.gender_entry = tk.Entry(self, width=10) self.gender_entry.grid(row=4, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Age:").grid(row=5, column=0, sticky="w") self.age_entry = tk.Entry(self, width=10) self.age_entry.grid(row=5, column=1, padx=10, pady=10, sticky="e") tk.Label(self, text="Address:").grid(row=6, column=0, sticky="w") self.address_entry = tk.Text(self, width=20, height=3) self.address_entry.grid(row=6, column=1, padx=10, pady=10, sticky="e") submit_button = tk.Button(self, text="Submit", width=8, command=self.submit) submit_button.grid(row=7, column=1, padx=10, pady=10, sticky="e") submit_button = tk.Button(self, text="Back", width=8, command=self.back) submit_button.grid(row=7, column=0, sticky="w", padx=10, pady=(10, 10)) self.pack() def submit(self): data = {} data["firstName"] = self.first_name_entry.get() data["lastName"] = self.last_name_entry.get() data["password"] = self.password_entry.get() data["email"] = self.email_entry.get() data["gender"] = self.gender_entry.get() data["age"] = self.age_entry.get() data["address"] = self.address_entry.get(1.0, tk.END) register(cursor, conn, data) def back(self): for widget in self.winfo_children(): widget.destroy() self.destroy() WelcomeWindow(self.master) class MainWindow(tk.Frame): def __init__(self, master): super().__init__() self.master = master center_window(600, 400) self.pack() root = tk.Tk() root.eval('tk::PlaceWindow . center') WelcomeWindow(root) root.mainloop()The database.py file.
import mysql.connector def initialize_connection(): conn = mysql.connector.connect( host = "localhost", user = "root", password = "db1963" ) cursor = conn.cursor() create_database(cursor) create_table(cursor) return conn, cursor def create_database(cursor): cursor.execute("SHOW DATABASES") temp = cursor.fetchall() databases = [item[0] for item in temp] if "tutorial" not in databases: cursor.execute("CREATE DATABASE tutorial") cursor.execute("USE tutorial") def create_table(cursor): cursor.execute("SHOW TABLES") temp = cursor.fetchall() tables = [item[0] for item in temp] if "users" not in tables: cursor.execute("""CREATE TABLE users( id INT AUTO_INCREMENT PRIMARY KEY, firstName VARCHAR(100), lastName VARCHAR(100), password VARCHAR(30), email VARCHAR(100) UNIQUE, gender VARCHAR(1), age INT, address VARCHAR(200) )""") def login(cursor, data): cursor.execute(f"""SELECT * FROM users WHERE email = '{data["email"]}' AND password = '{data["password"]}' """) if cursor.fetchone() != None: return True return False def register(cursor, conn, data): print(data) cursor.execute(f"""INSERT INTO users values( NULL, '{data["firstName"]}', '{data["lastName"]}', '{data["password"]}', '{data["email"]}', '{data["gender"]}', '{data["age"]}', '{data["address"]}' )""") conn.commit()This marks the end of the Python Tkinter Project with MySQL Database Tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.
The post Python Tkinter Project with MySQL Database appeared first on CodersLegacy.
Anarcat: how to audit for open services with iproute2
The computer world has a tendency of reinventing the while once in a while. I am not a fan of that process, but sometimes I just have to bite the bullet and adapt to change. This post explains how I adapted to one particular change: the netstat to sockstat transition.
I used to do this to show which processes where listening on which port on a server:
netstat -anpeIt was a handy mnemonic as, in France, ANPE was the agency responsible for the unemployed (basically). That would list all sockets (-a), not resolve hostnames (-n, because it's slow), show processes attached to the socket (-p) with extra info like the user (-e). This still works, but sometimes fail to find the actual process hooked to the port. Plus, it lists a whole bunch of UNIX sockets and non-listening sockets, which are generally irrelevant for such an audit.
What I really wanted to use was really something like:
netstat -pleunt | sort... which has the "pleut" mnemonic ("rains", but plural, which makes no sense and would be badly spelled anyway). That also only lists listening (-l) and network sockets, specifically UDP (-u) and TCP (-t).
But enough with the legacy, let's try the brave new world of sockstat which has the unfortunate acronym ss.
The equivalent sockstat command to the above is:
ss -pleuntOIt's similar to the above, except we need the -O flag otherwise ss does that confusing thing where it splits the output on multiple lines. But I actually use:
ss -plunt0... i.e. without the -e as the information it gives (cgroup, fd number, etc) is not much more useful than what's already provided with -p (service and UID).
All of the above also show sockets that are not actually a concern because they only listen on localhost. Those one should be filtered out. So now we embark into that wild filtering ride.
This is going to list all open sockets and show the port number and service:
ss -pluntO --no-header | sed 's/^\([a-z]*\) *[A-Z]* *[0-9]* [0-9]* *[0-9]* */\1/' | sed 's/^[^:]*:\(:\]:\)\?//;s/\([0-9]*\) *[^ ]*/\1\t/;s/,fd=[0-9]*//' | sort -guFor example on my desktop, it looks like:
anarcat@angela:~$ sudo ss -pluntO --no-header | sed 's/^\([a-z]*\) *[A-Z]* *[0-9]* [0-9]* *[0-9]* */\1/' | sed 's/^[^:]*:\(:\]:\)\?//;s/\([0-9]*\) *[^ ]*/\1\t/;s/,fd=[0-9]*//' | sort -gu [::]:* users:(("unbound",pid=1864)) 22 users:(("sshd",pid=1830)) 25 users:(("master",pid=3150)) 53 users:(("unbound",pid=1864)) 323 users:(("chronyd",pid=1876)) 500 users:(("charon",pid=2817)) 631 users:(("cups-browsed",pid=2744)) 2628 users:(("dictd",pid=2825)) 4001 users:(("emacs",pid=3578)) 4500 users:(("charon",pid=2817)) 5353 users:(("avahi-daemon",pid=1423)) 6600 users:(("systemd",pid=3461)) 8384 users:(("syncthing",pid=232169)) 9050 users:(("tor",pid=2857)) 21027 users:(("syncthing",pid=232169)) 22000 users:(("syncthing",pid=232169)) 33231 users:(("syncthing",pid=232169)) 34953 users:(("syncthing",pid=232169)) 35770 users:(("syncthing",pid=232169)) 44944 users:(("syncthing",pid=232169)) 47337 users:(("syncthing",pid=232169)) 48903 users:(("mosh-client",pid=234126)) 52774 users:(("syncthing",pid=232169)) 52938 users:(("avahi-daemon",pid=1423)) 54029 users:(("avahi-daemon",pid=1423)) anarcat@angela:~$But that doesn't filter out the localhost stuff, lots of false positive (like emacs, above). And this is where it gets... not fun, as you need to match "localhost" but we don't resolve names, so you need to do some fancy pattern matching:
ss -pluntO --no-header | \ sed 's/^\([a-z]*\) *[A-Z]* *[0-9]* [0-9]* *[0-9]* */\1/;s/^tcp//;s/^udp//' | \ grep -v -e '^\[fe80::' -e '^127.0.0.1' -e '^\[::1\]' -e '^192\.' -e '^172\.' | \ sed 's/^[^:]*:\(:\]:\)\?//;s/\([0-9]*\) *[^ ]*/\1\t/;s/,fd=[0-9]*//' |\ sort -guThis is kind of horrible, but it works, those are the actually open ports on my machine:
anarcat@angela:~$ sudo ss -pluntO --no-header | sed 's/^\([a- z]*\) *[A-Z]* *[0-9]* [0-9]* *[0-9]* */\1/;s/^tcp//;s/^udp//' | grep -v -e '^\[fe80::' -e '^127.0.0.1' -e '^\[::1\]' -e '^192\.' - e '^172\.' | sed 's/^[^:]*:\(:\]:\)\?//;s/\([0-9]*\) *[^ ]*/\ 1\t/;s/,fd=[0-9]*//' | sort -gu 22 users:(("sshd",pid=1830)) 500 users:(("charon",pid=2817)) 631 users:(("cups-browsed",pid=2744)) 4500 users:(("charon",pid=2817)) 5353 users:(("avahi-daemon",pid=1423)) 6600 users:(("systemd",pid=3461)) 21027 users:(("syncthing",pid=232169)) 22000 users:(("syncthing",pid=232169)) 34953 users:(("syncthing",pid=232169)) 35770 users:(("syncthing",pid=232169)) 48903 users:(("mosh-client",pid=234126)) 52938 users:(("avahi-daemon",pid=1423)) 54029 users:(("avahi-daemon",pid=1423))Surely there must be a better way. It turns out that lsof can do some of this, and it's relatively straightforward. This lists all listening TCP sockets:
lsof -iTCP -sTCP:LISTEN +c 15 | grep -v localhost | sortIn theory, this would do the equivalent on UDP
lsof -iUDP -sUDP:^Idle... but in reality, it looks like lsof on Linux can't figure out the state of a UDP socket:
lsof: no UDP state names available: UDP:^Idle... which, honestly, I'm baffled by. It's strange because ss can figure out the state of those sockets, heck it's how -l vs -a works after all. So we need something else to show listening UDP sockets.
The following actually looks pretty good after all:
ss -pluOThat will list localhost sockets of course, so we can explicitly ask ss to resolve those and filter them out with something like:
ss -plurO | grep -v localhostoh, and look here! ss supports pattern matching, so we can actually tell it to ignore localhost directly, which removes that horrible sed line we used earlier:
ss -pluntO '! ( src = localhost )'That actually gives a pretty readable output. One annoyance is we can't really modify the columns here, so we still need some god-awful sed hacking on top of that to get a cleaner output:
ss -nplutO '! ( src = localhost )' | \ sed 's/\(udp\|tcp\).*:\([0-9][0-9]*\)/\2\t\1\t/;s/\([0-9][0-9]*\t[udtcp]*\t\)[^u]*users:(("/\1/;s/".*//;s/.*Address:Port.*/Netid\tPort\tProcess/' | \ sort -nuThat looks horrible and is basically impossible to memorize. But it sure looks nice:
anarcat@angela:~$ sudo ss -nplutO '! ( src = localhost )' | sed 's/\(udp\|tcp\).*:\([0-9][0-9]*\)/\2\t\1\t/;s/\([0-9][0-9]*\t[udtcp]*\t\)[^u]*users:(("/\1/;s/".*//;s/.*Address:Port.*/Port\tNetid\tProcess/' | sort -nu Port Netid Process 22 tcp sshd 500 udp charon 546 udp NetworkManager 631 udp cups-browsed 4500 udp charon 5353 udp avahi-daemon 6600 tcp systemd 21027 udp syncthing 22000 udp syncthing 34953 udp syncthing 35770 udp syncthing 48903 udp mosh-client 52938 udp avahi-daemon 54029 udp avahi-daemonBetter ideas welcome.
Python for Beginners: Pandas Series to DataFrame in Python
Pandas series and dataframes are the two data structures that we use extensively to manipulate sequential or tabular data in Python. Sometimes, we need to convert one or multiple series objects to a dataframe in Python. This article discusses how to convert a pandas series to a dataframe in python.
Table of Contents- Convert Pandas Series to DataFrame Using the to_frame() Method
- Pandas Series to DataFrame Using the DataFrame() Function
- Convert the index of the series to a column in dataframe
- Convert the Index of A Series to Columns in The DataFrame Using the DataFrame() Function
- Multiple Pandas Series to DataFrame in Python
- Conclusion
We can convert a pandas series to a dataframe using the to_frame() method. The to_frame() method, when invoked on a series object, returns the dataframe representation of the series. You can observe this in the following example.
import pandas as pd numbers=[100,90,80,90,70,100,60] series=pd.Series(numbers) print("The series is:") print(series) df=series.to_frame() print("The dataframe is:") print(df)Output:
The series is: 0 100 1 90 2 80 3 90 4 70 5 100 6 60 dtype: int64 The dataframe is: 0 0 100 1 90 2 80 3 90 4 70 5 100 6 60In the above example, we have first converted a list into a series using the Series() function. Then, we used the to_frame() method to convert the series to a dataframe. Here, the input series doesn’t contain any index.
If the input series contains indices, they are converted to indices of the dataframe. You can observe this in the following example.
import pandas as pd numbers=[100,90,80,90,70,100,60] indices=[1,2,4,5,6,7,8] series=pd.Series(numbers,index=indices) print("The series is:") print(series) df=series.to_frame() print("The dataframe is:") print(df)Output:
The series is: 1 100 2 90 4 80 5 90 6 70 7 100 8 60 dtype: int64 The dataframe is: 0 1 100 2 90 4 80 5 90 6 70 7 100 8 60In this example, we first created a series having custom indices. Then, we converted it into a dataframe using the to_frame() method. You can observe that the indices of the series have been converted into dataframe index.
When we convert a pandas series to a dataframe using the to_frame() method, the newly created column in the dataframe is named “0”.
If you want to name the column explicitly, you can pass the column name to the name parameter in the to_frame() method. After execution, the to_frame() method will return a dataframe with the specified column name. You can observe this in the following example.
import pandas as pd numbers=[100,90,80,90,70,100,60] indices=[1,2,4,5,6,7,8] series=pd.Series(numbers,index=indices) print("The series is:") print(series) df=series.to_frame(name="Numbers") print("The dataframe is:") print(df)Output:
The series is: 1 100 2 90 4 80 5 90 6 70 7 100 8 60 dtype: int64 The dataframe is: Numbers 1 100 2 90 4 80 5 90 6 70 7 100 8 60In this example, we passed the string "Numbers" to the name parameter in the to_frame() method. Hence, the output dataframe contains the column "Numbers" instead of "0".
Pandas Series to DataFrame Using the DataFrame() FunctionInstead of the to_frame() method, you can also use the DataFrame() function to convert a pandas series to a dataframe. The DataFrame() function takes the series as its input argument and returns the output dataframe as shown below.
import pandas as pd numbers=[100,90,80,90,70,100,60] series=pd.Series(numbers) print("The series is:") print(series) df=pd.DataFrame(series) print("The dataframe is:") print(df)Output:
The series is: 0 100 1 90 2 80 3 90 4 70 5 100 6 60 dtype: int64 The dataframe is: 0 0 100 1 90 2 80 3 90 4 70 5 100 6 60In the above example, you can observe that the column name of the output dataframe is set to "0". To give a custom column name to the output dataframe, you can pass the column name to the columns parameter in the DataFrame() function as shown below.
import pandas as pd numbers=[100,90,80,90,70,100,60] series=pd.Series(numbers) print("The series is:") print(series) df=pd.DataFrame(series,columns=["Numbers"]) print("The dataframe is:") print(df)Output:
The series is: 0 100 1 90 2 80 3 90 4 70 5 100 6 60 dtype: int64 The dataframe is: Numbers 0 100 1 90 2 80 3 90 4 70 5 100 6 60If the input series contains custom indices, the indices are copied into the output dataframe. You can observe this in the following example.
import pandas as pd numbers=[100,90,80,90,70,100,60] indices=[1,2,4,5,6,7,8] series=pd.Series(numbers,index=indices) print("The series is:") print(series) df=pd.DataFrame(series,columns=["Numbers"]) print("The dataframe is:") print(df)Output:
The series is: 1 100 2 90 4 80 5 90 6 70 7 100 8 60 dtype: int64 The dataframe is: Numbers 1 100 2 90 4 80 5 90 6 70 7 100 8 60 Convert the index of the series to a column in dataframeIn the above examples, you might have observed that the indices of the input series are converted to the index of the dataframe. If you want to convert the indices of the input series to a column in the output dataframe, you can use the reset_index() method.
The reset_index() method, when invoked on a series, converts the index of the series into a column and returns the resulting dataframe as shown below.
import pandas as pd numbers=[100,90,80,90,70,100,60] indices=[1,2,4,5,6,7,8] series=pd.Series(numbers,index=indices) print("The series is:") print(series) df=series.reset_index() print("The dataframe is:") print(df)Output:
The series is: 1 100 2 90 4 80 5 90 6 70 7 100 8 60 dtype: int64 The dataframe is: index 0 0 1 100 1 2 90 2 4 80 3 5 90 4 6 70 5 7 100 6 8 60In this example, you can observe that the index of the series is converted to the column “index” in the output dataframe. However, the column containing values is named "0". To set the name of the column containing values, you can pass the name of the output column to the name parameter in the reset_index() method as shown in the following example.
import pandas as pd numbers=[100,90,80,90,70,100,60] indices=[1,2,4,5,6,7,8] series=pd.Series(numbers,index=indices) print("The series is:") print(series) df=series.reset_index(name="Numbers") print("The dataframe is:") print(df)Output:
The series is: 1 100 2 90 4 80 5 90 6 70 7 100 8 60 dtype: int64 The dataframe is: index Numbers 0 1 100 1 2 90 2 4 80 3 5 90 4 6 70 5 7 100 6 8 60 Convert the Index of A Series to Columns in The DataFrame Using the DataFrame() FunctionTo convert the index of a series to a column in the dataframe with desired column names, we will use the following steps.
- First, we will obtain a list of indices using the index attribute of the series.
- Then, we will obtain a list of values using the values attribute of the series.
- After this, we will create a python dictionary using the above lists. Here, we will use the column names of the desired dataframe as keys in the dictionary and the list of indices and values as the associated values.
- After creating the dictionary, we will convert the dictionary into a dataframe using the DataFrame() function. The DataFrame() function takes a list containing the dictionary as its input argument and returns the output dataframe.
After executing the above steps, you will get the desired output dataframe. You can observe this in the following example.
import pandas as pd numbers=[100,90,80,90,70,100,60] indices=["A","B","C","D","E","F","G"] series=pd.Series(numbers,index=indices) print("The series is:") print(series) values=series.values index_values=series.index myDict=dict() for i in range(len(values)): key=index_values[i] value=values[i] myDict[key]=value print("The dictionary is:") print(myDict) df=pd.DataFrame([myDict]) print("The dataframe is:") print(df)Output:
The series is: A 100 B 90 C 80 D 90 E 70 F 100 G 60 dtype: int64 The dictionary is: {'A': 100, 'B': 90, 'C': 80, 'D': 90, 'E': 70, 'F': 100, 'G': 60} The dataframe is: A B C D E F G 0 100 90 80 90 70 100 60 Multiple Pandas Series to DataFrame in PythonInstead of a single series, we can also convert multiple series into a dataframe. In this case, we can convert the series into rows or columns of the output dataframe. Let us discuss both these approaches.
Convert Multiple Series Objects into Rows of DataFrameTo convert multiple series into rows of a pandas dataframe, you can use the DataFrame() function. The DataFrame() function takes a list of series objects as its input argument and returns a dataframe as output.
You can observe this in the following example.
import pandas as pd numbers1=[100,90,80,90,70,100,60] numbers2=[1,2,3,4,5,6,7] series1=pd.Series(numbers1) series2=pd.Series(numbers2) print("The first series is:") print(series1) print("The second series is:") print(series2) df=pd.DataFrame([series1,series2]) print("The dataframe is:") print(df)Output:
The first series is: 0 100 1 90 2 80 3 90 4 70 5 100 6 60 dtype: int64 The second series is: 0 1 1 2 2 3 3 4 4 5 5 6 6 7 dtype: int64 The dataframe is: 0 1 2 3 4 5 6 0 100 90 80 90 70 100 60 1 1 2 3 4 5 6 7 Convert Multiple Series into Columns of DataFrameTo convert multiple series into columns of a pandas dataframe, we will use the concat() function defined in the pandas module. Here, we will pass the list of series to the concat() function as its first input argument. Additionally, we will set the axis parameter to 1. After execution of the concat() function, we will get the output dataframe with all the input series objects as columns of the dataframe.
You can observe this in the following example.
import pandas as pd numbers1=[100,90,80,90,70,100,60] numbers2=[1,2,3,4,5,6,7] series1=pd.Series(numbers1) series2=pd.Series(numbers2) print("The first series is:") print(series1) print("The second series is:") print(series2) df=pd.concat([series1,series2],axis=1) print("The dataframe is:") print(df)Output:
The first series is: 0 100 1 90 2 80 3 90 4 70 5 100 6 60 dtype: int64 The second series is: 0 1 1 2 2 3 3 4 4 5 5 6 6 7 dtype: int64 The dataframe is: 0 1 0 100 1 1 90 2 2 80 3 3 90 4 4 70 5 5 100 6 6 60 7 ConclusionIn this article, we have discussed different ways to convert a pandas series to a dataframe. We also discussed how to convert multiple series objects to dataframe in python. To learn more about pandas dataframes, you can read this article on how to assign column to a dataframe in python. You might also like this article on how to check for null values in pandas.
I hope you enjoyed reading this article. Stay tuned for more informative articles.
Happy Learning!
The post Pandas Series to DataFrame in Python appeared first on PythonForBeginners.com.
Real Python: The Real Python Podcast – Episode #148: Sharing Your Python App Across Platforms With BeeWare
Are you interested in deploying your Python project everywhere? This week on the show, Russell Keith-Magee, founder and maintainer of the BeeWare project, returns. Russell shares recent updates to Briefcase, a tool that converts a Python application into native installers on macOS, Windows, Linux, and mobile devices.
[ 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 ]
Django Weblog: PyCharm &amp; DSF Campaign 2023 Results
We are excited to share the results of the annual Django Developers Survey which was conducted this year in collaboration with JetBrains. Almost 5,000 Django users from 248 countries took the survey which covered a broad list of topics including Django usage, operating systems, libraries, tools, and many other insights.
View the results of the 2022 Django Developers Survey.
If you have feedback on the findings and how to improve the survey in future years please share on the official Django Forum.
Thank you to everyone who participated!
PyCharm: PyCharm 2022.3.3 Is Out!
This build brings better code coverage and support for tox 4.
You can update to this version from inside the IDE, using the Toolbox App, or via snaps if you’re using Ubuntu. You can also download it directly from our website.
In this latest release, we have addressed various bugs and made several improvements to enhance the overall user experience. Here are the most notable fixes and updates:
- PyCharm now bundles Coverage.py v.6.5 for working with Python 3.7 and later, and v.5.5 for working with Python 2.7 and Python 3.6. [PY-41674]
- PyCharm now supports tox 4. [PY-57956]
- It is once again possible to work with Python packages on newer versions of Conda. [PY-58678]
- The Shelf tab doesn’t disappear from the Commit tool window anymore. [IDEA-305906]
- The Check RegExp action no longer results in a false Bad regular expression pattern alert. [IDEA-261269]
- The IDE correctly saves the set Docker WSL distribution option. [IDEA-305901]
Check out the release notes to see all of the resolved issues and share your feedback and report any bugs through our issue tracker. We appreciate your help in improving PyCharm.
PyCharm: PyCharm 2023.1 EAP 5 Is Out!
We are fast approaching the release of PyCharm 2023.1. This new EAP version introduces option to save multiple tool window layouts and improvements to the frontend development experience.
To see what has already been added in PyCharm 2023.1, take a look at our previous EAP blog posts.
The Toolbox App is the easiest way to get the EAP builds and keep both your stable and EAP versions up to date. You can also manually download the EAP builds from our website.
Important! PyCharm EAP builds are not fully tested and might be unstable.
Option to save multiple tool window layoutsIn PyCharm 2023.1, you can save several different tool window layouts and switch between them as needed.
To save a new layout, arrange the tool windows as desired and go to Window | Layouts | Save Current Layout as New. Once you’ve adjusted the arrangement, you can update the current setup using the Save Changes in Current Layout option or save the changes as a new custom layout. You can find the saved layouts in the list under Window | Layouts | Restore Layout – hover over the name of the layout you want to use, and click to apply it.
Option to configure HTML code completionWith PyCharm 2021.3, we changed the way code completion works for HTML. Whenever you type a tag name or an abbreviation in the editor, or invoke code completion, PyCharm shows you relevant suggestions right away. Previously, it would only show them if you typed < first. Some users found this behavior distracting when entering plain text in HTML, so we’ve added an option to disable it. You can find the new option Enable auto-popup of tag name code completion when typing in HTML text in Preferences / Settings | Editor | General | Code Completion.
Notable fixes:- When creating a new SSH interpreter, you can now disable the automatic upload of project files to the SSH server. [PY-55517]
- We fixed a performance problem triggered by recursive TypedDict definitions. [PY-56541]
- For WSL-based projects, especially on Windows 11, the console and debugger now work faster. [PY-58997]
- When a virtual environment is set as a project interpreter, it is activated in the terminal, as it should be. [PY-58930]
These are the most important updates for PyCharm 2023.1 EAP 5. For the full list of improvements, check out the release notes. Share your feedback on the new features in the comments below, on Twitter, or in our issue tracker.
Codementor: Search from a Text file using Language C
Mike Driscoll: Python’s Built-in Functions Video Series
I have started producing a new Python video series on Python’s built-in functions. Did you know there are over 70 built-in functions?
Here’s a listing of all the functions straight from the Python documentation:
Here’s the introduction video to my new series on The Mouse vs Python YouTube channel:
But that’s only the introduction.
You want to see a video that explains one of Python’s 70+ built-in functions, right?
I’ve got you covered:
Be sure to subscribe to The Mouse vs Python YouTube channel to get more great Python content. Thanks for watching!
The post Python’s Built-in Functions Video Series appeared first on Mouse Vs Python.
Python for Beginners: Pandas Assign New Columns to a DataFrame
Pandas dataframes are the data structures that we use to handle tabular data in python. This article discusses different ways to assign new columns to pandas dataframe using the assign() method.
Table of Contents- The Pandas assign() Method
- Pandas Assign a Column to a DataFrame
- Assign a Column Based on Another Column
- Assign Multiple Columns to a DataFrame
- Conclusion
The assign() method is used to assign new columns to a pandas dataframe. It has the following syntax.
df.assign(col_1=series_1, col_2=series2,...)In the above function, the column names and the values for the columns are passed as keyword arguments. Here, column names are the keywords, and list or series objects containing the data for the column are the corresponding values.
When we invoke the assign() method on a dataframe df, it takes column names and series objects as its input argument. After execution, the assign() method adds the specified column to the dataframe df and returns the modified dataframe.
Pandas Assign a Column to a DataFrameTo assign a new column to the pandas dataframe using the assign() method, we will pass the column name as its input argument and the values in the column as the value assigned to the column name parameter.
After execution, the assign() method returns the modified dataframe. You can observe this in the following example.
import pandas as pd myDicts=[{"Roll":1,"Maths":100, "Physics":80, "Chemistry": 90}, {"Roll":2,"Maths":80, "Physics":100, "Chemistry": 90}, {"Roll":3,"Maths":90, "Physics":80, "Chemistry": 70}, {"Roll":4,"Maths":100, "Physics":100, "Chemistry": 90}, {"Roll":5,"Maths":90, "Physics":90, "Chemistry": 80}, {"Roll":6,"Maths":80, "Physics":70, "Chemistry": 70}] df=pd.DataFrame(myDicts) print("The original dataframe is:") print(df) df=df.assign(Name= ["Aditya","Joel", "Sam", "Chris", "Riya", "Anne"]) print("The mofified dataframe is:") print(df)Output:
The original dataframe is: Roll Maths Physics Chemistry 0 1 100 80 90 1 2 80 100 90 2 3 90 80 70 3 4 100 100 90 4 5 90 90 80 5 6 80 70 70 The mofified dataframe is: Roll Maths Physics Chemistry Name 0 1 100 80 90 Aditya 1 2 80 100 90 Joel 2 3 90 80 70 Sam 3 4 100 100 90 Chris 4 5 90 90 80 Riya 5 6 80 70 70 AnneIn the above example, we created a column "Name" in the input dataframe using a list and the assign() method.
Instead of a list, we can also assign a pandas series to the column name parameter to create a new column in the dataframe as shown below.
import pandas as pd myDicts=[{"Roll":1,"Maths":100, "Physics":80, "Chemistry": 90}, {"Roll":2,"Maths":80, "Physics":100, "Chemistry": 90}, {"Roll":3,"Maths":90, "Physics":80, "Chemistry": 70}, {"Roll":4,"Maths":100, "Physics":100, "Chemistry": 90}, {"Roll":5,"Maths":90, "Physics":90, "Chemistry": 80}, {"Roll":6,"Maths":80, "Physics":70, "Chemistry": 70}] df=pd.DataFrame(myDicts) print("The original dataframe is:") print(df) df=df.assign(Name= pd.Series(["Aditya","Joel", "Sam", "Chris", "Riya", "Anne"])) print("The mofified dataframe is:") print(df)Output:
The original dataframe is: Roll Maths Physics Chemistry 0 1 100 80 90 1 2 80 100 90 2 3 90 80 70 3 4 100 100 90 4 5 90 90 80 5 6 80 70 70 The mofified dataframe is: Roll Maths Physics Chemistry Name 0 1 100 80 90 Aditya 1 2 80 100 90 Joel 2 3 90 80 70 Sam 3 4 100 100 90 Chris 4 5 90 90 80 Riya 5 6 80 70 70 AnneIn this example, we passed a series to the assign() method as an input argument instead of a list. However, you can observe that the output dataframe in this example is the same as the previous example.
Assign a Column Based on Another ColumnWe can also create a column based on another column in a pandas dataframe. For this, we will first create a series based on another column. Then, we can use the assign() method and pass the column name with the series as its input to assign the column to the pandas dataframe.
You can observe this in the following example.
import pandas as pd myDicts=[{"Roll":1,"Maths":100, "Physics":80, "Chemistry": 90}, {"Roll":2,"Maths":80, "Physics":100, "Chemistry": 90}, {"Roll":3,"Maths":90, "Physics":80, "Chemistry": 70}, {"Roll":4,"Maths":100, "Physics":100, "Chemistry": 90}, {"Roll":5,"Maths":90, "Physics":90, "Chemistry": 80}, {"Roll":6,"Maths":80, "Physics":70, "Chemistry": 70}] df=pd.DataFrame(myDicts) print("The original dataframe is:") print(df) df=df.assign(GPI= df["Maths"]/10) print("The mofified dataframe is:") print(df)Output:
The original dataframe is: Roll Maths Physics Chemistry 0 1 100 80 90 1 2 80 100 90 2 3 90 80 70 3 4 100 100 90 4 5 90 90 80 5 6 80 70 70 The mofified dataframe is: Roll Maths Physics Chemistry GPI 0 1 100 80 90 10.0 1 2 80 100 90 8.0 2 3 90 80 70 9.0 3 4 100 100 90 10.0 4 5 90 90 80 9.0 5 6 80 70 70 8.0In this example, we have created the GPI column using the "Maths" column. For this, we created a series by dividing the Maths column by 10. Then, we assigned the new series to the GPI keyword as an input argument in the assign() method. You can also use the pandas apply method to create a new series in this case.
Assign Multiple Columns to a DataFrameTo assign multiple columns to the pandas dataframe, you can use the assign() method as shown below.
import pandas as pd myDicts=[{"Roll":1,"Maths":100, "Physics":80, "Chemistry": 90}, {"Roll":2,"Maths":80, "Physics":100, "Chemistry": 90}, {"Roll":3,"Maths":90, "Physics":80, "Chemistry": 70}, {"Roll":4,"Maths":100, "Physics":100, "Chemistry": 90}, {"Roll":5,"Maths":90, "Physics":90, "Chemistry": 80}, {"Roll":6,"Maths":80, "Physics":70, "Chemistry": 70}] df=pd.DataFrame(myDicts) print("The original dataframe is:") print(df) df=df.assign(Name=["Aditya","Joel", "Sam", "Chris", "Riya", "Anne"],GPI= df["Maths"]/10) print("The mofified dataframe is:") print(df)Output:
he original dataframe is: Roll Maths Physics Chemistry 0 1 100 80 90 1 2 80 100 90 2 3 90 80 70 3 4 100 100 90 4 5 90 90 80 5 6 80 70 70 The mofified dataframe is: Roll Maths Physics Chemistry Name GPI 0 1 100 80 90 Aditya 10.0 1 2 80 100 90 Joel 8.0 2 3 90 80 70 Sam 9.0 3 4 100 100 90 Chris 10.0 4 5 90 90 80 Riya 9.0 5 6 80 70 70 Anne 8.0In this example, we assigned two columns to the pandas dataframe using the assign() method. For this, we passed both the column names and their values as keyword arguments to the assign() method.
ConclusionIn this article, we have discussed different ways to assign columns to a pandas dataframe. To learn more about python programming, you can read this article on how to use the insert() method to insert a column into a dataframe. You might also like this article on how to convert epoch to datetime in python.
I hope you enjoyed reading this article. Stay tuned for more informative articles.
Happy Learning!
The post Pandas Assign New Columns to a DataFrame appeared first on PythonForBeginners.com.
Real Python: Python's Mutable vs Immutable Types: What's the Difference?
As a Python developer, you’ll have to deal with mutable and immutable objects sooner or later. Mutable objects are those that allow you to change their value or data in place without affecting the object’s identity. In contrast, immutable objects don’t allow this kind of operation. You’ll just have the option of creating new objects of the same type with different values.
In Python, mutability is a characteristic that may profoundly influence your decision when choosing which data type to use in solving a given programming problem. Therefore, you need to know how mutable and immutable objects work in Python.
In this tutorial, you’ll:
- Understand how mutability and immutability work under the hood in Python
- Explore immutable and mutable built-in data types in Python
- Identify and avoid some common mutability-related gotchas
- Understand and control how mutability affects your custom classes
To dive smoothly into this fundamental Python topic, you should be familiar with how variables work in Python. You should also know the basics of Python’s built-in data types, such as numbers, strings, tuples, lists, dictionaries, sets, and others. Finally, knowing how object-oriented programming works in Python is also a good starting point.
Free Sample Code: Click here to download the free sample code that you’ll use to explore mutable vs immutable data types in Python.
Mutability vs ImmutabilityIn programming, you have an immutable object if you can’t change the object’s state after you’ve created it. In contrast, a mutable object allows you to modify its internal state after creation. In short, whether you’re able to change an object’s state or contained data is what defines if that object is mutable or immutable.
Immutable objects are common in functional programming, while mutable objects are widely used in object-oriented programming. Because Python is a multiparadigm programming language, it provides mutable and immutable objects for you to choose from when solving a problem.
To understand how mutable and immutable objects work in Python, you first need to understand a few related concepts. To kick things off, you’ll take a look at variables and objects.
Variables and ObjectsIn Python, variables don’t have an associated type or size, as they’re labels attached to objects in memory. They point to the memory position where concrete objects live. In other words, a Python variable is a name that refers to or holds a reference to a concrete object. In contrast, Python objects are concrete pieces of information that live in specific memory positions on your computer.
The main takeaway here is that variables and objects are two different animals in Python:
- Variables hold references to objects.
- Objects live in concrete memory positions.
Both concepts are independent of each other. However, they’re closely related. Once you’ve created a variable with an assignment statement, then you can access the referenced object throughout your code by using the variable name. If the referenced object is mutable, then you can also perform mutations on it through the variable. Mutability or immutability is intrinsic to objects rather than to variables.
However, if the referenced object is immutable, then you won’t be able to change its internal state or contained data. You’ll just be able to make your variable reference a different object that, in Python, may or may not be of the same type as your original object.
If you don’t have a reference (variable) to an object, then you can’t access that object in your code. If you lose or remove all the references to a given object, then Python will garbage-collect that object, freeing the memory for later use.
Now that you know that there are differences between variables and objects, you need to learn that all Python objects have three core properties: identity, type, and value.
Objects, Value, Identity, and TypeIn Python, everything is an object. For example, numbers, strings, functions, classes, and modules are all objects. Every Python object has three core characteristics that define it at a foundational level. These characteristics are:
- Value
- Identity
- Type
Arguably, the value is probably the most familiar object characteristic that you’ve dealt with. An object’s value consists of the concrete piece or pieces of data contained in the object itself. A classic example is a numeric value like an integer or floating-point number:
>>>>>> 42 42 >>> isinstance(42, int) True >>> 3.14 3.14 >>> isinstance(3.14, float) TrueThese numeric values, 42 and 3.14, are both objects. The first number is an instance of the built-in int class, while the second is an instance of float. In both examples, you confirm the object’s type using the built-in isinstance() function.
Note: In this tutorial, you’ll use the term value to refer to an object’s data. Your objects will have custom and meaningful values only if you add those values yourself. Sometimes, you’ll create or find objects that don’t have meaningful values. However, all Python objects contain built-in attributes that also hold data. So, in a way, each Python object has an implicit value.
For example, all Python objects will have special methods and attributes that the language adds automatically under the hood:
>>>>>> obj = object() >>> dir(obj) [ '__class__', '__delattr__', ... '__str__', '__subclasshook__' ]In this example, you created a generic object using the built-in object class. You haven’t added any attributes to the object. So, from your programming perspective, this object doesn’t have a meaningful value. So, you may think it only has identity and type, which you’ll explore in just a moment.
However, because all objects have built-in attributes, you can mutate them by adding new attributes and data dynamically, as you’ll learn in the Mutability in Custom Classes section of this tutorial.
Read the full article at https://realpython.com/python-mutable-vs-immutable-types/ »[ 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 ]
Python Bytes: #326 Let's Go for a PyGWalk
Test and Code: 195: What would you change about pytest?
Anthony Sottile and Brian discuss changes that would be cool for pytest, even unrealistic changes. These are changes we'd make to pytest if we didn't ahve to care about backwards compatibilty.
Anthony's list:
- The import system
- Multi-process support out of the box
- Async support
- Changes to the fixture system
- Extend the assert rewriting to make it modular
- Add matchers to assert mechanism
- Ban test class inheritance
Brian's list:
- Extend assert rewriting for custom rewriting, like check
- pytester matchers available for all tests
- Throw out nose and unittest compatibility plugins
- Throw out setup_module, teardown_module and other xunit style functions
- Remove a bunch of the hook functions
- Documentation improvement of remaining hook functions which include examples of how to use it
- Start running tests before collection is done
- Split collection and running into two processes
- Have the fixtures be able to know the result of the test during teardown
Special Guest: Anthony Sottile.
Links:
<p>Anthony Sottile and Brian discuss changes that would be cool for pytest, even unrealistic changes. These are changes we'd make to pytest if we didn't ahve to care about backwards compatibilty.</p> <p>Anthony's list:</p> <ol> <li>The import system</li> <li>Multi-process support out of the box</li> <li>Async support</li> <li>Changes to the fixture system</li> <li>Extend the assert rewriting to make it modular</li> <li>Add matchers to assert mechanism</li> <li>Ban test class inheritance</li> </ol> <p>Brian's list: </p> <ol> <li>Extend assert rewriting for custom rewriting, like check</li> <li>pytester matchers available for all tests</li> <li>Throw out nose and unittest compatibility plugins</li> <li>Throw out setup_module, teardown_module and other xunit style functions</li> <li>Remove a bunch of the hook functions</li> <li>Documentation improvement of remaining hook functions which include examples of how to use it</li> <li>Start running tests before collection is done</li> <li>Split collection and running into two processes</li> <li>Have the fixtures be able to know the result of the test during teardown</li> </ol><p>Special Guest: Anthony Sottile.</p><p>Links:</p><ul><li><a href="https://www.youtube.com/@anthonywritescode" title="anthonywritescode - YouTube" rel="nofollow">anthonywritescode - YouTube</a></li><li><a href="https://www.twitch.tv/anthonywritescode" title="anthonywritescode - Twitch" rel="nofollow">anthonywritescode - Twitch</a></li><li><a href="https://pypi.org/project/pytest-asyncio/" title="pytest-asyncio · PyPI" rel="nofollow">pytest-asyncio · PyPI</a></li><li><a href="https://tonybaloney.github.io/posts/async-test-patterns-for-pytest-and-unittest.html" title="async test patterns for pytest" rel="nofollow">async test patterns for pytest</a></li><li><a href="https://pypi.org/project/future-fstrings/" title="future-fstrings · PyPI" rel="nofollow">future-fstrings · PyPI</a></li><li><a href="https://pypi.org/project/re-assert/" title="re-assert · PyPI" rel="nofollow">re-assert · PyPI</a></li><li><a href="https://numpy.org/doc/stable/reference/routines.testing.html" title="numpy.testing" rel="nofollow">numpy.testing</a></li><li><a href="https://sourcegraph.com/search" title="Sourcegraph" rel="nofollow">Sourcegraph</a></li></ul>Python Insider: Python 3.12.0 alpha 6 released
I'm pleased to announce the release of Python 3.12 alpha 6.
https://www.python.org/downloads/release/python-3120a6/
This is an early developer preview of Python 3.12.Major new features of the 3.12 series, compared to 3.11
Python 3.12 is still in development. This release, 3.12.0a6 is the sixth of seven planned alpha releases.
Alpha releases are intended to make it easier to test the current state of new features and bug fixes and to test the release process.
During the alpha phase, features may be added up until the start of the beta phase (2023-05-08) and, if necessary, may be modified or deleted up until the release candidate phase (2023-07-31). Please keep in mind that this is a preview release and its use is not recommended for production environments.
Many new features for Python 3.12 are still being planned and written. Among the new major new features and changes so far:
- Even more improved error messages. More exceptions potentially caused by typos now make suggestions to the user.
- Support for the Linux perf profiler to report Python function names in traces.
- The deprecated wstr and wstr_length members of the C implementation of unicode objects were removed, per PEP 623.
- In the unittest module, a number of long deprecated methods and classes were removed. (They had been deprecated since Python 3.1 or 3.2).
- The deprecated smtpd and distutils modules have been removed (see PEP 594 and PEP 632). The setuptools package (installed by default in virtualenvs and many other places) continues to provide the distutils module.
- A number of other old, broken and deprecated functions, classes and methods have been removed.
- Invalid backslash escape sequences in strings now warn with SyntaxWarning instead of DeprecationWarning, making them more visible. (They will become syntax errors in the future.)
- The internal representation of integers has changed in preparation for performance enhancements. (This should not affect most users as it is an internal detail, but it may cause problems for Cython-generated code.)
- (Hey, fellow core developer, if a feature you find important is missing from this list, let Thomas know.)
For more details on the changes in Python 3.12, see What's New In Python 3.12. The next pre-release of Python 3.12 will be 3.12.0a7, currently scheduled for 2023-04-03.
More resources
- Online Documentation
- PEP 693, the 3.12 Release Schedule
- Report bugs at https://github.com/python/cpython/issues.
- Help fund Python and its community.
And now for something completely different
Let me not to the marriage of true mindsAdmit impediments. Love is not loveWhich alters when it alteration finds,Or bends with the remover to remove:O, no! it is an ever-fixed mark,That looks on tempests and is never shaken;It is the star to every wandering bark,Whose worth’s unknown, although his height be taken.Love’s not Time’s fool, though rosy lips and cheeksWithin his bending sickle’s compass come;Love alters not with his brief hours and weeks,But bears it out even to the edge of doom.
If this be error, and upon me prov’d,I never writ, nor no man ever lov’d.
Sonnet 116, by William Shakespeare.Enjoy the new releases
Thanks to all of the many volunteers who help make Python Development and these releases possible! Please consider supporting our efforts by volunteering yourself or through organization contributions to the Python Software Foundation.
https://www.python.org/psf/
Your release team,Thomas WoutersNed DeilySteve Dower