Below is a partial class definition of mine:
class Trial:
font = pygame.font.Font(None, font_size)
target_dic = {let: font.render(let, True, WHITE, BG) for let in list("ABCDEFGHJKLMNPRSTUVWX")}
The last line of the partial class definition, target_dic = {let: font.render(let, True, WHITE, BG) for let in list("ABCDEFGHJKLMNPRSTUVWX")
returns the error: global name 'font' is not defined. Fair enough.
However, I tried the following test case and got no error:
class x:
dat = 1
datlist = [dat for i in range(10)]
Why should the first case not work? Doesn't the member font
exist by the time the dictionary comprehension is reached?
Do I need to move these operations to __init__
or is it possible to define the list exactly once when the class object is created?
EDIT:
For clarity, I'd like to be able to populate the list at class object creation time to cut down on the time spent creating Trial objects.
Partial answer, as it is more to cut some wrong paths to follow.
If I take back your working sample and put a dict comprehension:
class x:
dat = 1
datlist = {i:dat for i in range(10)}
I get also this:
>>> NameError: global name 'dat' is not defined
So it looks like dict comprehension is hiding the temporary dict use during class statement execution, but the list comprehension doesn't.
No further information found by now in the docs about this...
Edit based on @interjay comment: class construction not fulfilling scope norm is addressed in this post. Short story is that list comprehension are buggy in 2.x and see class members but they shouldn't.
Because class decorators are invoked after the class is created, you could use one to work around the limitations of referencing class attributes in the class body and effectively "post-process" the class just created and add any missing attributes:
def trial_deco(cls):
cls.target_dic = {let: cls.font.render(let, True, WHITE, BG) for let in "ABCDEFGHJKLMNPRSTUVWXZ"}
return cls
@trial_deco
class Trial:
font = pygame.font.Font(None, font_size)
target_dic = None # redefined by decorator
An alternative way would be to make the attribute initially a data-descriptor (aka "property") that was (only) invoked the first time the attribute was read. This is accomplished by overwriting the descriptor with the computed value, so it only happens once.
class AutoAttr(object):
""" data descriptor for just-in-time generation of instance attribute """
def __init__(self, name, font, antialias, color, background):
self.data = name, font, antialias, color, background
def __get__(self, obj, cls=None):
name, font, antialias, color, background = self.data
setattr(obj, name, {let: font.render(let, antialias, color, background)
for let in "ABCDEFGHJKLMNPRSTUVWXZ"})
return getattr(obj, name)
class Trial(object):
font = pygame.font.Font(None, fontsize)
target_dic = AutoAttr('target_dic', font, TRUE, WHITE, BG)
There's also other ways to do things like this, such as with meta-classes or defining a __new__
method, but it's doubtful that any would work better or be any less complex.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With