最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Properties vs. generic settergetter and descriptor - Stack Overflow

programmeradmin0浏览0评论

I can't seem to find a definitive answer on the matter and I guess the reason is because it depends on the situation.

a, b and c (and d, e, f... as only 3 attributes are listed in this example for simplicity purposes, which probably tells that the approach is wrong if a class needs that many attributes) are different attributes but have some similarities, which probably means that splitting them in other classes would be the better choice.

Which one of the following options is the most "Pythonic" or efficient when creating a class with multiple "private" attributes? Or would a different approach (e.g. grouping similar attributes in separate classes) be better? How'd that look if, let's say, b and c are the same "kind" of attribute? (e.g. b being an input path and c being an output path).

Multiple property decorators and setters:

class A:  # Too many properties and setters?

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        self._a = a

    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, value):
        self._b = b

    ...  # (same for c)

or multiple setters/getters with "private" attributes:

class A:  # Name mangling issues?

    def __init__(self, a, b, c):
        self.__a = a
        self.__b = b
        self.__c = c

    def set_a(self, a):
        self.__a = a

    def get_a(self):
        return self.__a

    def set_b(self, b):
        self.__b = b

    def get_b(self):
        return self.__b

    ...  # (same for c)

or generic setters/getters:

class A:  # Less cluttered, but less intuitive that attributes are meant to be "private"?
    
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def __getattr__(self, name: str):
        return self.__dict__[f"_{name}"]

    def __setattr__(self, name, value):
        self.__dict__[f"_{name}"] = value

or a Python Descriptor? (How'd that'd look like if b and c were to be moved to a separate class as they share some similarities?).

Thanks!

I can't seem to find a definitive answer on the matter and I guess the reason is because it depends on the situation.

a, b and c (and d, e, f... as only 3 attributes are listed in this example for simplicity purposes, which probably tells that the approach is wrong if a class needs that many attributes) are different attributes but have some similarities, which probably means that splitting them in other classes would be the better choice.

Which one of the following options is the most "Pythonic" or efficient when creating a class with multiple "private" attributes? Or would a different approach (e.g. grouping similar attributes in separate classes) be better? How'd that look if, let's say, b and c are the same "kind" of attribute? (e.g. b being an input path and c being an output path).

Multiple property decorators and setters:

class A:  # Too many properties and setters?

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        self._a = a

    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, value):
        self._b = b

    ...  # (same for c)

or multiple setters/getters with "private" attributes:

class A:  # Name mangling issues?

    def __init__(self, a, b, c):
        self.__a = a
        self.__b = b
        self.__c = c

    def set_a(self, a):
        self.__a = a

    def get_a(self):
        return self.__a

    def set_b(self, b):
        self.__b = b

    def get_b(self):
        return self.__b

    ...  # (same for c)

or generic setters/getters:

class A:  # Less cluttered, but less intuitive that attributes are meant to be "private"?
    
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def __getattr__(self, name: str):
        return self.__dict__[f"_{name}"]

    def __setattr__(self, name, value):
        self.__dict__[f"_{name}"] = value

or a Python Descriptor? (How'd that'd look like if b and c were to be moved to a separate class as they share some similarities?).

Thanks!

Share Improve this question edited Feb 1 at 15:53 joanis 12.3k23 gold badges37 silver badges48 bronze badges asked Feb 1 at 13:34 YoRHa_A2YoRHa_A2 371 silver badge6 bronze badges 12
  • 2 in this case @dataclass is probably the most pythonic – Wombatz Commented Feb 1 at 14:04
  • 1 None of the above if all you are doing is directly accessing the "private" attribute with no other logic. Just use public attributes. – chepner Commented Feb 1 at 14:12
  • As an aside, if you do use properties, even __init__ should use them instead of assigning directly to the private attribute. – chepner Commented Feb 1 at 14:12
  • For my team (and hence opinion) we typically use get/set methods when we anticipate the calls to be expensive. Here we would use properties though as the operations are not expensive. – JonSG Commented Feb 1 at 14:54
  • As written, none of these should be used, because your getters/setters don't do anything, so these should be normal attibutes – juanpa.arrivillaga Commented Feb 1 at 15:35
 |  Show 7 more comments

3 Answers 3

Reset to default 6

The most pythonic way to create a class that has a member is as follows

# adding type-hints is recommended
class A:
  def __init__(self, a: int):
    self.a: int = a

Now everyone uses it, but in a year you have a requirement that

when user sets a you need to notify all observers

Now getting and setting a needs to be done through a function. But doing that would break all users that rely on obj.a working. The solution is then to make a a property.

class A:
  def __init__(self, a: int):
    self._a: int = a
  
  @property
  def a(self) -> int:
    return self._a

  @a.setter
  def a(self, value: int):
    self._a = value
    self.notify_observers(self._a)

... # observers code here

Now uses of obj.a still work flawlessly.

  1. Don't add getters and setters unless you absolutely need to. private members usually don't need getters and setters.
  2. If you see a getter and setter that do nothing except get and set a member then this member should be a public member instead. And tell that java developer that python has properties, and he is not writing java code anymore. (this really happened before)

Other uses for properties are for read-only members or data that is stored in C objects that you cannot get a reference to, or is computed lazily, or validation, etc .... The important part is that you must have a reason to use getters and setters, and when you do need them then use properties, don't sprinkle them where they are not needed.

First of all: nothing is private in Python; if you try hard enough you can change the literal number 2 to mean three.

However, you can make editing attributes harder and clearly signal that they are not meant to be edited. There are several ways to achieve this. Below is one of my favorites. It's simple enough to implement for production code and gets the point across.

class MyClass:
    def __init__(self, secretval):
        object.__setattr__(self, "secret", secretval)  # bypass __setattr__

    # Custom __setattr__ to prevent setting private attributes
    def __setattr__(self, name, value):
        if name in ["secret"]:  # Check if it's a private attribute
            raise AttributeError("Can't touch this")
        else:
            object.__setattr__(self, name, value)

    # Custom __getattr__ to prevent reading private attributes
    def __getattribute__(self, name):
        if name in ["secret"]:  # Check if it's a private attribute
            raise AttributeError("Can't touch this")
        else:
            return object.__getattribute__(self, name)

myobject = MyClass("secret value") # valid usage
print(myobject.secret)  # raises error
myobject.secret = "Bob" # raises error

print(object.__getattribute__(myobject, "secret")) # this bypasses the check

You can also use other tools like name mangling or mapping proxies or even C level code to protect attributes but all of these can be bypassed given enough dedication.

I'll try to supplement the technical answers with a few thoughts from an OOP perspective:

  1. Getters and setters are somewhat inconsistent with the OOP paradigm. Because your objects start to represent data structures. Some thoughts can be read in this article.
  2. The correct way to encapsulate objects is to use a constructor (__init__).
  3. Our task is how to decompose classes so that each class has a few fields. The cohesion indicator can be used to check whether we are going in the right direction. To calculate it, we can use the LCOM metrics.
发布评论

评论列表(0)

  1. 暂无评论