Python Beginners: Creating dynamic class objects with dynamic attributes with loops - python

I'm trying to create some simple objects that are defined dynamically through a class - to allow me to rapidly iterate through the creation of all possibilities of these objects.
class NSObjects:
def __init__(self, shape, position, shading):
self.shape = shape
self.position = position
self.shading = shading
def __str__(self):
return '{} - {} - {}'.format(self.shape(), self.position(), self.shading())
def NSGenerator_1():
for i in range (0,3):
obj_1_i = NSObjects(shape_init_top + i, posn_init_top+i, shading_init_top+i)
for i in range (3,6):
obj_1_i = NSObjects(shape_init_mid + i, posn_init_mid+i, shading_init_mid+i)
for i in range (6,9):
obj_1_i = NSObjects(shape_init_mid + i, posn_init_mid+i, shading_init_mid+i)
NSGenerator_1()
print(obj_1_2)
At the moment it is telling me that obj_1_2 doesn't exist. For the purpose of this you can assume that I have defined all the init variables to start at 0, 1 or 2 elsewhere in the code. I am basically trying to create a series of objects which will have properties as defined by a mathematical formula.
Thanks in advance for any help you can provide (I only started coding a few weeks ago so this might be a very silly question!)

You only ever assigned to obj_1_i, not obj_1_2, and it was local to the function. There is no way for Python to tell that the _i was meant as a separate variable instead of part of the longer variable name. For a quick fix, try replacing the
obj_1_i = parts with globals()[f'obj_1_{i}'] =.
But rolling numeric indexes into the variable names like that (_1_2) is a code smell. A better design is to actually use them as indexes to a data structure, like a list or dict.
For example, define
obj = {} at the top level (outside of any class or function).
Then you can replace obj_1_2 everywhere with obj[1, 2], etc. If you wrote them that way,obj[1, i] would work as you expect inside those for loops.

Related

Methods/function naming conventions, static methods in Python

I have a problem with naming two similar methods. One is a static method and another one is a method that is the same method but works on the instance. Is it a proper way to do it or should I use only a static method.
Class GameBoard()
def __init__(self, blank_board):
self.board = blank_board
#staticmethod
def get_empty_cells(board):
"""Returns a list of empty cells coordinates (x,y)"""
empty = []
for row_no, row in enumerate(board):
for cell_no, cell in enumerate(row):
if cell == ' ':
empty.append((row_no, cell_no))
return empty
def board_empty_cells(self):
return self.get_empty_cells(self.board)
board1 = GameBoard(blank_board)
board2 = [.....]
empty_board1 = board1.board_empty_cells()
empty_board2 = GameBoard.get_empty_cells(board2)
The reason of that is that I would like to be able to evaluate other boards with the static method, but also if I want to get the instance's empty cells I would like to call board_empty_cells().
Is that a clean code approach or should I get empty cells like:
board1 = GameBoard(blank_board)
empty_board1 = board1.get_empty_cells(board1.board)
What would be proper names for those two functions to be descriptive and unambiguous that one of them is a static method and another operates on instance. Is there any convention to follow to distinguish static methods from methods?
#staticmethod
def get_empty_cells(board):
pass
def board_empty_cells(self):
pass
A lot of times I run into a 'problem' with proper naming for methods and functions.
Is there any guide/convention how to properly name methods (like get_board, is_finished etc.)? I don't mean PEP 8 which I'm familiar with. I mean something that would help me choose proper names that actually would make my code more readable.

Python Appending objects to a list

Beginner with Python, need some help to understand how to manage list of objects.
I built a list of simple objects representing a coordinate
class Point:
def __init__(self, x, y):
self.x=0
self.y=0
I create a empty list to store different points :
combs = []
point = Point(0, 0)
then I build different points using point and ever ytime appending to the list combs
For instance:
point.x=2
point.y=2
combs.append(point)
point.x=4
point.y=4
combs.append(point)
I expect that combs is something like [.. 2,2 4,4] on the contrary it's [....4,4 4,4].
It means that every time I change the instance of a point, I change all the points stored in the list with the latest value.
How can I do this?
The thing is when you're trying to change the value of x and y , you're expecting to have a new object (like a new x and y with different values) but you aren't. What happens is I think is whenever you set point.x =4 and point.y = 4 is you're just changing the attribute x and y in your class
take a look at this link. This helped me a lot, I encountered that kind of problem or should I say similar of yours
I suggest using the copy package
https://www.programiz.com/python-programming/shallow-deep-copy
You are appending the same variable to the combine. You need to create a new Point object and initialize it with the new values.
combine = []
p = Point(2,2)
combine.append(p)
p = Point(4,4)
combine.append(p)
This is because Python is using reference count for the garbage collection.
In your example you create point which increments the ref count.
Then you pass it to the list which increment the count. Then change the value and pass the same variable. Which increments the count once again.
Think of it more like passing a reference or memory pointer of the point variable to the list. You gave the list twice the same pointer.
So you need to create different variables or make a deep copies https://docs.python.org/3.8/library/copy.html
Custom classes, unless they are built on an immutable type, are mutable. This means that you have appended a reference to your list, and that changing the value of the references will change every instance of the reference. Consider this:
class Test():
def __init__(self, a):
self.a = a
>>> t1 = Test(1)
>>> t1.a
1
>>> t2 = t1
>>> t2.a
1
>>> t2.a = 2
>>> t2.a
2
>>> t1.a
2
See how changing t2 also changed t1?
So you have a few options. You could create new points instead of reusing old ones, you could use a copy() method, or you could write a method into your Point class that exports something immutable, like a tuple of the (x, y) values and append that to your list, instead of appending the entire object to your list.
You are working with only a single Point. Construct a second one. See commented line below.
point = Point(0, 0)
point.x=2
point.y=2
combs.append(point)
point = Point(0, 0) # add this
point.x=4
point.y=4
combs.append(point)
By the way, your __init__ ignores its parameters -- throws them away. A better version is below. We assign self.x=x to make use of the parameter. (Likewise y).
def __init__(self, x, y):
self.x=x
self.y=x
You need to pass one value into point
How to add an item to your list
combs = []
point = 1 # For example
combs.append(point)
Use command lines to study
Try to use BASH or CMD... Use command lines... They will have instant feedback of your code
Good place to find basic stuff
Try to see the examples on w3scholl. It is a great place. Here is the link for W3Scholl - Python - List
Understand basics first
Before you jump into classes, try to understand lists very well! You will learn more and build a solid knowledge if you take a step by step growing! Keep pushing!!!

How to create several classes in a loop?

I have a script, where I have to define several classes:
class track1:
number = 0
min_class = 0
members = []
class track2:
number = 0
min_class = 0
members = []
class track3:
number = 0
min_class = 0
members = []
And so on...
Later I change in some classes the values. For example: min_class will be 10 in the classes 2,5 and 6. Or the members list will contain different members in every different track.
But sometimes I have to define four classes, sometimes 16.
So my question is: Is there a way, to define classes in Python in a loop?
for i in range(x):
#define class track i
Use the type function to dynamically create classes.
track = []
for i in range(x):
track.append(type('track%d' % i, (), {'number': 0, 'min_class': 0, 'members': []}))
Yes - this can be done. The only strange part is to force the classes created dynamically to actually have a name in the module - although it can be done by writting to the globals() dictionary.
Anyway, what creates a class dynamically in Python is a call to type: the class of which classes are instances.
So, a simple way there, if the classes have all the same body, is to create a Base class for all of then, and then you could do at module level:
class Base:
attributes = 0
...
for i in range(16):
globals()[f'class{i}'] = type(f'class{i}', (Base,), {})
Depending on what how you intend your code to be read, if the name is the only issue, you could just write a for loop, and declare the class inside it as well, just taking care of the name - creating a class with a class kewyord block and using type are equivalent (but for static analysis tools, like autocompleters used by IDEs - this stuff will get lost eitherway)
for i in range(16):
class Base:
attributes = []
...
Base.__name__ = f"class{i}"
globals()[f"class{i}"] = Base
However, as I stated in the very beggining, it is not usual to dynamically create variables in Python code - and by variables here, I mean anything with a static name , including functions and classes - if you are typing the hardcoded name of such a class in another ".py" file, it should be typed hardcoded here.
So, if your classes are to be used dymically, let's say some other code have to select an specific class of these based on some other data, it is not conveninent they are bound to the module as "class1, class2", etc... rather, they should be part of another data structures, such as a list or dictionary - let's suppose you want one such class depending on a "product category" some other part of the code would have - You could just as well create a dicionary whose keys are product categories, and the values the classes.
Or, to keep things simple, let's just do a list:
myclasses = []
for i in range(16):
myclasses.append(type(f"class{i}", (Base,), {})
(The form with the class body is valid as well, the only difference is that you don't assign your generated classes to the dict in globals() , and rather, to another data structure.
Your class definitions are identical.
Why not have one class definition:
class track:
number = 0
min_class = 0
members = []
and then create as many instances as you need?
L = []
for i in range(x):
L.append(track())
Now, you possibly want the class members to be instance members, so need to use self appropriately.
You've created your class trackX multiple times, however you need to create instances of one class:
class Track:
number = 0
min_class = 0
members = []
def __init__(number, min_class, members):
self.number = number
self.min_class = min_class
self.members = members
Then in your loop you want to create instances of your class:
for i in range(x):
track = Track(number, min_class, members)
If you want a list of tracks just append this track to your list of tracks:
tracks = []
for i in range(x):
track = Track(number, min_class, members)
tracks.append(track)
A large part of design and programming is avoiding or removing duplication.
This is what you are trying to do, so that's a good start.
However, the only thing that varies is the name of the class, which seems a strange thing to need.
When you instantiate the classes there will essentially be no other difference between the object types.
In design you want to encapsulate what stays the same (a class or algorithm for example), and parameterize it with what varies (data).
I'd advise you to parameterize the object constructor with the track name:
class Track:
def init(name, number, min_class, members):
self.name = name
self.number = number
self.min_class = min_class
self.members = members

Python: how to use some kind of generic setter?

I've made this code:
if fc.get('field_photo_1'):
pt.photo1 = fc.get('field_photo_1')
if fc.get('field_photo_2'):
pt.photo2 = fc.get('field_photo_2')
if fc.get('field_photo_3'):
pt.photo3 = fc.get('field_photo_3')
I'd like to optimize it to some kind of code like this:
update_field(photo1, 'field_photo_1'):
update_field(photo2, 'field_photo_2'):
update_field(photo3, 'field_photo_3'):
I just don't know how make kinddof a setter with index in python. How would you implement the update_field() function?
You could take a look at the setattr() function.
I would suggest making an array of photos and then iterate strings with a for loop instead of trying to create such setter. It's easier to understand and doesn't do uncommon things with your class that other programmers might not expect.
class Pt:
def __init__(self):
self.photos = [None for i in range(3)]
pt = Pt()
for i in range(1,4)
fieldString = 'field_photo_{0}'.format(i)
if fc.get(fieldString):
pt.photos[i] = fc.get(fieldString)
You are looking for setattr, which accepts string attribute names.
Try this:
for x in range(1, 4):
attrname = "photo_".format(x)
setattr(pt, attrname, fc["field_".format(attrname)])
You should probably refactor your code though to use lists.
If you are wanting to set the pt attribute based on the passed parameter, setattr is the way to go.
def update_field(attribute, value):
setattr(pt, attribute, value)
The other answers here assume that you are iterating through a preset number of photo attributes. If you are wanting a single set, then the above should work. If looking to set them all, Ben's or Ritave's answer might be more suitable.
Depending on the scope of the "pt" variable. You might have to do something like setattr(self.pt, attribute, value).
You could use a dict:
photos = dict()
photos['photo1'] = fc.get('field_photo_1')
# etc...
Or more concisely:
photos = {photo: fc.get(photo) for photo in ('field_photo_1', 'field_photo_2', 'field_photo_3')}

Python Large Functions Many Variables (Refactoring)

I have a large function in my script that contains the bulk of the logic of my program.
At one point, it used to span ~100 lines which I then tried to refactor into multiple smaller functions. However, I had many local variables that were eventually being modified in the smaller functions, and I needed some way to keep track of them in the scope of the larger function.
For instance, it looked like
def large_func():
x = 5
... 100 lines ...
to
def large_func():
x = 6
small_func_that_will_increment_x()
small_func()
....
What is a pythonic way to handle this?
The two approaches I can think of are:
1) global variables --- will probably get messy as I have many variables
2) using a dict to keep track of them like
tracker = {
'field1' : 5
'field2' : 4
}
and make modifications on the dict instead.
Is there a different way to do this that I might have overlooked?
Without more information, it's hard to know whether this is appropriate or not, but…
An object is a namespace. In particular, you can turn each of those local variables into attributes on an object. For example:
class LargeThing(object):
def __init__(self):
self.x = 6
def large_func(self):
self.small_func_that_will_increment_x()
self.small_func()
# ...
def small_func_that_will_increment_x(self):
self.x += 1
Whether the self.x = 6 belongs in __init__ or at the start of large_func, or whether this is even a good idea, depends on what all those variables actually mean, and how they fit together.
Closures will work here:
def large_func()
x = 6
def func_that_uses_x():
print x
def func_that_modifies_x():
nonlocal x # python3 only
x += 1
func_that_uses_x()
func_that_modifies_x()
Another tip - make use of Python's ability to return multiple values. If you have a function that modifies two variables, do something like this:
def modifies_two_vars(a, b, c, d):
return a+b, c+d
x, y = modifies_two_vars(x, y, z, w)
One alternative could be:
def small_func_that_will_return_new_x(old_x):
return old_x + 1
def large_func():
x = small_func_that_will_return_new_x(6)
instead of:
def large_func():
x = 6
small_func_that_will_increment_x()
Object composition. Create small objects that hold state, and then feed them as initializers an object that manages them. See Global State and Singletons
"Build the door knob, which you use to build the door, which you use to construct the house. Not the other way around"

Categories