Re: [Tutor] question about descriptors

2015-11-13 Thread Steven D'Aprano
On Thu, Nov 12, 2015 at 12:11:19PM +, Albert-Jan Roskam wrote:

> > __getattr__() is only invoked as a fallback when the normal attribute 
> > lookup 
> > fails:
> 
> 
> Aha.. and "normal attributes" live in self.__dict__?

Not necessarily.

Attributes can live either in "slots" or the instance dict, or the class 
dict, or one of the superclass dicts. Some examples may help. Let's 
start with defining a hierarchy of classes, and make an instance:


class Grandparent(object):
spam = "from the grandparent class"
def __getattr__(self, name):
return "%s calculated by __getattr__" % name

class Parent(Grandparent):
eggs = "from the parent class"

class MyClass(Parent):
cheese = "from the instance's own class"

instance = MyClass()
instance.tomato = "from the instance itself"


The attributes defined above return their value without calling 
__getattr__:

py> instance.tomato, instance.cheese, instance.eggs, instance.spam
('from the instance itself', "from the instance's own class", 
 'from the parent class', 'from the grandparent class')

but only "tomato" lives in the instance __dict__:

py> instance.__dict__
{'tomato': 'from the instance itself'}


You can check MyClass.__dict__, etc. to see the other class attributes. 
And, of course, __getattr__ is called for anything not found in those 
dicts:

py> instance.foo
'foo calculated by __getattr__'


Slots are an alternative to dict-based attributes. If you have millions 
of instances, all with a fixed number of attributes, using a dict for 
each one can waste a lot of memory. Using slots is a way of optimizing 
for memory:

class Slotted(object):
__slots__ = ["spam", "eggs"]
def __init__(self):
self.spam = 1
self.eggs = 2
def __getattr__(self, name):
return "%s calculated by __getattr__" % name

x = Slotted()


This works similarly to the above, except there is no instance dict at 
all:

py> x.spam
1
py> x.eggs
2
py> x.foo
'foo calculated by __getattr__'
py> x.__dict__
'__dict__ calculated by __getattr__'


To be honest, I didn't expect that last result. I expected it to return 
Slotted.__dict__. I'm not entirely sure why it didn't.


[...]
> > If you need to intercept every attribute lookup use __getattribute__():
> 
> Fantastic, thank you for the clear explanation. Do you happen to know 
> whether the __getattr__ vs. __getattribute__ distinction was (a) a 
> deliberate design decision or (b) a historic anomaly? 

A bit of both.

Originally, classes didn't support __getattribute__. Only __getattr__ 
existed (together with __setattr__ and __delattr__), and as you have 
seen, that is only called where the normal attribute lookup mechanism 
fails. That was deliberate.

But in Python 2.2, "new style" classes were added. For technical 
reasons, new-style classes need to support intercepting every attribute 
lookup (that provides the hook for descriptors to work). So 
__getattribute__ was added, but only for new-style classes.

But be warned: writing your own __getattribute__ method is tricky to get 
right, and tends to slow down your class. So it's best avoided, unless 
you really need it.




-- 
Steve
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-13 Thread Albert-Jan Roskam
> To: tutor@python.org
> From: __pete...@web.de
> Date: Fri, 13 Nov 2015 09:26:55 +0100
> Subject: Re: [Tutor] question about descriptors
> 
> Albert-Jan Roskam wrote:
> 
> >> __getattr__() is only invoked as a fallback when the normal attribute
> >> lookup fails:
> > 
> > 
> > Aha.. and "normal attributes" live in self.__dict__?
> 
> I meant "normal (attribute lookup)" rather than "(normal attribute) lookup".
> __getattr__() works the same (I think) when there is no __dict__: 
> 
> >>> class A(object):
> ... __slots__ = ["foo"]
> ... def __getattr__(self, name):
> ... print "looking for", name
> ... return 42
> ... 
> >>> a = A()
> >>> a.foo
> looking for foo
> 42
> >>> a.__dict__
> looking for __dict__
> 42
> >>> a.foo = "bar"
> >>> a.foo
> 'bar'

Thank you again for the explanation. Much appreciated. I had not even thought 
about __slots__ yet.

  
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-13 Thread Peter Otten
Albert-Jan Roskam wrote:

>> __getattr__() is only invoked as a fallback when the normal attribute
>> lookup fails:
> 
> 
> Aha.. and "normal attributes" live in self.__dict__?

I meant "normal (attribute lookup)" rather than "(normal attribute) lookup".
__getattr__() works the same (I think) when there is no __dict__: 

>>> class A(object):
... __slots__ = ["foo"]
... def __getattr__(self, name):
... print "looking for", name
... return 42
... 
>>> a = A()
>>> a.foo
looking for foo
42
>>> a.__dict__
looking for __dict__
42
>>> a.foo = "bar"
>>> a.foo
'bar'


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-12 Thread Albert-Jan Roskam
> To: tutor@python.org
> From: __pete...@web.de
> Date: Wed, 11 Nov 2015 20:06:20 +0100
> Subject: Re: [Tutor] question about descriptors
> 
> Albert-Jan Roskam wrote:
> 
> >> From: st...@pearwood.info
> 
> >> Fortunately, Python has an mechanism for solving this problem:
> >> the `__getattr__` method and friends.
> >> 
> >> 
> >> class ColumnView(object):
> >> _data = {'a': [1, 2, 3, 4, 5, 6],
> >>  'b': [1, 2, 4, 8, 16, 32],
> >>  'c': [1, 10, 100, 1000, 1, 10],
> >>  }
> >> def __getattr__(self, name):
> >> if name in self._data:
> >> return self._data[name][:]
> >> else:
> >> raise AttributeError
> >> def __setattr__(self, name, value):
> >> if name in self._data:
> >> raise AttributeError('read-only attribute')
> >> super(ColumnView, self).__setattr__(name, value)
> >> def __delattr__(self, name):
> >> if name in self._data:
> >> raise AttributeError('read-only attribute')
> >> super(ColumnView, self).__delattr__(name)
> > 
> > That also seems very straightforward. Why does "if name in self._data:"
> > not cause a recursion? self._data calls __getattr__, which has self._data
> > in it, which...etc.
> 
> __getattr__() is only invoked as a fallback when the normal attribute lookup 
> fails:


Aha.. and "normal attributes" live in self.__dict__?


 
> >>> class A(object):
> ... def __getattr__(self, name):
> ... return self.data[name]
> ... 
> >>> a = A()
> >>> a.data = dict(foo="bar")
> >>> a.foo
> 'bar'
> >>> del a.data
> >>> import sys
> >>> sys.setrecursionlimit(10)
> >>> a.foo
> Traceback (most recent call last):
>   File "", line 1, in 
>   File "", line 3, in __getattr__
>   File "", line 3, in __getattr__
>   File "", line 3, in __getattr__
> RuntimeError: maximum recursion depth exceeded while calling a Python object
> 
> If you need to intercept every attribute lookup use __getattribute__():

Fantastic, thank you for the clear explanation. Do you happen to know whether 
the __getattr__ vs. __getattribute__ distinction was (a) a deliberate design 
decision or (b) a historic anomaly? If one considers the distinction between 
"normal attributes"  vs. "attributes of which the read/write/delete 
properties*) may be changed" , I'd say (a).
 
*) with files these are called "attributes", so one could call them attributes 
with attributes. :-)

> >>> class B(A):
> ... def __getattribute__(self, name):
> ... print "looking for", name
> ... return super(B, self).__getattribute__(name)
> ... 
> >>> b = B()
> >>> b.data = dict(foo="bar")
> >>> b.foo
> looking for foo
> looking for data
> 'bar'
> 
> 
> ___
> Tutor maillist  -  Tutor@python.org
> To unsubscribe or change subscription options:
> https://mail.python.org/mailman/listinfo/tutor
  
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-11 Thread Peter Otten
Albert-Jan Roskam wrote:

>> From: st...@pearwood.info

>> Fortunately, Python has an mechanism for solving this problem:
>> the `__getattr__` method and friends.
>> 
>> 
>> class ColumnView(object):
>> _data = {'a': [1, 2, 3, 4, 5, 6],
>>  'b': [1, 2, 4, 8, 16, 32],
>>  'c': [1, 10, 100, 1000, 1, 10],
>>  }
>> def __getattr__(self, name):
>> if name in self._data:
>> return self._data[name][:]
>> else:
>> raise AttributeError
>> def __setattr__(self, name, value):
>> if name in self._data:
>> raise AttributeError('read-only attribute')
>> super(ColumnView, self).__setattr__(name, value)
>> def __delattr__(self, name):
>> if name in self._data:
>> raise AttributeError('read-only attribute')
>> super(ColumnView, self).__delattr__(name)
> 
> That also seems very straightforward. Why does "if name in self._data:"
> not cause a recursion? self._data calls __getattr__, which has self._data
> in it, which...etc.

__getattr__() is only invoked as a fallback when the normal attribute lookup 
fails:

>>> class A(object):
... def __getattr__(self, name):
... return self.data[name]
... 
>>> a = A()
>>> a.data = dict(foo="bar")
>>> a.foo
'bar'
>>> del a.data
>>> import sys
>>> sys.setrecursionlimit(10)
>>> a.foo
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in __getattr__
  File "", line 3, in __getattr__
  File "", line 3, in __getattr__
RuntimeError: maximum recursion depth exceeded while calling a Python object

If you need to intercept every attribute lookup use __getattribute__():

>>> class B(A):
... def __getattribute__(self, name):
... print "looking for", name
... return super(B, self).__getattribute__(name)
... 
>>> b = B()
>>> b.data = dict(foo="bar")
>>> b.foo
looking for foo
looking for data
'bar'


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-11 Thread Peter Otten
Albert-Jan Roskam wrote:

>> class ReadColumn(object):
>> def __init__(self, index):
>> self._index = index
>> def __get__(self, obj, type=None):
>> return obj._row[self._index]
>> def __set__(self, obj, value):
>> raise AttributeError("oops")
> 
> This appears to return one value, whereas I wanted I wanted to return all
> values of a column, ie as many values as there are rows. But the logic
> probably won't change. 

Sorry, I overlooked that aspect. If you want a whole column you either have 
to iterate over the complete file and keep the data in memory or you need a 
separate file descriptor for every access of a column. Here's an 
implementation of the first:

def csv_columns(instream):
reader = csv.reader(instream, delimiter=";")

header = next(reader)
return namedtuple("Columns", header)._make(tuple(zip(*reader)))



___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-11 Thread Albert-Jan Roskam
> Date: Sun, 8 Nov 2015 01:24:58 +1100
> From: st...@pearwood.info
> To: tutor@python.org
> Subject: Re: [Tutor] question about descriptors
> 
> On Sat, Nov 07, 2015 at 12:53:11PM +, Albert-Jan Roskam wrote:
> 
> [...]
> > Ok, now to my question. I want to create a class with read-only 
> > attribute access to the columns of a .csv file. E.g. when a file has a 
> > column named 'a', that column should be returned as list by using 
> > instance.a. At first I thought I could do this with the builtin 
> > 'property' class, but I am not sure how. 
> 
> 90% of problems involving computed attributes (including "read-only" 
> attributes) are most conveniently solved with `property`, but I think 
> this may be an exception. Nevertheless, I'll give you a solution in 
> terms of `property` first.
> 
> I'm too busy/lazy to handle reading from a CSV file, so I'll fake it 
> with a dict of columns.
 
Actually, I want to make this work for any iterable, as long as I can get the 
header names and as long as it returns one record per iteration.

 
> class ColumnView(object):
> _data = {'a': [1, 2, 3, 4, 5, 6],
>  'b': [1, 2, 4, 8, 16, 32],
>  'c': [1, 10, 100, 1000, 1, 10],
>  }
> @property
> def a(self):
> return self._data['a'][:]
> @property
> def b(self):
> return self._data['b'][:]
> @property
> def c(self):
> return self._data['c'][:]

Interesting. I never would have thought to define a separate class for this.
 
 
> And in use:
> 
> py> cols = ColumnView()
> py> cols.a
> [1, 2, 3, 4, 5, 6]
> py> cols.a = []
> Traceback (most recent call last):
>   File "", line 1, in ?
> AttributeError: can't set attribute
> 
> 
> 
> Now, some comments:
> 
> (1) You must inherit from `object` for this to work. (Or use Python 3.) 
> It won't work if you just say "class ColumnView:", which would make it a 
> so-called "classic" or "old-style" class. You don't want that.

Are there any use cases left where one still must use old-style classes? Or 
should new code always inherit from object (unless one want to inherit from 
another "true" class, of course).

 
> (2) Inside the property getter functions, I make a copy of the lists 
> before returning them. That is, I do:
> 
> return self._data['c'][:]
> 
> rather than:
> 
> return self._data['c']
> 
> 
> The empty slice [:] makes a copy. If I did not do this, you could mutate 
> the list (say, by appending a value to it, or deleting items from it) 
> and that mutation would show up the next time you looked at the column.

These mutability problems always make me pull my hair out! :-) I like the [:] 
notation, but: 

In [1]: giant = range(10 ** 7)

In [2]: %timeit copy1 = giant[:]
10 loops, best of 3: 97 ms per loop

In [3]: from copy import copy

In [4]: %timeit copy2 = copy(giant)
10 loops, best of 3: 90 ms per loop

In [5]: import copy

In [6]: %timeit copy2 = copy.copy(giant)
10 loops, best of 3: 88.6 ms per loop

Hmmm, wicked, when I looked earlier this week the difference appear to be 
bigger.

 
> (3) It's very tedious having to create a property for each column ahead 
> of time. But we can do this instead:
> 
> 
> def make_getter(key):
> def inner(self):
> return self._data[key][:]
> inner.__name__ = key
> return property(inner)
> 
> 
> class ColumnView(object):
> _data = {'a': [1, 2, 3, 4, 5, 6],
>  'b': [1, 2, 4, 8, 16, 32],
>  'c': [1, 10, 100, 1000, 1, 10],
>  }
> for key in _data:
> locals()[key] = make_getter(key)
> del key
> 
> 
> and it works as above, but without all the tedious manual creation of 
> property getters.
> 
> Do you understand how this operates? If not, ask, and someone will 
> explain. (And yes, this is one of the few times that writing to locals() 
> actually works!)

I think so. I still plan to write several working implementations to get a 
better idea about which strategy to  choose. 
 
> (4) But what if you don't know what the columns are called ahead of 
> time? You can't use property, or descriptors, because you don't know 
> what to call the damn things until you know what the column headers are, 
> and by the time you know that, the class is already well and truly 
> created. You might think you can do this:
> 
> class ColumnView(object):
> def __init__(self):
> # read the columns from the

Re: [Tutor] question about descriptors

2015-11-11 Thread Albert-Jan Roskam


 
> I think the basic misunderstandings are that 
> 
> (1) the __get__() method has to be implemented by the descriptor class
> (2) the descriptor instances should be attributes of the class that is 
> supposed to invoke __get__(). E. g.:
> 
> class C(object):
>x = decriptor()
> 
> c = C()
> 
> c.x # invoke c.x.__get__(c, C) under the hood.

Exactly right, that was indeed my misunderstanding! I was thinking about 
__get__ and __set__ in the same terms as e.g. __getitem__ and __setitem__

 
> As a consequence you need one class per set of attributes, instantiating the 
> same AttrAccess for csv files with differing layouts won't work.

That is no problem at all for me. One instance per file will be fine.


> Here's how to do it all by yourself:
> 
> class ReadColumn(object):
> def __init__(self, index):
> self._index = index
> def __get__(self, obj, type=None):
> return obj._row[self._index]
> def __set__(self, obj, value):
> raise AttributeError("oops")

This appears to return one value, whereas I wanted I wanted to return all 
values of a column, ie as many values as there are rows.
But the logic probably won't change. Same applies to the use of namedtuple, I 
suppose (?). I have never used namedtuple like namedtuple("Column", 
self.header)(*self.columns).

 
> def first_row(instream):
> reader = csv.reader(instream, delimiter=";")
> 
> class Row(object):
> def __init__(self, row):
> self._row = row
> 
> for i, header in enumerate(next(reader)):
> setattr(Row, header, ReadColumn(i))
> 
> return Row(next(reader))
> 
> 
> f = StringIO("a;b;c\n1;2;3\n4;5;6\n7;8;9\n")
> row = first_row(f)
> print row.a
> row.a = 42
> 
> Instead of a custom descriptor you can of course use the built-in property:
> 
> for i, header in enumerate(next(reader)):
> setattr(Row, header, property(lambda self, i=i: self._row[i]))

This seems most attractive/straightforward to me.

> In many cases you don't care about the specifics of the row class and use 
> collections.namedtuple:
> 
> 
> def rows(instream):
> reader = csv.reader(instream, delimiter=";")
> Row = collections.namedtuple("Row", next(reader))
> return itertools.imap(Row._make, reader)
> 
> 
> f = StringIO("a;b;c\n1;2;3\n4;5;6\n7;8;9\n")
> row = next(rows(f))
> print row.a
> row.a = 42

Thanks a lot for helping me!


  
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-07 Thread Steven D'Aprano
On Sat, Nov 07, 2015 at 12:53:11PM +, Albert-Jan Roskam wrote:

[...]
> Ok, now to my question. I want to create a class with read-only 
> attribute access to the columns of a .csv file. E.g. when a file has a 
> column named 'a', that column should be returned as list by using 
> instance.a. At first I thought I could do this with the builtin 
> 'property' class, but I am not sure how. 

90% of problems involving computed attributes (including "read-only" 
attributes) are most conveniently solved with `property`, but I think 
this may be an exception. Nevertheless, I'll give you a solution in 
terms of `property` first.

I'm too busy/lazy to handle reading from a CSV file, so I'll fake it 
with a dict of columns.


class ColumnView(object):
_data = {'a': [1, 2, 3, 4, 5, 6],
 'b': [1, 2, 4, 8, 16, 32],
 'c': [1, 10, 100, 1000, 1, 10],
 }
@property
def a(self):
return self._data['a'][:]
@property
def b(self):
return self._data['b'][:]
@property
def c(self):
return self._data['c'][:]



And in use:

py> cols = ColumnView()
py> cols.a
[1, 2, 3, 4, 5, 6]
py> cols.a = []
Traceback (most recent call last):
  File "", line 1, in ?
AttributeError: can't set attribute



Now, some comments:

(1) You must inherit from `object` for this to work. (Or use Python 3.) 
It won't work if you just say "class ColumnView:", which would make it a 
so-called "classic" or "old-style" class. You don't want that.


(2) Inside the property getter functions, I make a copy of the lists 
before returning them. That is, I do:

return self._data['c'][:]

rather than:

return self._data['c']


The empty slice [:] makes a copy. If I did not do this, you could mutate 
the list (say, by appending a value to it, or deleting items from it) 
and that mutation would show up the next time you looked at the column.


(3) It's very tedious having to create a property for each column ahead 
of time. But we can do this instead:


def make_getter(key):
def inner(self):
return self._data[key][:]
inner.__name__ = key
return property(inner)


class ColumnView(object):
_data = {'a': [1, 2, 3, 4, 5, 6],
 'b': [1, 2, 4, 8, 16, 32],
 'c': [1, 10, 100, 1000, 1, 10],
 }
for key in _data:
locals()[key] = make_getter(key)
del key


and it works as above, but without all the tedious manual creation of 
property getters.

Do you understand how this operates? If not, ask, and someone will 
explain. (And yes, this is one of the few times that writing to locals() 
actually works!)


(4) But what if you don't know what the columns are called ahead of 
time? You can't use property, or descriptors, because you don't know 
what to call the damn things until you know what the column headers are, 
and by the time you know that, the class is already well and truly 
created. You might think you can do this:

class ColumnView(object):
def __init__(self):
# read the columns from the CSV file
self._data = ...
# now create properties to suit
for key in self._data:
setattr(self, key, property( ... ))


but that doesn't work. Properties only perform their "magic" when they 
are attached to the class itself. By setting them as attributes on the 
instance (self), they lose their power and just get treated as ordinary 
attributes. To be technical, we say that the descriptor protocol is only 
enacted when the attribute is found in the class, not in the instance.

You might be tempted to write this instead:

setattr(self.__class__, key, property( ... ))

but that's even worse. Now, every time you create a new ColumnView 
instance, *all the other instances will change*. They will grown new 
properties, or overwrite existing properties. You don't want that.

Fortunately, Python has an mechanism for solving this problem: 
the `__getattr__` method and friends.


class ColumnView(object):
_data = {'a': [1, 2, 3, 4, 5, 6],
 'b': [1, 2, 4, 8, 16, 32],
 'c': [1, 10, 100, 1000, 1, 10],
 }
def __getattr__(self, name):
if name in self._data:
return self._data[name][:]
else:
raise AttributeError
def __setattr__(self, name, value):
if name in self._data:
raise AttributeError('read-only attribute')
super(ColumnView, self).__setattr__(name, value)
def __delattr__(self, name):
if name in self._data:
raise AttributeError('read-only attribute')
super(ColumnView, self).__delattr__(name)



-- 
Steve
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-07 Thread Peter Otten
Albert-Jan Roskam wrote:

> 
> 
> p, li { white-space: pre-wrap; }
> 
> Hi,
> First, before I forget, emails from hotmail/yahoo etc appear to end up in
> the spam folder these days, so apologies in advance if I do not appear to
> follow up to your replies. Ok, now to my question. I want to create a
> class with read-only attribute access to the columns of a .csv file. E.g.
> when a file has a column named 'a', that column should be returned as list
> by using instance.a. At first I thought I could do this with the builtin
> 'property' class, but I am not sure how. I now tried to use descriptors
> (__get__ and __set__), which are also used by ' property' (See also:
> https://docs.python.org/2/howto/descriptor.html).
> 
> In the " if __name__ == '__main__'" section, [a] is supposed to be a
> shorthand for == equivalent to [b]. But it's not.I suspect it has to do
> with the way attributes are looked up. So once an attribute has been found
> in self.__dict__ aka "the usual place", the search stops, and __get__ is
> never called. But I may be wrong. I find the __getatttribute__,
> __getattr__ and __get__ distinction quite confusing. What is the best
> approach to do this? Ideally, the column values should only be retrieved
> when they are actually requested (the .csv could be big). Thanks in
> advance!
> 
> 
> 
> import csv
> from cStringIO import StringIO
> 
> 
> class AttrAccess(object):
> 
> 
> def __init__(self, fileObj):
> self.__reader = csv.reader(fileObj, delimiter=";")
> self.__header = self.__reader.next()
> #[setattr(self, name, self.__get_column(name)) for name in
> #[self.header]
> self.a = range(10)
> 
> 
> @property
> def header(self):
> return self.__header
> 
> def __get_column(self, name):
> return [record[self.header.index(name)] for record in
> self.__reader]  # generator expression might be better here.
> 
> def __get__(self, obj, objtype=type):
> print "__get__ called"
> return self.__get_column(obj)
> #return getattr(self, obj)
> 
> def __set__(self, obj, val):
> raise AttributeError("Can't set attribute")
> 
> if __name__ == " __main__":
> f = StringIO("a;b;c\n1;2;3\n4;5;6\n7;8;9\n")
> instance = AttrAccess(f)
> print instance.a  # [a] does not call __get__. Looks, and finds, in
> self.__dict__?
> print instance.__get__("a")  # [b] this is supposed to be equivalent
> to [a]
> instance.a = 42  # should throw AttributeError!

I think the basic misunderstandings are that 

(1) the __get__() method has to be implemented by the descriptor class
(2) the descriptor instances should be attributes of the class that is 
supposed to invoke __get__(). E. g.:

class C(object):
   x = decriptor()

c = C()

c.x # invoke c.x.__get__(c, C) under the hood.

As a consequence you need one class per set of attributes, instantiating the 
same AttrAccess for csv files with differing layouts won't work.

Here's how to do it all by yourself:

class ReadColumn(object):
def __init__(self, index):
self._index = index
def __get__(self, obj, type=None):
return obj._row[self._index]
def __set__(self, obj, value):
raise AttributeError("oops")


def first_row(instream):
reader = csv.reader(instream, delimiter=";")

class Row(object):
def __init__(self, row):
self._row = row

for i, header in enumerate(next(reader)):
setattr(Row, header, ReadColumn(i))

return Row(next(reader))


f = StringIO("a;b;c\n1;2;3\n4;5;6\n7;8;9\n")
row = first_row(f)
print row.a
row.a = 42

Instead of a custom descriptor you can of course use the built-in property:

for i, header in enumerate(next(reader)):
setattr(Row, header, property(lambda self, i=i: self._row[i]))

In many cases you don't care about the specifics of the row class and use 
collections.namedtuple:


def rows(instream):
reader = csv.reader(instream, delimiter=";")
Row = collections.namedtuple("Row", next(reader))
return itertools.imap(Row._make, reader)


f = StringIO("a;b;c\n1;2;3\n4;5;6\n7;8;9\n")
row = next(rows(f))
print row.a
row.a = 42


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] question about descriptors

2015-11-07 Thread Alan Gauld

On 07/11/15 12:53, Albert-Jan Roskam wrote:

Ok, now to my question.

> I want to create a class with read-only attribute access

to the columns of a .csv file.


Can you clarify what you mean by that?
The csvreader is by definition read only.
So is it the in-memory  model that you want read-only?
Except you don't really have an in-memory model that I can see?


E.g. when a file has a column named 'a', that column should

> be returned as list by using instance.a.

That appears to be a separate issue to whether the returned
list is read-only or not? As ever the issue of dynamically
naming variables at run time and then figuring out how to
access them later raises its head. Its hardly ever a good plan.


At first I thought I could do this with the builtin 'property'

> class, but I am not sure how.

To use property I think you'd need to know the names of your
columns in advance. (Or dynamically build your classes)


I now tried to use descriptors (__get__ and __set__),
which are also used by ' property'



In the " if __name__ == '__main__'" section, [a] is supposed

> to be a shorthand for == equivalent to [b].

I have no idea what you mean by that sentence?


class AttrAccess(object):

 def __init__(self, fileObj):
 self.__reader = csv.reader(fileObj, delimiter=";")
 self.__header = self.__reader.next()
 @property
 def header(self):
 return self.__header

 def __get_column(self, name):
 return [record[self.header.index(name)] for record in self.__reader]  
# generator expression might be better here.


You should only get the index once otherwise it could add a lot of time 
for a long file(especially if there were a lot of columns)


 def __get_column(self, name):
idx = self.header.index(name)
return [record[idx] for record in self.__reader]


 def __get__(self, obj, objtype=type):
 print "__get__ called"
 return self.__get_column(obj)
 #return getattr(self, obj)

 def __set__(self, obj, val):
 raise AttributeError("Can't set attribute")


If you want everything read-only should this not be __setattr__()?


--
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor