Python: repeating a list of objects in a list of objects

I created two classes called "Home and Window." Then I compiled a list containing four Houses. Each instance of House has a Windows list. I am trying to iterate through the windows in every house and print its ID. However, I seem to be getting some odd results: S I would really appreciate any help.

#!/usr/bin/env python # Minimal house class class House: ID = "" window_list = [] # Minimal window class class Window: ID = "" # List of houses house_list = [] # Number of windows to build into each of the four houses windows_per_house = [1, 3, 2, 1] # Build the houses for new_house in range(0, len(windows_per_house)): # Append the new house to the house list house_list.append(House()) # Give the new house an ID house_list[new_house].ID = str(new_house) # For each new house build some windows for new_window in range(0, windows_per_house[new_house]): # Append window to house window list house_list[new_house].window_list.append(Window()) # Give the window an ID house_list[new_house].window_list[new_window].ID = str(new_window) #Iterate through the windows of each house, printing house and window IDs. for house in house_list: print "House: " + house.ID for window in house.window_list: print " Window: " + window.ID #################### # Desired output: # # House: 0 # Window: 0 # House: 1 # Window: 0 # Window: 1 # Window: 2 # House: 2 # Window: 0 # Window: 1 # House: 3 # Window: 0 #################### 
+6
source share
4 answers

You are currently using class attributes instead of instance attributes . Try changing the class definitions as follows:

 class House: def __init__(self): self.ID = "" self.window_list = [] class Window: def __init__(self): self.ID = "" 

Now that your code has all instances of House , the same window_list .

+5
source

C ++, Java, C #, etc. have this really strange behavior regarding instance variables, in which the data (members or fields, depending on which culture you belong to) that are described in the class {} block refer to instances, while functions (well, methods but C ++ programmers seem to hate the term and instead say “member functions”), described in one block, belong to the class itself. Strange and confusing when you really think about it.

Many people do not think about it; they just accept it and keep moving on. But this actually causes confusion for many newbies who assume that everything inside the block belongs to instances. This leads to bizarre (experienced programmers) issues and problems associated with the overhead for each instance of these methods, and the difficulty of wrapping them around the whole implementation concept of "vtable". (Of course, this is mainly a collective mistake of teachers for not explaining that vtables is just one implementation, and in order not to give clear distinctions between classes and instances in the first place.)

Python does not have this confusion. Since functions (including methods) are objects in Python, it would be oddly inconsistent for the compiler to make that distinction. So what happens in Python is what you should intuitively expect: everything in the indented class block belongs to the class itself. And, yes, Python classes they themselves are objects (which gives a place to place these class attributes), and you do not need to skip over the standard library fairings to use them reflectively. (The lack of manifest is printed here quite freely.)

So, how do I hear you protest, are we really adding any data to the instances? Well, by default, Python does not limit you to adding anything to any instance. This does not even require you to make different instances of the same class with the same attributes. And this, of course, does not predetermine one block of memory to contain all the attributes of an object. In any case, it could contain links, given that Python is a pure semantics reference language, without C # style value types or Java style primitives.)

But obviously, it’s a good idea to do so, so the usual convention is to “add all the data at the time the instance is created and then not add more (or remove any attributes)”.

"When is it built"? Python really has no constructors in the sense of C ++ / Java / C #, since this lack of "reserved space" means that there is no real benefit to considering "initialization" as a separate task from its usual purpose - except that the advantage of initialization is that automatically happens to a new object.

So, in Python, our closest equivalent is the magic __init__ method, which is automatically called for newly created instances of the class. (There is another magic __new__ method, which behaves more like a constructor, in the sense that it is responsible for the actual creation of the object. However, in almost every case, we just want to delegate the base __new__ object, which calls some kind of built-in logic, in principle to give we need a small pointing ball that can serve as an object and point it to the class definition. Therefore, there is no point worrying about __new__ almost every case. It is more similar to operator new overloading for a class in C ++.) In the body of this method (not with initialization squeals in C ++ style, since there are no previously reserved data for initialization), we set the initial values ​​for the attributes (and, possibly, do other work), based on the parameters that we give.

Now, if we want to be a little more careful about things, or efficiency is a real problem, there is another trick up our sleeve: we can use the __slots__ magic attribute for a class to specify the attribute names of the class. This is a list of strings, nothing unusual. However, this still does not initialize anything ; an instance does not have an attribute until you assign it. It just stops you from adding attributes with other names. You can even remove attributes from the object whose class specified __slots__ . All that happens is that the instances are given a different internal structure to optimize memory usage and attribute lookups.

Using __slots__ requires us to get out of the built-in type object , which we must do anyway (although this is not required in Python 2.x), this is for backward compatibility purposes only).


So now we can make the code work . But how to make it right for Python?

Firstly, just like in any other language, constantly commenting to explain already self-evident things, this is a bad idea. This distracts the user, and doesn’t really help you as a student of the language. You need to know what a class definition looks like, and if you need a comment to tell you that a class definition is a class definition, then reading the code comments is not the help you need.

With all of this “duck typing,” its poor form includes data type names in variable (or attribute) names. You are probably protesting, "but how should I track the type differently, without a manifest type declaration?" No. The code that uses your window list does not care that your window list is a list of windows. He just takes care that he can iterate over the list of windows and thus get values ​​that can be used in certain ways related to windows. The way duck printing works: stop thinking about what the object is , and worry about what he can do .

In the code below, you will notice that I have entered the string conversion code in the House and Window constructors. This serves as a primitive form of type checking, and also ensures that we cannot forget to do the conversion. If someone tries to create a House with an identifier that cannot even be converted to a string, then it will throw an exception. It’s easier to ask for forgiveness, except permission, in the end. (Note that you really need to do some work in Python to create

As for the actual iteration ... in Python, we iterate over the objects in the container. Java and C # also have this concept, and you can get it together with the standard C ++ library (although many people are not worried). We do not iterate over indices, because it is a useless and distracting indirectness. We do not need to specify our windows_per_house values ​​to use them; we just need to look at each value in turn.

What about the identification numbers, I hear what you are asking? Simply. Python provides us with an "enumerate" function that gives us (index, element) pairs, given the input sequence of elements). It is clean, it allows us to clearly indicate our need for an index to solve the problem (and the purpose of the index), and it is built in, which does not need interpretation, like all Python code, so it does not carry so much overhead. (When memory is a concern, a lazy-valued version can be used instead.)

But even then, the iterations to create each house, and then manually adding each of them to the initially empty list, are too low. Python knows how to build a list of values; we don’t need to tell how to do it. (And as a bonus, we usually get better performance by letting it do this part on its own, since the actual loop logic can now be executed internally, in native C.) Instead, we describe what we want in the list, with the list. We don’t need to go through the steps “each time to count each window count, make up the corresponding house and add it to the list”, because we can say “list of houses with the corresponding number of windows for each window-count in this input list”. This may be clunkier in English, but much cleaner in a programming language such as Python, because you can skip a bunch of small words and you don’t have to spend effort on describing the source list or the action of adding finished houses to the list. You do not describe the process at all, just the result. Made to order.

Finally, as a general programming concept, it makes sense, whenever possible, to postpone the construction of an object until we have everything that is necessary for the existence of this object. The "two-phase design" is ugly. Therefore, first we make windows for the house, and then at home (using these windows). With a list of concepts, it's simple: we just insert lists.

 class House(object): __slots__ = ['ID', 'windows'] def __init__(self, id, windows): self.ID = str(id) self.windows = windows class Window(object): __slots__ = ['ID'] def __init__(self, id): self.ID = str(id) windows_per_house = [1, 3, 2, 1] # Build the houses. houses = [ House(house_id, [Window(window_id) for window_id in range(window_count)]) for house_id, window_count in enumerate(windows_per_house) ] # See how elegant the list comprehensions are? # If you didn't quite follow the logic there, please try **not** # to imagine the implicitly-defined process as you trace through it. # (Pink elephants, I know, I know.) Just understand what is described. # And now we can iterate and print just as before. for house in houses: print "House: " + house.ID for window in house.windows: print " Window: " + window.ID 
+3
source

Here's the updated code.

 # Minimal house class class House: def __init__(self, id): self.ID = id self.window_list = [] # Minimal window class class Window: ID = "" # List of houses house_list = [] # Number of windows to build into each of the for houses windows_per_house = [1, 3, 2, 1] # Build the houses for new_house in range(len(windows_per_house)): # Append the new house to the house list house_list.append(House(str(new_house))) # For each new house build some windows for new_window in range(windows_per_house[new_house]): # Append window to house window list house_list[new_house].window_list.append(Window()) # Give the window an ID house_list[new_house].window_list[new_window].ID = str(new_window) #Iterate through the windows of each house, printing house and window IDs. for house in house_list: print "House: " + house.ID for window in house.window_list: print " Window: " + window.ID 

The actual problem is that the window_list attribute window_list changed, so when different instances use it, they end up using the exact same one. By moving window_list to __init__ , each instance gets its own.

+2
source

Besides some indentation errors, you assign an ID and window_list class, not instances.

Do you want something like

 class House(): def __init__(self, ID): self.ID = ID self.window_list = [] 

and etc.

Then you can do house_list.append(House(str(newHouse))) , etc.

0
source

Source: https://habr.com/ru/post/897265/


All Articles