0

The code below works (it will eventually pull records from a database via SQL), but I am having trouble understanding why the assert statement in the code below works.

def build_row(table, cols):
    """Build a class that creates instances of specific rows"""
    class DataRow:
        """Generic data row class, specialized by surrounding function"""
        def __init__(self, data):
            """Uses data and column names to inject attributes"""
            assert len(data)==len(self.cols)
            for colname, dat in zip(self.cols, data):
                setattr(self, colname, dat)
        def __repr__(self):
            return "{0}_record({1})".format(self.table, ", ".join(["{0!r}".format(getattr(self, c)) for c in self.cols]))
    DataRow.table = table
    DataRow.cols = cols.split()
    return DataRow

When is the length of 'data' determined and how is it guaranteed to be the same length as 'self.cols'? Will someone please explain this behavior?

2
  • 1
    it should be setattr(self, colname, dat) instead of using data. Commented Jun 13, 2011 at 4:07
  • 1
    you also have to add () to call cols.split() otherwise your code won't work Commented Jun 13, 2011 at 4:41

3 Answers 3

1

This may make more sense to you if you replace the references to self.cols and self.table with self.__class__.cols and self.__class__.table. This confusion is caused by accessing class attributes through the self object as if they were instance attributes, and why I don't particularly like this coding idiom. When looking at the code in __init__, where instance attributes are typically assigned their values, it is jarring to immediately see a statement that reads the value of self.cols - where the heck did self.cols get initialized?! On the other hand, if you change this assert to:

assert len(data)==len(self.__class__.cols)

then it becomes clearer that the list of data values is expected to be the same length as the defined list of columns for this instance of DataRow. The class attribute cols gets initialized right after the class definition, in this statement:

DataRow.cols = cols.split()

long before any instance gets created - that's why this assert works.

The writer of this code might consider converting to the more current namedtuple construct.

Sign up to request clarification or add additional context in comments.

9 Comments

@Paul McGuire: Your answer is useful (I upvoted it), but I'm still not understanding why the constructor of an instance of DataRow is able to satisfy the 'assert' statement. As I understand your answer, 'table' and 'cols' are attributes of the class, but not necessarily the instance. In the 'assert' statement the length of the list 'self.cols' is being compared to the length of 'data' (type? I would guess 'list'). I understand that 'data' is now an attribute of the instance of DataRow, but I don't see where 'data' can possibly be given a length before its length is compared to self.cols.
Data is being passed in to the constructor, and is most likely a list or tuple of values. The values in data correspond to the column names defined in cols. A simple example would be: DateClass=build_row('date','year month day'); today = DateClass([2011, 6, 13]); print today.year, today.month, today.day
And no, data is not an attribute of the instance, it is merely an argument passed to the initializer.
@Paul McGuire: Thank you for the above helpful comment. "Data is being passed in to the constructor, and is most likely a list or tuple of values." I still don't see where any assignment is being made to data before the assert statment is reached.
This is a fundamental concept, the passing of arguments to functions. Do you understand where a and b get "assigned" in this: def add(a,b): return a+b; c = add(10,20) When add is called, argument a gets the value 10, and argument b gets the value 20. In the DateClass example I posted, when the initializer gets called when creating today, argument data gets the value [2011, 6, 13].
|
1

data is given a value when you instantiate the class returned by the build_row() function.

It is not guaranteed to be the same length as self.cols; perhaps you never instantiate the class, and so never see an error.

4 Comments

What value is assigned to 'data' once the class is instantiated? Is it of type None by default and then later assigned a value?
No. data is an argument of the initializer, hence it gets the value passed to the constructor.
So, when the 'init' method is called, the inclusion of 'data' as an argument simply assigns the string 'data' to the namespace of the DataRow class. Is this a correct understanding?
Arguments to a function are considered local to that function. It doesn't become part of an object until you assign to an attribute.
0

When you defined DataRow, the column names are set in the variable self.cols. So, when you're instantiating the class, you're supposed to fill every column.

That's why you need both lists having the same length. Otherwise you may not have all attributes.

The way it's done is by setting an attribute with the name of the column which isn't be the best option because if you have a column named cols or table or even __repr__, it may break your code.

4 Comments

first because the code should be cols.split() instead of cols.split. Then it really works
NVM, Ignacio's answer explains it.
"When you defined DataRow, the column names are set in the variable self.cols." How does Python know to assign the column names to the variable self.cols? Is it because the superclass of of DataRow (object) has 'cols' in its namespace because we're still within the local namespace of the function build_row?
at the very end of your code: DataRow.cols = cols.split() it means that when you access self.cols you'll get the cols name because it's now an attribute

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.