Dynamic class creation with type and __slots__?

2019-08-08 16:20发布

问题:

When creating result objects within a class, is it possible to use __slots__ in this example? I thought I could get it to work by passing '__slots__' into the dictionary for the third argument to type:

class GeocodeResult(object):
    """class to handle Reverse Geocode Result"""

    __slots__ = ['geometry', 'response', 'spatialReference',
                'candidates', 'locations', 'address', 'type', 'results']

    def __init__(self, res_dict, geo_type):
        RequestError(res_dict)
        self.response = res_dict
        self.type = 'esri_' + geo_type
        self.spatialReference = None
        self.candidates = []
        self.locations = []
        self.address = []
        if 'spatialReference' in self.response:
            self.spatialReference = self.response['spatialReference']

        # more stuff

    @property
    def results(self):
        results = []
        for result in self.address + self.candidates + self.locations:
            result['__slots__'] = ['address', 'score', 'location', 'attributes']
            results.append(type('GeocodeResult.result', (object,), result))
        return results

    def __len__(self):
        """get length of results"""
        return len(self.results)

For the results property, I want to build a list of small, light-weight objects with 4 properties: ['address', 'score', 'location', 'attributes']

The resulting object is created, and I can even get at the slots, but it is still keeping the __dict__. Since there could potentially be hundreds of result objects, I want only the four properties listed above to save space.

Example:

>>> rev = GeocodeResult(r, 'reverseGeocode')
>>> result = rev.results[0]
>>> result.__slots__
['address', 'score', 'location', 'attributes']
>>> hasattr(result, '__dict__')
True
>>> 

Is there a better way of doing this? Or do I have to define an explicit class to handle this?

回答1:

No, I don't think there's any way to use the three-parameter form of type to create an object with __slots__; per the docs:

the dict dictionary [third parameter] is the namespace containing definitions for class body and becomes the __dict__ attribute.

I'm not sure you need to, though; why not define the class with __slots__ once, as they all appear to be the same, and just return a list of instances? That said, I would be inclined to use a collections.namedtuple for this; they're pretty lightweight and still give access to their contents by attribute:

class GeocodeResult(object):
    """class to handle Reverse Geocode Result"""

    ...

    Result = namedtuple(
        'Result', 
        'address score location attributes',
    )

    @property
    def results(self):
        results = []
        for result in self.address + self.candidates + self.locations:
            results.append(self.Result(...))  # pass in the four relevant attributes
        return result

    ...

You could simplify the property a bit with a list comprehension, too.



回答2:

It's totally possible to dynamically create classes with slots:

>>> C = type('C', (), {'__slots__': ('a', 'b')})
>>> C.a
<member 'a' of 'C' objects>
>>> dir(C())
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'a', 'b']
>>> vars(C())
Traceback (most recent call last):
  ...
TypeError: vars() argument must have __dict__ attribute

Works in Python 3 and 2.

You're seeing hasattr(result, '__dict__') evaluate True in your example because the list returned by GecodeResult.results is a list of types, not a list of instances. If you were to say result().__dict__, you would get an AttributeError.

(Also worth noting: each type in that list shares the name "GeocodeResult.result", but they are not the same type! results[0].__class__ == results[1].__class__ is False.)

As jonrsharpe noted, it's better to just define the type once and reuse it, and namedtuple is perfect for the job, so stick with that :)