[ Team LiB ] |
23.2 Pseudo-Private Class AttributesIn Part IV, we learned that every name assigned at the top level of a file is exported by a module. By default, the same holds for classes—data hiding is a convention, and clients may fetch or change any class or instance attribute they like. In fact, attributes are all "public" and "virtual" in C++ terms; they're all accessible everywhere and all looked up dynamically at runtime.[1]
That's still true today. However, Python also includes the notion of name "mangling" (i.e., expansion), to localize some names in classes. This is sometimes misleadingly called private attributes—really, it's just a way to localize a name to the class that created it, and does not prevent access by code outside the class. That is, this feature is mostly intended to avoid namespace collisions in instances, not to restrict access to names in general. Pseudo-private names are an advanced feature, entirely optional, and probably won't be very useful until you start writing large class hierarchies in multi-programmer projects. But because you may see this feature in other people's code, you need to be somewhat aware of it even if you don't use it in your own. 23.2.1 Name Mangling OverviewHere's how name mangling works. Names inside a class statement that start with two underscores (and don't end with two underscores) are automatically expanded to include the name of the enclosing class. For instance, a name like __X within a class named Spam is changed to _Spam__X automatically: a single underscore, the enclosing class's name, and the rest of the original name. Because the modified name is prefixed with the name of the enclosing class, it's somewhat unique; it won't clash with similar names created by other classes in a hierarchy. Name mangling happens only in class statements and only for names you write with two leading underscores. Within a class, though, it happens to every name preceded with double underscores wherever they appear. This includes both method names and instance attributes. For example, an instance attribute reference self.__X is transformed to self._Spam__X. Since more than one class may add attributes to an instance, this mangling helps avoid clashes; but we need to move on to an example to see how. 23.2.2 Why Use Pseudo-Private Attributes?The problem that the pseudo-private attribute feature is meant to alleviate has to do with the way instance attributes are stored. In Python, all instance attributes wind up in the single instance object at the bottom of the class tree. This is very different from the C++ model, where each class gets its own space for data members it defines. Within a class method in Python, whenever a method assigns to a self attribute (e.g., self.attr=value), it changes or creates an attribute in the instance (inheritance search only happens on reference, not assignment). Because this is true even if multiple classes in a hierarchy assign to the same attribute, collisions are possible. For example, suppose that when a programmer codes a class, she assumes that she owns the attribute name X in the instance. In this class's methods, the name is set and later fetched: class C1: def meth1(self): self.X = 88 # Assume X is mine. def meth2(self): print self.X Suppose further that another programmer, working in isolation, makes the same assumption in a class that he codes: class C2: def metha(self): self.X = 99 # Me too def methb(self): print self.X Both of these classes work by themselves. The problem arises if these two classes are ever mixed together in the same class tree: class C3(C1, C2): ... I = C3( ) # Only 1 X in I! Now, the value that each class will get back when it says self.X depends on which class assigned it last. Because all assignments to self.X refer to the same single instance, there is only one X attribute—I.X, no matter how many classes use that attribute name. To guarantee that an attribute belongs to the class that uses it, prefix the name with double underscores everywhere it is used in the class, as in this file, private.py: class C1: def meth1(self): self.__X = 88 # Now X is mine. def meth2(self): print self.__X # Becomes _C1__X in I class C2: def metha(self): self.__X = 99 # Me too def methb(self): print self.__X # Becomes _C2__X in I class C3(C1, C2): pass I = C3( ) # Two X names in I I.meth1( ); I.metha( ) print I.__dict__ I.meth2( ); I.methb( ) When thus prefixed, the X attributes are expanded to include the name of the class, before being added to the instance. If you run a dir call on I or inspect its namespace dictionary after the attributes have been assigned, you see the expanded names: _C1__X and _C2__X, but not X. Because the expansion makes the names unique within the instance, the class coders can assume they truly own any names that they prefix with two underscores: % python private.py {'_C2__X': 99, '_C1__X': 88} 88 99 This trick can avoid potential name collisions in the instance, but note that it is not true privacy at all. If you know the name of the enclosing class, you can still access these attributes anywhere you have a reference to the instance, by using the fully expended name (e.g., I._C1__X=77). On the other hand, this feature makes it less likely that you will accidentally step on a class's names. We should note that this feature tends to become more useful for larger, multi-programmer projects, and then only for selected names. That is, don't clutter your code unnecessarily; only use this feature for names that truly need to be controlled by a single class. For simpler programs, it's probably overkill. |
[ Team LiB ] |