我正在阅读“使用Python和Pygame制作游戏”,我注意到因为作者使用大量函数来打破他的代码,他使用了许多全局变量,例如GRIDSIZE
或BACKGROUNDCOLOR
。我总是被告知全局变量一般很糟糕,但没有它们,每个绘图函数都会有十个,重复参数 - 我也被告知重复是坏的。我想知道,作者是否正确使用全局变量来显示大多数(绘图)函数中出现的参数,或者应该使用更多重复参数。
答案 0 :(得分:2)
全局通常很糟糕,因为很难跟踪庞大程序中的值。这在Python中有点不那么糟糕,因为“全局”变量是模块级的,但即使在这种情况下,建议你避免它们:你的模块会变得庞大(使全局变量难以跟踪),模块级变量是容易被其他模块等更新。
我并不是说你不应该使用它们 - 你应该在需要时使用 。需要的两种常见情况是共享状态和常量。
例如,模块级变量是存储通过所有模块共享的状态的最佳方式,甚至是通过所有程序。让我们假设我们要实现游戏的渲染器;它应该存储所有程序共享的所有图像的地图。所以在我们的模块中我们可以这样做:
# map_renderer.py
_map = [
[None, None, None, None],
[None, None, None, None],
[None, None, None, None],
[None, None, None, None]
]
def put_sprint(sprint, x, y):
_map[x][y] = sprint
def get_sprint(x, y):
return _map[x][y]
现在你甚至可以写一个这样的模块:
# background.py
import map_renderer as mr
def draw_background(background_images):
for i, row in enumerate(background_images):
for j, image in enumerate(row):
put_sprint(image, i, j)
......在另一个模块中做这样的事情
# npc.py
import map_renderer as mr
def draw_npc(npc, x, y):
image = npc.image
put_sprint(image, x, y)
由于两个模块都应该更新相同的映射,因此可以使用模块级变量来完成它。
注意:对于那些面向设计模式的人来说,我们可以说模块级变量是实现单例的一种非常有效且简单的方法。
然而,本书中的代码是使用模块级变量的另一种好方法的示例:常量。 (我认为这些全局变量实际上是常量,因为它们遵循PEP 8常量的样式。)
例如,考虑BACKGROUNDCOLOR
变量。我想这是一个通过所有模块可能相同的值,也许是通过所有程序。假设此值为0xFA2BEE
。然后你有一些选择:
你可以(理论上)在任何地方写出值:
def paint_frame(image, x, y, h, w, fx, fy):
paint_background(0xFA2BEE, fx, fy)
spring = slice_image(image, x, y, x+w, y+h)
paint(image, px, py)
def clear_frame(fx, fy):
paint_background(0xFA2BEE, fx, fy)
然而,这显然是一个坏主意。首先,弗里克是0xFA2BEE
?任何阅读你的代码的人 - 即使你将来 - 都会被它弄糊涂。另外,如果应该将背景更改为0xB2BAA3
,该怎么办?现在,您必须在代码中找到所有0xFA2BEE
,确保它是背景的值而不是相同的其他值然后替换它。不好,对吗? 如果这很糟糕,你可以这样想:好吧,因为我应该避免使用“全局”变量,但原始值不好用,我可以将它们作为参数传递!
def paint_frame(image, x, y, h, w, fx, fy, background):
paint_background(background, fx, fy)
spring = slice_image(image, x, y, x+w, y+h)
paint(image, px, py)
def clear_frame(fx, fy, background):
paint_background(background, fx, fy)
好吧,至少现在你0xFA2BEE
作为paint_frame
/ clear_frame
的最后一个参数是背景值。但是,您在一些已经复杂的函数中添加了另一个参数。此外,如果您更改背景,则必须在整个代码库中找到对paint_frame
/ clear_frame
的所有来电。不酷,兄弟。
相反,你可以......
...创建常量!事实上,这是作者所做的:
BACKGROUND = 0xFA2BEE
def paint_frame(image, x, y, h, w, fx, fy):
paint_background(BACKGROUND, fx, fy)
spring = slice_image(image, x, y, x+w, y+h)
paint(image, px, py)
def clear_frame(fx, fy):
paint_background(BACKGROUND, fx, fy)
现在,您1)不需要猜测0xFA2BEE
是否为背景值,2)您的功能更简单; 3)您可以轻松地在一个地方更改背景值。如您所见,模块级变量的使用在这里付出了很多。即使您需要在大多数中使用相同的背景值,但并非所有位置,该常量也会有所帮助:只需将其设为background
的默认值即可参数:
BACKGROUND = 0xFA2BEE
def paint_frame(image, x, y, h, w, fx, fy, background=BACKGROUND):
paint_background(background, fx, fy)
spring = slice_image(image, x, y, x+w, y+h)
paint(image, px, py)
def clear_frame(fx, fy, background=BACKGROUND):
paint_background(background, fx, fy)
现在你打电话,让我们大多数时间说paint_frame(img, i, j, FRAME_SIZE, FRAME_SIZE, k*FRAME_SIZE, l*FRAME_SIZE)
,只在需要时传递背景参数
常量也适合作为全局变量/模块级变量,因为它们不会更改。全局变量特别令人讨厌,因为你应该跟踪它们的值。但是,如果值没有改变,这不会有问题,对吗?
注意:这些变量是常规的常量,可以更新。 Python没有任何只读变量,但您可以通过遵循代码标准来避免问题。)
你会发现很多人说“不要使用X”,“不要做Y”,“总是使用Z”等等。嗯,大多数时候这些建议都是正确的,但你可能会永远找到他们不适用的背景。这是“不使用全局”规则的情况。在某些情况下,使用全球是最佳选择。即使在C语言中也存在这样的情况,并且Python有很多其他情况可以做到,因为模块级变量仍然比全局变量更安全。
避免全局变量(或模块级变量)的建议是一个很好的建议,特别是对于新手。但是,正如您所看到的,有些情况下最好不遵循它。
答案 1 :(得分:1)
在Python中,你不会遇到全局问题,因为Python全局变量在模块级别。 (所以 global 无论如何都有点用词。)在许多情况下使用模块级全局变量是不错的,因为真正的全局变量是不合适的。
答案 2 :(得分:1)
书籍和文章中的代码解释事情要做的事情要坚持到底,不要淹没读者可能是特定主题的噪音或切线。
作者正在采取捷径来保持代码尽可能针对他们解释的问题。
也就是说,有比使用模块级变量更好的方法来做他们正在做的事情。甚至在书籍和教程中也是如此。
包含太多范围的变量,无论它们处于什么级别,都会产生副作用,即创建看不见的副作用,这些副作用会从应用程序变为的巨大状态机中显现出来。
它应该告诉您,您可能需要更好地封装数据。应将多个相关参数分组到单个数据结构中,并将该单个数据结构作为参数传递。
答案 3 :(得分:1)
尽管您可能听过任何一般性警告,但常量全局变量都可以。
在Python中,“常量”表示“以大写字母命名为暗示没人应该修改它们”,“全局”意味着“模块级变量”,但原则仍然适用。所以希望GRIDSIZE
和BACKGROUNDCOLOR
在程序的持续时间内保持不变,但它们可能不是:我似乎没有代码。
标准Python库中有很多模块级常量的例子。例如,errno
包含E2BIG
,EACCESS
等,math
包含math.pi
。
但是,这些例子比GRIDSIZE
更加稳定。据推测,它可能会在同一程序的不同运行之间发生变化,而math.pi
则不会。因此,您需要评估此特定全局的后果,并决定是否使用它。
我总是被告知全局性一般都不好
糟糕的事情是有原因的,不是“一般都不好”。为了决定做什么,你需要了解原因。使用全局变量(或者,在Python中,模块范围对象)会导致某些问题:
如果您使用可在其他地方修改的全局,那么为了让某人阅读代码来推断其当前值,他们可能需要了解整个程序,因为程序可能访问全局。如果将mutable保留在只有部分代码可以看到的对象中,那么您只需要推断代码中与该对象交互的那些部分。
如果您的程序是一个100行长的文件,这听起来可能不是很大,但最终会变成一个。使代码易于使用几乎所有使读者能够轻松地推断代码的作用。还有什么可以影响它是其中很重要的一部分,所以这很重要。而“读者”就是你,下次你想改变一些东西,所以帮助你的代码的“读者”纯属自身利益。
对并发执行代码的依赖:mutable globals + threads = need locks = effort。
隐藏的依赖关系:这甚至适用于常量。
您必须确定这段代码是否可以使某些依赖项不被该代码的调用者作为参数(“注入”)提供。
这个问题的答案几乎总是“是”。如果它“绝对不是”,那么如果您不喜欢大量参数,那么您就处于紧张状态,因为您甚至不会按名称使用Python内置函数,更不用说代码导入的模块中的名称了。确定隐藏依赖项是可以的,你必须考虑GRIDSIZE
作为其中之一的后果,对你使用的方式,特别是测试代码。如果您不想在同一个程序中绘制不同大小的网格,那么在您想要编写涵盖许多不同GRIDSIZE
值的测试之前,您基本上就可以了。你这样做是为了让自己有信心现在当你稍后更改GRIDSIZE
时什么都不会破坏。
此时您可能希望能够将其作为参数提供。因此,您可能会发现使用默认值而不是全局值的参数更有用。为了防止长重复的参数列表,您可以传递一个对象,该对象组合了所有这些设置而不是单独的每个设置。只要注意创建一个具有多个无关目的的对象,因为如果你只想推理一个,那么其他人就会分散注意力。
static
则两个完全不相关的文件不能具有相同名称的不同功能。在Python中并不那么重要,因为一切都在命名空间中。答案 4 :(得分:0)
这取决于具体情况。
全球通常不是坏事,但如果不是绝对必要,应该避免。
例如,常量完全可以作为全局变量,而配置数据应该尽可能地捆绑(封装)。
如果将所有这些数据集中在一个(或几个)对象上,您可能希望将命名函数作为对象类的方法,或者您可能希望从这些函数中访问这些对象。 / p>
最后,这是一个品味问题。如果你认为事物可以方便地处理,那么就让它成为现实。如果它看起来像混乱,最好改变它。