How should I expose read-only fields from Python classes?

前端 未结 5 1882
野趣味
野趣味 2020-12-28 13:51

I have many different small classes which have a few fields each, e.g. this:

class Article:
    def __init__(self, name, available):
        self.name = name         


        
相关标签:
5条回答
  • 2020-12-28 14:12

    Based in the Chris answer, but arguably more pythonic:

    def ro_property(field):
        return property(lambda self : self.__dict__[field])
    
    class Article(object):
        name = ro_property('_name')
    
        def __init__(self):
            self._name = "banana"
    

    If trying to modify the property it will raise an AttributeError.

    a = Article()
    print a.name # -> 'banana'
    a.name = 'apple' # -> AttributeError: can't set attribute
    

    UPDATE: About your updated answer, the (little) problem I see is that you are modifying the definition of the property in the class every time you create an instance. And I don't think that is such a good idea. That's why I put the ro_property call outside of the __init__ function

    What about?:

    def ro_property(name):
        def ro_property_decorator(c):
            setattr(c, name, property(lambda o: o.__dict__["_" + name]))
            return c
        return ro_property_decorator
    
    @ro_property('name')
    @ro_property('other')
    class Article(object):
        def __init__(self, name):
            self._name = name
            self._other = "foo"
    
    a = Article("banana")
    print a.name # -> 'banana'
    a.name = 'apple' # -> AttributeError: can't set attribute
    

    Class decorators are fancy!

    0 讨论(0)
  • 2020-12-28 14:16

    It should be noted that it's always possible to modify attributes of an object in Python - there are no truly private variables in Python. It's just that some approaches make it a bit harder. But a determined coder can always lookup and modify the value of an attribute. For example, I can always modify your __setattr__ if I want to...

    For more information, see Section 9.6 of The Python Tutorial. Python uses name mangling when attributes are prefixed with __ so the actual name at runtime is different but you could still derive what that name at runtime is (and thus modify the attribute).

    0 讨论(0)
  • 2020-12-28 14:18

    I would use property as a decorator to manage your getter for name (see the example for the class Parrot in the documentation). Use, for example, something like:

    class Article(object):
        def __init__(self, name, available):
            self._name = name
            self.available = available
    
        @property
        def name(self):
            return self._name
    

    If you do not define the setter for the name property (using the decorator x.setter around a function) this throws an AttributeError when you try and reset name.

    Note: You have to use Python's new-style classes (i.e. in Python 2.6 you have to inherit from object) for properties to work correctly. This is not the case according to @SvenMarnach.

    0 讨论(0)
  • 2020-12-28 14:29

    As pointed out in other answers, using a property is the way to go for read-only attributes. The solution in Chris' answer is the cleanest one: It uses the property() built-in in a straight-forward, simple way. Everyone familiar with Python will recognize this pattern, and there's no domain-specific voodoo happening.

    If you don't like that every property needs three lines to define, here's another straight-forward way:

    from operator import attrgetter
    
    class Article(object):
        def __init__(self, name, available):
            self._name = name
            self.available = available
        name = property(attrgetter("_name"))
    

    Generally, I don't like defining domain-specific functions to do something that can be done easily enough with standard tools. Reading code is so much easier if you don't have to get used to all the project-specific stuff first.

    0 讨论(0)
  • 2020-12-28 14:34

    I would stick with your option 1 but refined it to use Python property:

    class Article
        def get_name(self):
            return self.__name
    
        name = property(get_name)
    
    0 讨论(0)
提交回复
热议问题