Python, variable importing between files

别说谁变了你拦得住时间么 提交于 2020-08-17 11:55:28

问题


Preamble: I feel I probably have wasted so much time for a simple situation...

Now, I am making a game with pygame, and at some point, I wanted to split files into two, namely main.py and configurations.py in order to make it more readable.

Everything was going good, until I ran into this problem.

I will share whole code at the bottom, but I want to summarize first:

Now first of all, in main.py, I am importing by,

from configurations import *

now, the game loop on the main.py depends on the variable running by

while running:
    .......
    .......
    .......

And the variable running is initialized in configurations.py by,

# initialize some variables
running = True

So, the main.py has to be getting variable running because it does not give any error and uses it in while running statement.

In the main loop, there is a section where I check for events as follows,

for event in pygame.event.get():
        # check for closing window
        if event.type == pygame.QUIT:
            running = False

This part works just as expected, it alters variable running and program gets out of the while loop.

Now, here comes the problematic part.

In one of the classes(Player class) there is a method as decrease_HP,

def decrease_HP(self):
        self.HP -= 1
        print("-1 HP", "Current HP:", self.HP)
        if self.HP <= 0:
            running = False

However, the point I could not figure out is that, it is not changing running variable properly, and game never stops(gets out of while loop). Here is one example output which shows the problem.

pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
-1 HP Current HP: 2
-1 HP Current HP: 1
-1 HP Current HP: 0
-1 HP Current HP: -1
-1 HP Current HP: -2
-1 HP Current HP: -3
-1 HP Current HP: -4
-1 HP Current HP: -5
-1 HP Current HP: -6

So, I hope I could make it clear. I probably have a misunderstanding about importing variables or variable scopes.

By the way, I have tried adding global running above the running = False statement in Player.decrease_HP function.

Thanks in advance.


Exact codes in the files

main.py

# Pygame template - skeleton for a new pygame project
from configurations import *

# initiate some variables
max_bullet = 10


# initialize pygame and create window

pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("CORONA RACE")
clock = pygame.time.Clock()

player = Player()
all_sprites.add(player)


# initialize some variables
running = True

# have to use this because, otherwise, for the first SPACE key pressing, the newest_bullet is not defined yet.
newest_bullet = Bullet(0, 0)

# Game loop

while running:
    # keep loop running at the right speed
    clock.tick(FPS)
    # Process input (events)
    for event in pygame.event.get():
        # check for closing window
        if event.type == pygame.QUIT:
            running = False
        else:
            pass

    while len(mobs) != 5:
        m = Mob()
        all_sprites.add(m)
        mobs.add(m)

    keystate = pygame.key.get_pressed()
    player.speedx = 0
    if keystate[pygame.K_RIGHT]:
        player.speedx += player.SPEED
    if keystate[pygame.K_LEFT]:
        player.speedx -= player.SPEED
    if keystate[pygame.K_SPACE] and player.rect.top - newest_bullet.rect.bottom > BULLET_H + MARGIN and not len(bullets) >= max_bullet:
        newest_bullet = player.shoot()
    # BULLET_H refers to height of the bullet and margin refers to the minimum allowable margin between two consequent b
    # If there are more than 10 bullets at a time on the screen, then no more new bullets can be fired.
    if keystate[pygame.K_ESCAPE]:
        running = False
    if random.randint(0, 14530) > 14470:
        power_up = PowerUp()
        all_sprites.add(power_up)
        powerups.add(power_up)

    hits = pygame.sprite.spritecollide(player, powerups, True)
    for pu in hits:
        power_up_funcs[pu.type](player)

    hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
    for m in hits:
        pass

    hits = pygame.sprite.spritecollide(player, mobs, True)
    if hits:
        player.decrease_HP()

    # print(player.HP)

    # Update

    all_sprites.update()

    # Draw / render
    screen.fill(WHITE)
    all_sprites.draw(screen)
    # *after* drawing everything, flip the display
    pygame.display.flip()

pygame.quit()
raise SystemExit  # to exit python

configurations.py

import pygame
import random

# define constants
WIDTH = 600
HEIGHT = 960
FPS = 30
BULLET_H = 24
BULLET_W = 8
POWERUP_H = 30
POWERUP_W = 30
MOB_W = 50
MOB_H = 80
MARGIN = 10

# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
MAGENTA = (255, 0, 255)
CYAN = (0, 255, 255)

# create sprite groups
all_sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
powerups = pygame.sprite.Group()
mobs = pygame.sprite.Group()


# initialize some variables
running = True


# player sprite


class Player(pygame.sprite.Sprite):

    SPEED = 15

    def __init__(self):
        super().__init__()
        # pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((100, 150))
        self.image.fill(CYAN)
        pygame.draw.circle(self.image, RED, (50, 75), 15, 5)
        self.rect = self.image.get_rect()
        self.rect.centerx = WIDTH / 2
        self.rect.bottom = HEIGHT - 5
        self.speedx = 0
        self.HP = 3

    def update(self):
        self.rect.x += self.speedx
        if self.rect.right > WIDTH:
            self.rect.right = WIDTH
        if self.rect.left < 0:
            self.rect.left = 0

    def shoot(self):
        bullet = Bullet(self.rect.centerx, self.rect.top)
        all_sprites.add(bullet)
        bullets.add(bullet)
        return bullet  # I need this to set the margin in continious fire.

    def change_color(self):
        pass

    def increase_HP(self):
        if self.HP <= 2:
            self.HP += 1
            print("+1 HP", "Current HP:", self.HP)
        else:
            print("HP IS ALREADY FULL", "Current HP:", self.HP)

    def decrease_HP(self):
        self.HP -= 1
        print("-1 HP", "Current HP:", self.HP)
        if self.HP <= 0:
            running = False


class Mob(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((MOB_W, MOB_H))
        self.image.fill(MAGENTA)
        self.rect = self.image.get_rect()
        self.rect.left = random.randint(0, WIDTH - POWERUP_W)
        self.rect.bottom = random.randint(-2 * MOB_H, 0)

    def update(self):
        self.rect.y += 6
        if self.rect.top > HEIGHT:
            self.kill()

# Bullet sprite


class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((BULLET_W, BULLET_H))
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.rect.centerx = x
        self.rect.bottom = y
        self.speedx = 0
        self.speedy = -20

    def update(self):
        self.rect.y += self.speedy
        # kill it if it moves away from the screen
        if self.rect.bottom < 0:
            self.kill()  # built in method of pygame.sprite


# powerup sprite

power_up_funcs = [Player.increase_HP, print]  # container for to-do functs.


class PowerUp(pygame.sprite.Sprite):

    SPEEDY = 8

    def __init__(self):
        super().__init__()
        self.type = random.randint(0, 1)  # [0,1] integer
        if self.type == 0:  # HP power up
            self.image = pygame.Surface((POWERUP_W, POWERUP_H))
            self.image.fill(GREEN)
            self.rect = self.image.get_rect()
            self.rect.left = random.randint(0, WIDTH - POWERUP_W)
            # self.rect.centerx = player.rect.centerx #debug
            self.rect.bottom = 0
        elif self.type == 1:  # shield
            self.image = pygame.Surface((POWERUP_W, POWERUP_H))
            self.image.fill(BLUE)
            self.rect = self.image.get_rect()
            self.rect.left = random.randint(0, WIDTH - POWERUP_W)
            # self.rect.centerx = player.rect.centerx # debug
            self.rect.bottom = 0
        else:
            pass

    def update(self):
        self.rect.y += self.SPEEDY
        if self.rect.top > HEIGHT:
            self.kill()

回答1:


TLDR: Use import configuration and fully qualified names, e.g. configuration.running.

If a function inside configuration needs to modify a top-level value, it must use global.

def decrease_HP(self):
    global running
    self.HP -= 1
    print("-1 HP", "Current HP:", self.HP)
    if self.HP <= 0:
        running = False

Using from configurations import running (or equivalent via ... import *) in main binds the value of configurations.running to a new name main.running. While these names initially share the same value, re-assigning either breaks this equivalency. This is exactly the same as rebinding other names.

>>> a = 1
>>> b = a  # a and b point to same value
>>> a == b
True
>>> b = 2  # rebind only b
>>> a == b
False

To make changes visible across the application, one should use an object an modify its value. A common example are containers, such as lists.

>>> a = [1]
>>> b = a  # a and b point to same value
>>> a == b
True
>>> b[0] = 2  # modify content of value of b
>>> a == b
True
>>> a[0] == b[0]  # content is the same
True

Since modules are objects, it is possible to use them directly to store state.

>>> import configuration
>>> b = configuration  # configuration and b point to same value
>>> configuration == b
True
>>> b.running = False  # modify content of value of b
>>> configuration == b
True
>>> configuration.running == b.running  # content is the same
True

Functions have local scope. Any assignment to a name inside a function implicitly declares the target as local to the function.

>>> running = True
>>> def stop():
...    running = False
...
>>> stop()  # changes only running inside the function
>>> running
True

This can be made visible by accessing a local name before it has a value.

>>> running = True
>>> def stop():
...    print(running)
...    running = False
...
>>> stop()
UnboundLocalError: local variable 'running' referenced before assignment

By declaring the name global, no local name is created. The global value can be directly read and written.

>>> running = True
>>> def stop():
...    global running
...    print(running)
...    running = False
...
>>> stop()  # will print the global value before the change
True
>>> running  # global value was changed
False


来源:https://stackoverflow.com/questions/60838018/python-variable-importing-between-files

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!