3

How can you create a class attribute that will always contain the same list? It should always reference the same list although the contents of the list can be changed.

The obvious solution is to use property.

class Table(list):
    def filter(kwargs):
    """Filter code goes here."""


class db:
    _table = Table([1, 2])
    table = property(lambda self: self._table)


db.table.append(3)

I would have assumed that db.table should return a list and that you should be able to append to this list. But no, this code throws an exception:

AttributeError: 'property' object has no attribute 'append'

How do you create a attribute that always refers to the same list?

ILLUSTRATION:

db.table = [x for x in db.table if x > 2]
db.filter(3)    # This filter method got lost when reassigning the table in the previous line.
ChaimG
  • 7,024
  • 4
  • 38
  • 46
  • 2
    What's wrong with `class db: table = [1, 2]`? – Aran-Fey May 14 '18 at 14:28
  • 1
    `db` is a class, and you want an instance. – bereal May 14 '18 at 14:28
  • @Alan-Frey I am not actually using a list type, but a custom subclass of list with added functionality. If the list gets inadvertently changed then the functionality would be lost. – ChaimG May 14 '18 at 14:29
  • 1
    e.g. you should do `db().table.append(3)` to create an instance.... – Corley Brigman May 14 '18 at 14:30
  • @bereal I only plan on using one instance of this class. Normally, in such a case I want a class not an instance. – ChaimG May 14 '18 at 14:31
  • @bereal Is an instance the only way to do it in this case? – ChaimG May 14 '18 at 14:32
  • 1
    I don't see how the type of the class attribute matters. It'll always be the same instance, unless you reassign it. – Aran-Fey May 14 '18 at 14:33
  • 1
    @Alan-Frey Exactly. I want to prevent inadvertent reassignment. I have come across bugs where it is being reassigned via the oh so useful list comprehension inadvertently, which breaks the code later in a different module. – ChaimG May 14 '18 at 14:34
  • @ChaimG if you want it to be shared between instances, just make it a class attribute as Aran-Fey suggests. If you want to prohibit its assignment, that may require some metaclass magic, I don't know if it's worth it. – bereal May 14 '18 at 14:35
  • 2
    You can either define the property in the metaclass as shown [here](https://stackoverflow.com/questions/5189699/how-to-make-a-class-property) or - and this is probably the better solution - make `db` an instance rather than a class. – Aran-Fey May 14 '18 at 14:37
  • I edited the example, adding the Table class. – ChaimG May 14 '18 at 14:39
  • Possible duplicate of [Raising an exception on updating a 'constant' attribute in python](https://stackoverflow.com/questions/1358711/raising-an-exception-on-updating-a-constant-attribute-in-python) – quamrana May 14 '18 at 14:50
  • @ChaimG even if you don't plan on using more than one instance of a class, most of the times using an instance is the simplest solution, and the one that will makes for the most testable and maintainable code. Been here, done that, thought I was smart, now I know better... – bruno desthuilliers May 14 '18 at 15:34

3 Answers3

2

Here a solution using class property, using this answers: How to make a class property?

class ClassPropertyDescriptor(object):

    def __init__(self, fget, fset=None):
        self.fget = fget
        self.fset = fset

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        return self.fget.__get__(obj, klass)()

    def __set__(self, obj, value):
        if not self.fset:
            raise AttributeError("can't set attribute")
        type_ = type(obj)
        return self.fset.__get__(obj, type_)(value)

    def setter(self, func):
        if not isinstance(func, (classmethod, staticmethod)):
            func = classmethod(func)
        self.fset = func
        return self

def classproperty(func):
    if not isinstance(func, (classmethod, staticmethod)):
        func = classmethod(func)

    return ClassPropertyDescriptor(func)


class db(object):
        _table = [1,2]

        @classproperty
        def table(cls):
                return list(cls._table)



t = db.table
t.append(3)
print t  # [1, 2, 3]
print db.table  # [1, 2]
Yassine Faris
  • 951
  • 6
  • 26
0

There is no immutable functinality that can be added. Hoewever I would offer to create a class that stores your list. And make the list immutable defining only setters and making the list private. And for the changing of the list elements you can create methods.

You would have something like

class FinalList:

list

init(list)

getList()

append(element)

delete(element)

...etc

Superluminal
  • 947
  • 10
  • 23
0

For comparison, here is the code written as an instance of a class.

class Table(list):
    def filter(kwargs):
        """Filter code goes here."""


class DB:
    def __init__(self):
        DB._table = Table([1, 2])

    table = property(lambda self: DB._table)


db = DB()

db.table.append(3)
print(db.table)
db.table = [2]

Outputs:

[1, 2, 3]
AttributeError: can't set attribute

Perfect. This is much simpler than creating class properties.

Technically, we are using a class instance which makes the code so much simpler. However, by storing the Table in the class itself, we ensure that all instances share the same tables.

ChaimG
  • 7,024
  • 4
  • 38
  • 46