一段python程序引发的问题


一段python程序引发的问题

先放代码。

import time
import random

player_list =  ['【狂血战士】','【森林箭手】','【光明骑士】','【独行剑客】','【格斗大师】','【枪弹专家】']
enemy_list = ['【暗黑战士】','【黑暗弩手】','【骷髅骑士】','【嗜血刀客】','【首席刺客】','【陷阱之王】']
players = random.sample(player_list,3)
enemies = random.sample(enemy_list,3)
player_info = {}
enemy_info = {}
def born_role():
        life = random.randint(100,180)
        attack = random.randint(30,50)
        return life,attack # 返回一个元组
def show_role():
    for i in range(3):
        player_info[players[i]] = born_role()
        enemy_info[enemies[i]] = born_role()

    print('----------------- 角色信息 -----------------')
    print('你的人物:')
    for i in range(3):
        print('{}  \n 血量:{}  \n 攻击:{}\n'.format(players[i],player_info[players[i]][0],player_info[players[i]][1]))
        # time.sleep(2)

    print('----------------- 角色信息 -----------------')
    print('电脑敌方:')
    for i in range(3):
        print('{} \n 血量:{}  \n 攻击:{}\n'.format(enemies[i],enemy_info[enemies[i]][0],enemy_info[enemies[i]][1]))
        # time.sleep(2)
def order_role():
    global players
    order_dict = {} # 新建字典,存储顺序
    for i in range(3):
        order = int(input('你想将 {} 放在第几个上场?(输入数字1~3)'.format(players[i])))
        order_dict[order] = players[i]
    players = []
    for i in range(1,4):
        players.append(order_dict[i]) 
    print('\n我方角色的出场顺序是:%s、%s、%s' %(players[0],players[1],players[2]))
    print('敌方角色的出场顺序是:%s、%s、%s' %(enemies[0],enemies[1],enemies[2]))

def main():
    show_role()
    order_role()
main()

运行结果如下:

ty2

主要分析这段代码:

def order_role():
    global players
    order_dict = {} # 新建字典,存储顺序
    for i in range(3):
        order = int(input('你想将 {} 放在第几个上场?(输入数字1~3)'.format(players[i])))
        order_dict[order] = players[i]
    players = []
    for i in range(1,4):
        players.append(order_dict[i]) 
    print('\n我方角色的出场顺序是:%s、%s、%s' %(players[0],players[1],players[2]))
    print('敌方角色的出场顺序是:%s、%s、%s' %(enemies[0],enemies[1],enemies[2]))

问题一:global的位置

1.问题阐述

global players放到第一个循环后为什么会报错:SyntaxError: name ‘players’ is used prior to global declaration

def order_role():
    order_dict = {} # 新建字典,存储顺序
    for i in range(3):
        order = int(input('你想将 {} 放在第几个上场?(输入数字1~3)'.format(players[i])))
        order_dict[order] = players[i]
    global players  #将global players放到第一个循环后
    players = []
    for i in range(1,4):
        players.append(order_dict[i]) 
    print('\n我方角色的出场顺序是:%s、%s、%s' %(players[0],players[1],players[2]))
    print('敌方角色的出场顺序是:%s、%s、%s' %(enemies[0],enemies[1],enemies[2]))

2.知识点复查

  • 在Python中,当引用一个变量的时候,对这个变量的搜索是按找本地作用域(Local)、嵌套作用域(Enclosing function locals)、全局作用域(Global)、内置作用域(builtins模块)的顺序来进行的,即所谓的LEGB规则。即python引用变量的顺序: 当前作用域局部变量->外层作用域变量->当前模块中的全局变量->python内置变量。最终在全局变量中找这个变量,如果找不到则抛出 UnboundLocalError 异常。

  • 然而当在一个函数内部为一个变量赋值时,并不是按照上面所说LEGB规则来首先找到变量,之后为该变量赋值。在Python中,在函数中为一个变量赋值时,有下面这样一条规则:“当在函数中给一个变量名赋值是(而不是在一个表达式中对其进行引用),Python总是创建或改变本地作用域的变量名,除非它已经在那个函数中被声明为全局变量.

  • Python 是弱类型语言,弱类型语言有两个典型特征。 1.变量无须声明即可直接赋值: 对一个不存在的变量赋值就相当于定义了一个新变量。 2.变量的数据类型可以动态改变:同一个变量可以一会儿被赋值为整数值, 一会儿被赋值为 字符串。Python 使用等号(=)作为赋值运算符,例如 a = 20 就是一条赋值语句,这条语句用于将 20 装入变量 a 中一一这个过程就被称为赋值:将 20 赋值给变量 a。

  • 在函数体中多条可执行语句之间有严格的执行顺序,排在函数体前面的语句总是先执行,排在 函数体后面的语句总是后执行。

  • global关键字用来在函数或其他局部作用域中使用全局变量。

    • 如果不修改全局变量,只是引用全局变量,也可以不使用global关键字。

    • 如果在局部要对全局变量修改,需要在局部也要先声明该全局变量。

下面探讨global修饰符的用法:

示例1:

第一种情况:
  • 在模块层面定义的变量(无需global修饰),如果在函数中没有再定义同名变量,可以在函数中当做全局变量使用:
  a=1
  def f():
      print(a)
  f()
  print(a) 
  #输出1和1
第二种情况:
  • 但如果在函数中有再赋值/定义(因为python是弱类型语言,赋值语句和其定义变量的语句一样),则会产生引用了未定义变量的错误:
  a=1
  def f():
      print(a)
      a=2
  f()
  print(a) 
  #输出错误:UnboundLocalError: local variable 'a' referenced before assignment
  • 抛出的错误信息为:UnboundLocalError: local variable ‘a’ referenced before assignment。如果内部函数有引用外部函数的同名变量或者全局变量,并且对这个变量有修改.那么python会认为它是一个局部变量。
第三种情况:
  • 而如果在函数中的定义在引用前使用,那么会正常运行但函数中的变量和模块中定义的全局变量不为同一个
  a=1
  def f():
      a=2
      print(a)
  f()
  print(a) 
  #输出2和1
  • 上述输出是2和1,也即f函数中print使用的是局部变量a,而最后一个print语句使用的全局a。
第四种情况:
  • 那么我们会有疑问,如果我可能在函数使用某一变量后又对其进行修改(也即再赋值),怎么让函数里面使用的变量是模块层定义的那个全局变量而不是函数内部的局部变量呢?这时候global修饰符就派上用场了。
  a=1
  def f():
      global a
      print(a)
      a=3
  f()
  print(a) 
  #输出1和3
  • 在用global修饰符声明a是全局变量的a后(注意,global语句不允许同时进行赋值如global a=3是不允许的),输出是1和3,得到了我们想要的效果。此时使用global关键字对全局变量进行了重新赋值。
第五种情况:
a=1
def f():
    print(a)
    global a
    a=3
f()
print(a) 
#输出异常 SyntaxError: name 'a' is used prior to global declaration
  • 变量a在全局声明之前就被使用。

示例2:

a = 0   #定义了一个全局变量,(可以省略global关键字)
def test():
    print (a)    #不修改,只是引用全局变量,不使用global关键字
def count():
    global a    
    a +=1         # a +=1 即a=a+1 属于先调用后赋值
    print (a)     #修改全局变量,需要使用global关键字
test()
count()
  • 输出为0 1
a = 0   
def test():
    print (a)   
def count():
    # global a    #如果注释掉global
    a +=1  
    print (a)    
test()
count()

输出为0 UnboundLocalError: local variable ‘a’ referenced before assignment,错误代码为 a +=1

在这个例子中设置的 a=0属于全局变量,而在函数内部中没有对 a 的定义。

根据 Python 访问局部变量和全局变量的规则:当搜索一个变量的时候,Python 先从局部作用域开始搜索,如果在局部作用域没有找到那个变量,那样 Python 就会在作用域范围逐层寻找。最终在全局变量中找这个变量,如果找不到则抛出 UnboundLocalError 异常。

但是,明明已经在全局变量中找到同名变量了,怎么还是报错?

因为内部函数有引用外部函数的同名变量或者全局变量,并且对这个变量有修改的时候,此时 Python 会认为它是一个局部变量,而函数中并没有 a 的定义和赋值,所以报错。

知识点归纳:

变量在函数中使用情况输出结果及处理方式对应示例
只调用不赋值函数内外均输出全局变量示例1第一种情况
只赋值不调用一般不会这样做
先赋值后调用函数内输出局部变量,外输出全局变量示例1第三种情况
先调用后赋值调用前加global,不加报错示例1第二、四种情况;示例2

3.问题解决

def order_role():
    global players  #global players放在第一处
    order_dict = {} # 新建字典,存储顺序
    for i in range(3):
        order = int(input('你想将 {} 放在第几个上场?(输入数字1~3)'.format(players[i])))
        order_dict[order] = players[i]
    #global players放在第二处    
    players = []
    for i in range(1,4):
        players.append(order_dict[i]) 
    print('\n我方角色的出场顺序是:%s、%s、%s' %(players[0],players[1],players[2]))
    print('敌方角色的出场顺序是:%s、%s、%s' %(enemies[0],enemies[1],enemies[2]))
  • 与示例1的第四种情况类似。属于先调用后赋值,即在函数使用某一变量后又对其进行修改(也即再赋值)。第五行的players[i]调用的变量players,第七行给变量players再赋值。

  • global players放在第一处,正确。

  • global players放在第二处,这种情况下,程序会报 SyntaxError ,理由是 “name ‘players’ is used prior to global declaration”,即变量在全局声明之前就被使用。(同示例1的第五种情况)

  • 如果一二两处均无global players。报错UnboundLocalError: local variable ‘players’ referenced before assignment。即局部变量在分配空间前被引用。(同示例1的第二种情况)这种就相当于在变量定义前就调用,当然会出错。

    综上:在函数内的变量(已在函数外有全局变量):

    • 如果函数内没有对其进行再赋值,则采用全局变量;
    • 如果函数内有对其进行再赋值,则采用局部变量。且再赋值一定要位于调用之前。否则会报错:UnboundLocalError: local variable ‘a’ referenced before assignment
    • 如果函数内有全局变量的声明(global),则在global之后、再赋值之前采用全局变量。global一定要位于调用之前,否则会报错:name ‘players’ is used prior to global declaration。

4.解决方案

这类问题基本上两个解决方案:

  • 一是添加或者改变global的位置
  • 二是改变变量名称。使局部变量与全局变量不同。
def order_role():
    global players
    order_dict = {} # 新建字典,存储顺序
    for i in range(3):
        order = int(input('你想将 {} 放在第几个上场?(输入数字1~3)'.format(players[i])))
        order_dict[order] = players[i]
    player = []  #改局部变量players为player
    for i in range(1,4):
        player.append(order_dict[i]) 
    print('\n我方角色的出场顺序是:%s、%s、%s' %(players[0],players[1],players[2]))
    print('敌方角色的出场顺序是:%s、%s、%s' %(enemies[0],enemies[1],enemies[2]))
    #可以正确运行

5.遗留问题

还有没有解释的是两处:

  • 两种报错涉及的深层执行原理(数据结构和微机原理的层次)

  • 下面这种改法里合并了局部变量和全局变量,列表内的元素发生了改变,却仍然可以不使用global,相当于示例1的第二种情况。这个问题还需要再从列表在内存里的存储等方面来思考下。

def order_role():
    #global players
    order_dict = {} # 新建字典,存储顺序
    for i in range(3):
        order = int(input('你想将 {} 放在第几个上场?(输入数字1~3)'.format(players[i])))
        order_dict[order] = players[i]
    # global players
    # players = []
    for i in range(1,4):
        # players.append(order_dict[i]) 
        players[i-1] = order_dict[i] 
    print('\n我方角色的出场顺序是:%s、%s、%s' %(players[0],players[1],players[2]))
    print('敌方角色的出场顺序是:%s、%s、%s' %(enemies[0],enemies[1],enemies[2]))
    #可以正确运行

问题二:input

  • 在 Python3.x 中 raw_input() 和 input() 进行了整合,去除了 raw_input( ),仅保留了input( )函数,其接收任意任性输入,将所有输入默认为字符串处理,并返回字符串类型。

input()以字符串的方式获取用户输入:

>>> x = input()
4.5
>>> type(x)
<class 'str'>

>>>a = input("input:")
input:123                  # 输入整数
>>> type(a)
<class 'str'>              # 字符串

>>> y = input()
Do you love python?
>>> type(y)
<class 'str'>

>>> a = input("input:")    
input:runoob              # 正确,字符串表达式
>>> type(a)
<class 'str'>             # 字符串

输入的字符串可以通过运算符进行连接、复制等操作:

>>> x = input()
abc
>>> x * 3
'abcabcabc'
>>> y = input()
123
>>> x + y
'abc123'

但无法直接参与算术运算,需要先转换类型。比如使用int转换。如:

>>> x = input()
5
>>> x + 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be str, not int
>>> x * 5
'55555'
>>> y = input()
6
>>> x * y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'

问题三:range

概要:

  • range左闭右开,range(5)等价于range(0, 5),range(0, 5)即[0, 1, 2, 3, 4]。

  • python的range() 函数可创建一个整数列表,一般用在 for 循环中。

函数语法

range(start, stop[, step])

参数说明:

  • start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5);
  • stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
  • step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)

实例

>>>range(10)        # 从 0 开始到 10
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(1, 11)     # 从 1 开始到 11
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> range(0, 30, 5)  # 步长为 5
[0, 5, 10, 15, 20, 25]
>>> range(0, 10, 3)  # 步长为 3
[0, 3, 6, 9]
>>> range(0, -10, -1) # 负数
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
>>> range(0)
[]
>>> range(1, 0)
[]

#以下是 range 在 for 中的使用,循环出runoob 的每个字母:
>>>x = 'runoob'
>>> for i in range(len(x)) :
...     print(x[i])
... 
r
u
n
o
o
b
>>>

文章作者: 未名
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 未名 !
  目录