0

In pursuit of creating something equivalent to the struct of Matlab in Python, I want to create a class that is designed so that when an instance of it is given a new attribute that the class as a whole doesn't yet have, the class is automatically declared to have an attribute of that name (but dynamic value).

For example, if I have defined the class color in this manner, but having no attributes to start with, then I could do the following:

>> red = color()
>> blue = color()
>> blue.temperature
   AttributeError: type object 'color' has no attribute 'temperature'
>> red.temperature = 'hot'
>> blue.temperature
   blue.temperature = ''
>> blue.temperature = 'cool'

Is there a way to hack the process of adding another attribute and add to it a command like cls.x = '', with x being a variable for the name of the attribute added to the instance?

Post169
  • 668
  • 8
  • 26
  • Instance attributes and class attributes are two different things. – juanpa.arrivillaga Feb 08 '19 at 23:07
  • 1
    Don't try it. It does not fit with the python oop model. – hpaulj Feb 09 '19 at 02:22
  • @juanpa.arrivillaga, sounds like he wants all other existing instances to automatically get this new attribute (with some default value?). I don't know how MATLAB does it; I know the 1st attempt at adding class to MATLAB was build on top of the `struct` data structure, though I believe the current version is major reworking of that. It certainly doesn't make sense in the Python class system. – hpaulj Feb 09 '19 at 02:31
  • 1
    @juanpa.arrivillaga, I misread the question. He wants to emulate a MATLAB `struct`. `scipy.io.loadmat` translates a `struct` into a `numpy` structured/record array, with a dtype field corresponding to each attribute (or field) in the MATLAB. In MATLAB one can add an attribute to all 'records' simply by defining a value for one record. – hpaulj Feb 09 '19 at 07:51
  • 1
    You could model the `struct` as a list of dictionaries, and then write a small set of utility function(s) that ensure that when a key is added to one dictionary in the list, that key with some default value is added to all the other dictionaries. Once those work to your satisfaction, you could look to packaging that as a Python class. – hpaulj Feb 09 '19 at 07:55
  • A python class does not (normally) keep a record of its instances. And an instance can't modify its own class definition. – hpaulj Feb 09 '19 at 08:19
  • As shown in my other comments my knowledge of MATLAB is hit-or-miss. It might help if you created a demo `struct` to get a better idea of what you want to emulate. You might also experiment with the `.mat` transfer as I demonstrate at [How to save a list of python dictionaries as an array of matlab structured arrays?](https://stackoverflow.com/questions/54598286) – hpaulj Feb 09 '19 at 17:18
  • @hpaulj, I'm not seeking to transfer variables between languages. I mostly want the ability to dynamically build up a class as I add to instances of it, and use it in code that I might need to change a few times without worrying about redefining the class by hand each time. – Post169 Feb 09 '19 at 19:19
  • 1
    It's possible to add methods to a class as you go along, and class variables. But instance variables are kept in its own `.__dict__` dictionary, which isn't shared with the class or other instances. – hpaulj Feb 09 '19 at 19:37

2 Answers2

1

the setattr method

x = "temperature"
setattr(red,x,"HOT")

I think is what you are asking for

but maybe what you want is to overload the __setattr__ and __getattr__ methods of your color class

class color:
     attrs = {}
     def __getattr__(self,item):
         if item in self.attrs:
            return self.attrs[item]
         return ""
     def __setattr__(self,attr,value):
         self.attrs[attr] = value

c = color()
print(repr(c.hello))
c.hello = 5
print(repr(c.hello))
print(repr(c.temperature))
x = 'temperature'
setattr(c,x,"HOT")
print(repr(c.temperature))
Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • This does give the new attribute to all instances, but I was hoping to have a sequence of events like 1. Assign a value to an attribute that did not previously exist in the class before, for a single instance of that class 2. Create that attribute for the whole class, with a default value 3. Set that attribute to the specified value for the instance dealt with – Post169 Feb 09 '19 at 18:07
  • I've modified the code so that there are two dictionaries, clsAttrs (like your attrs, belonging to the class) and insAttrs (belonging to the instance). But now getattr needs to check whether something is in the instance attribute dictionary, which seems impossible without calling itself, getattr. I searched online, found getattribute, but that seems not to make a difference. Should I start another questions? – Post169 Feb 12 '19 at 16:14
1

To take an example from Octave https://octave.org/doc/v4.4.1/Structure-Arrays.html

Make a structure array:

>> x(1).a = "string1";
>> x(2).a = "string2";
>> x(1).b = 1;
>> x(2).b = 2;
>>
>> x
x =

  1x2 struct array containing the fields:

    a
    b

If I add a field to one entry, a default value is added or defined for the other:

>> x(1).c = 'red'
x =

  1x2 struct array containing the fields:

    a
    b
    c

>> x(2)
ans =

  scalar structure containing the fields:

    a = string2
    b =  2
    c = [](0x0)

>> save -7 struct1.mat x

In numpy

In [549]: dat = io.loadmat('struct1.mat')
In [550]: dat
Out[550]: 
{'__header__': b'MATLAB 5.0 MAT-file, written by Octave 4.2.2, 2019-02-09 18:42:35 UTC',
 '__version__': '1.0',
 '__globals__': [],
 'x': ...

In [551]: dat['x']
Out[551]: 
array([[(array(['string1'], dtype='<U7'), array([[1.]]), array(['red'], dtype='<U3')),
        (array(['string2'], dtype='<U7'), array([[2.]]), array([], shape=(0, 0), dtype=float64))]],
      dtype=[('a', 'O'), ('b', 'O'), ('c', 'O')])
In [552]: _.shape
Out[552]: (1, 2)

The struct has been translated into a structured numpy array, with the same shape as the Octave size(x). Each struct field is an object dtype field in dat.

In contrast to Octave/MATLAB we can't add a field to dat['x'] in-place. I think there's a function in import numpy.lib.recfunctions as rf that can add a field, with various forms of masking or default for undefined values, but that will make a new array. With some work I could do that from scratch.

In [560]: x1 = rf.append_fields(x, 'd', [10.0])
In [561]: x1
Out[561]: 
masked_array(data=[(array(['string1'], dtype='<U7'), array([[1.]]), array(['red'], dtype='<U3'), 10.0),
                   (array(['string2'], dtype='<U7'), array([[2.]]), array([], shape=(0, 0), dtype=float64), --)],
             mask=[(False, False, False, False),
                   (False, False, False,  True)],
       fill_value=('?', '?', '?', 1.e+20),
            dtype=[('a', 'O'), ('b', 'O'), ('c', 'O'), ('d', '<f8')])
In [562]: x1['d']
Out[562]: 
masked_array(data=[10.0, --],
             mask=[False,  True],
       fill_value=1e+20)

This kind of action does not fit the Python class system well. A class doesn't normally keep track of its instances. And once defined a class is not normally amended. It is possible to maintain a list of instances, and it is possible to add methods to an existing class, but that's not common practice.

hpaulj
  • 221,503
  • 14
  • 230
  • 353