Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
I'm just trying to learn (re-learn) and I wan't to create a 2-dimensional grid of objects...
-
Hey everyone!
Long story short, I've dabbled in programming for many many years. I was most proficient with Blitzmax and a BASIC variant for PalmOS back in the day but I was never super serious. For example, I conceptually understand OOP and think it's very logical but in actual practice it used to give me a lot of trouble and I was more comfortable with procedural.
Anyway, its been years since I've done anything and Pythonista seemed like an attractive way to try to jump back into things (I have a soft spot for mobile platforms). There are very noticeable differences between Python and BASIC, and a lot of other stuff that's very similar overall. But I'm trying to slog through and do different things and there's one that I just can't seem to nail:
Initially I just wanted to create a grid (2D array, or however you'd prefer to call it) which could store an integer at each gridpoint and could be dynamically sized and created using loops (instead of typing out each list-item by hand). Something that could hypothetically be accessed with something like:
mygrid [2, 5] = 5 print mygrid [2, 5]
I actually basically managed to achieve that particular goal using numpy, with the following:
mygrid = numpy.zeros((heightvariable, widthvariable))
And from there I could change and access the integer using the first bit of code.
Next I wanted to take it a step further and create a similar grid, but instead of each gridpoint being an integer, I wanted to make each an instance of an object. I haven't had a clue how to do this and tutorials/info online hasn't seemed to help much so I've been trying to do it myself (while trying to get more comfortable with classes). To simplify it I've been starting out with trying to just create one row (list) of object instances and I haven't gotten anything working yet.
For reference: I'm loosely imagining/shooting for this as being a tilemap for [insert generic game here]. I'll be using the Scene module for output. And I'm looking for just the most barebones way to create a map object and then populate it with tiles which are arranged in rows/columns in a way that they can be individually accessed.
For additional reference: part of my problem I think has been syntax/code arrangement: I keep bumping into scope and argument issues, which I more or less understand but I'm not always sure how it wants me to resolve them. For example, in the following code:
class MyScene(scene.Scene): themap = Map() class Map(object): #blahblahblah
I get the error 'Map' is not defined (NameError). So I tried creating my map instance in the update() or init() methods of the scene (so that they're created at the start) but when I later tried to USE the map instance, I got the error global name 'mymap' is not defined (NameError). So there's a scope issue where I have no clue where I'm supposed to create it and how I'm supposed to make it accessible at least in the rest of the class.
I apologize for putting so much in here. I realize it's a lot of basic stuff, I've been trying to read as much as I can but I think I'll find it more helpful being able to have an actual dialog with someone. I appreciate any help anyone can give!
Cheers,
Nate -
You can think of a two-dimensional array as an array of arrays. You basically have a list of rows, and each row is a list of individual objects.
In Python, a
list
is the simplest way to represent a sequence of objects, so let's build a list of lists:from pprint import pprint def make_grid(num_rows, num_cols, value=None): grid = [] for y in range(num_rows): row = [] grid.append(row) for x in range(num_cols): row.append(value) return grid grid = make_grid(6, 4) grid[2][3] = 'hello' grid[3][3] = 'world' pprint(grid)
pprint
means "pretty-print", and it's a function/module that prints nested structures in a more readable form, so it shows up in the console as an actual grid, and not just a long list.The example above also shows how you would set objects in the grid, using the
[y][x]
notation (which is the same as= item).I've written the example in a way that's hopefully easy to understand, even if you're not that familiar with Python syntax (yet!). There's actually a much more concise way to achieve the same thing using list comprehensions:
grid = [[None for col in range(4)] for row in range(6)]
Oh, and welcome! :)
-
Thank you so much for taking the time to respond, and thanks for the work you've done on this project!
I think I understand everything in your example, and I like that you can put any datatype into each gridpoint instead of just an integer like with my numpy solution.
I was going to ask you how to then actually add the objects to the new grid but then I decided I wanted to see if I can do it myself. I have a solution below which runs, I'd love to know if there's anything wrong with it conceptually that I just have managed to jump around:
import random from pprint import * def make_grid(height, width): # 'Value' no longer needed grid = [] for y in range(height): row = [] grid.append(row) for x in range(width): row.append(MyClass(x)) # I'm not sure why this format works (as opposed to the usual instancename = ClassName() but again, it's what I found in an example. return grid def print_grid(height, width, grid): # Doesn't currently print in a nice grid fashion but it correctly accesses every object's 'randomnumber' value and prints the information. for y in range(height): for x in range(width): print grid[y][x].randomnumber, print "" # EDIT: added the comma to the first print command and the additional print command with the empty quotes in the larger loop to format the grid properly. I feel very clever now :) class MyClass(object): randomnumber = None # Initializing the variable. def __init__(self, number): self.number = number # I have no clue what this line is for (or what self.number refers to) but this line was used in an example I found. self.randomnumber = random.randint(0, 9) random.seed() grid = make_grid(7, 7) print_grid(7, 7, grid) # the arguments make it possible to print just a portion of the grid. # This print function no longer works currently since it won't let me access 'randomnumber' for the entire grid. # pprint(grid.randomnumber)
-
Some notes on your questions in the comments:
I'm not sure why this format works (as opposed to the usual instancename = ClassName() but again, it's what I found in an example.
As you only need the object once, there's no need to store it in a variable. It works for the same reason that
make_grid(7, 7)
works (as opposed toheight = 7; width = 7; grid = make_grid (height, width)
. I hope that makes sense.self.number = number # I have no clue what this line is for (or what self.number refers to) but this line was used in an example I found.
You're basically creating an attribute (instance variable) called
number
that gets the value that was passed when initializing the instance. In this case,self.number
will be the column number in the grid (because you passx
to the constructor when making the grid). You could remove this entirely, as I don't see you using thenumber
attribute anywhere else. You could then simply create yourMyClass
instances usingMyClass()
instead ofMyClass(x)
.One other important thing: Your
randomnumber = None
initialization doesn't do what you think it does. Put directly in the class's scope, this creates a class attribute, i.e. one that's the same for all instances of the class. The reason the random numbers are actually different for each instance in your example is that you also create an instance variable with the same name,self.randomnumber
, so the class variable gets "overshadowed". You should removerandomnumber = None
entirely.# This print function no longer works currently since it won't let me access 'randomnumber' for the entire grid.
If you want to make
pprint
work nicely with your class, you can implement the "magic"__repr__
method. This basically defines how an object is printed in the console.Example:
class MyClass(object): def __init__(self): self.randomnumber = random.randint(0, 9) def __repr__(self): return str(self.randomnumber)
Now,
pprint
basically acts as if you had plain numbers in your grid (but theMyClass
instances are still there of course, and you could change the__repr__
method to make them distinguishable from regular numbers). -
Again, thank you!
-
"As you only need the object once, there's no need to store it in a variable."
Since we're dealing with a list here, the list element number is taking place of the instancename, but written without the equal sign (since it's part of an argument)? I was initially interpreting that line as the value of X being the name of the instance but I see now that I was mis-reading it. And having eliminated the 'number' argument, I was able to delete 'x' without any effect on the program. -
"initialization doesn't do what you think it does. [etc.]"
So this is where my understanding was clearly off again. I thought that variables listed in the "shell" of the class would be generated for all instances of the class, but then syntax-wise you had to use self.variable in order to refer to them for the current instance.
So on that topic (since I was confused about scope earlier as well: if I want instance-variables (ones that will be different for each instance of the class), I should declare them in init() and prefix them with self? And if I want "class variables" (which I'm not sure how to use but conceptually understand), I declare them in the class scope and don't give them a prefix. Is all of that information correct?
-
-
@WTFruit said:
if I want instance-variables (ones that will be different for each instance of the class), I should declare them in
__init__()
and prefix them with self? And if I want "class variables" (which I'm not sure how to use but conceptually understand), I declare them in the class scope and don't give them a prefix. Is all of that information correct?Yes, that's correct.
-
You have been more than generous with your time and knowledge and this brief conversation has seriously helped me more than the countless hours of reading and searching that I've been doing so far. After the stuff you just clarified and explained, I've already been able to display the grid in a scene and hook it up to a touch event to re-generate the grid. I could ask more questions but I think they should wait awhile while I poke around more :).
Thanks,
Nate