Repository: python/mypy

Issue:
Double underscore name mangling in classes to allow independent types
Mypy should perform name mangling in classes.



Discussion:
A simple example, exposing the behavior:
```
class foo:
    def __bar(self) -> None:
        print("xD")

foo()._foo__bar()
```

```
/tmp/test.py:5: error: "foo" has no attribute "_foo__bar"
```

That looks intentional. You shouldn't be accessing private attributes like that.
@gvanrossum in production code not. But in tests you sometimes need to access the internal state of the object to validate what happened.
You can use `getattr(foo(), '_foo__bar')()` in tests. The main issue is allowing multiple classes in a class hierarchy use the same `__foo` attribute with independent types.
@JukkaL, yes, but it's less readable 
The fact of the matter is that `foo` does have the attribute `_foo__bar` and we all know it. The error message is a blatant lie and a source of confusion.
Thank you for your encouragement. You should just use `# type: ignore` and move on. If you do this a lot, clearly something is wrong in your application or API design and mypy should not endorse that.
@gvanrossum 
> You should just use ``# type: ignore`` and move on. If you do this a lot, clearly something is wrong in your application or API design and mypy should not endorse that.

As @JukkaL mentioned the original issue is _not_ about allowing ``Foo()._Foo__foo``, this issue is about allowing things like:
```python
class B:
    __foo: int
class C(B):
    __foo: str
```
which is type safe but currently prohibited.
Ouch, sorry, I read too quickly.
I'm looking at this one.

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -1504,6 +1504,9 @@ def check_override(self, override: FunctionLike, original: FunctionLike,
             if original_class_or_static and not override_class_or_static:
                 fail = True
 
+        if is_private(name):
+            fail = False
+
         if fail:
             emitted_msg = False
             if (isinstance(override, CallableType) and
@@ -1649,6 +1652,8 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
             # Normal checks for attribute compatibility should catch any problems elsewhere.
             non_overridden_attrs = base.names.keys() - typ.names.keys()
             for name in non_overridden_attrs:
+                if is_private(name):
+                    continue
                 for base2 in mro[i + 1:]:
                     # We only need to check compatibility of attributes from classes not
                     # in a subclass relationship. For subclasses, normal (single inheritance)
@@ -1940,6 +1945,9 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, lvalue_type: Optional[
                 if lvalue_node.name() == "__slots__" and base.fullname() != "builtins.object":
                     continue
 
+                if is_private(lvalue_node.name()):
+                    continue
+
                 base_type, base_node = self.lvalue_type_from_base(lvalue_node, base)
 
                 if base_type:
@@ -4396,3 +4404,8 @@ def is_subtype_no_promote(left: Type, right: Type) -> bool:
 
 def is_overlapping_types_no_promote(left: Type, right: Type) -> bool:
     return is_overlapping_types(left, right, ignore_promotions=True)
+
+
+def is_private(node_name: str) -> bool:
+    """Check if node is private to class definition."""
+    return node_name.startswith('__') and not node_name.endswith('__')


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -5899,3 +5899,34 @@ class A:
 class B:
     @dec  # E: Unsupported decorated constructor type
     def __new__(cls, x: int) -> B: ...
+
+[case testIgnorePrivateAttributesTypeCheck]
+class B:
+    __foo_: int
+class C(B):
+    __foo_: str
+[out]
+
+[case testIgnorePrivateMethodsTypeCheck]
+class B:
+    def __foo_(self) -> int: ...
+class C(B):
+    def __foo_(self) -> str: ...
+[out]
+
+[case testCheckForPrivateMethodsWhenPublicCheck]
+class B:
+    __foo__: int
+class C(B):
+    __foo__: str
+[out]
+main:4: error: Incompatible types in assignment (expression has type "str", base class "B" defined the type as "int")
+
+[case testIgnorePrivateMethodsTypeCheck]
+class A:
+    def __foo_(self) -> int: ...
+class B:
+    def __foo_(self) -> str: ...
+
+class C(A, B): pass
+[out]