2

According to the official documentation, "data attributes override method attributes with the same name". However, I found that to be incorrect.

class C:
    x = 111
    def x(self):
        print('I am x')
c = C()
print(c.x)

The print statement in the code above shows c.x being a method, not the data attribute assigned to 111. Thus, this code indicates that data attributes do not necessarily override method attributes with the same name and the documentation is wrong. Can anyone confirm my finding?

P.S. I tried the code in both Python 3.5 and Python 2.7 and obtained the same result.

Isaac To
  • 679
  • 3
  • 17
  • 3
    variables can shadow other variables ... methods and attributes and classes and functions are all really just variables – Joran Beasley Aug 18 '16 at 19:06

5 Answers5

5

I guess the tutorial is unfortunately (because ambiguously) phrased and by

[d]ata attributes override method attributes with the same name

it actually means "Data attributes override previously assigned/defined method attributes of the same name and vice versa: method attributes override previously assigned/defined data attributes of the same name."

"Duh", you might think "data attributes also override previously assigned data attributes of the same name, so what's the big deal? Why is this even mentioned?" Assigning and re-assigning (called "overriding" in the cited tutorial) to variables (whether called "attributes" of something or not) is after all one of the prototypical features of an imperative programming language.

Well, let me introduce you to

Namespaces

Python classes are namespaces. So what the tutorial might try to tell us here is that data attributes and method attributes within a class share a namespace.

This isn't the case for attributes of different classes, though. If a class inherits from another class, it has access to its parent's names. If a name is reused for method definition or data assignment within the inheriting class, the parent class keeps the original values. In the child class they are merely temporarily shadowed. If you remove the name from the child class, it, too, will again provide access to the parent's attribute of the same name:

class A:
        x = 111

class B1(A):
        x = 123  # Shadows A.x

assert B1.x == 123

del B1.x            # But after removing B1's own x attribute ...
assert B1.x == 111  # ... B1.x is just an alias to A.x !


# Shadowing can happen at any time:

class B2(A):
        pass

assert B2.x == A.x == 111

B2.x = 5  # shadowing attributes can also be added after the class definition

assert B2.x == 5
assert A.x == 111

del B2.x
assert B2.x == A.x == 111

Contrast this with re-definition a.k.a. re-assignment (or "overriding" as the tutorial calls it):

class C:
        x = 555
        def x(self):
                print('I am x')

C().x()  # outputs "I am x"

del C.x
print(C.x) # AttributeError: 'C' object has no attribute 'x'

Method C.x() didn't temporarily shadow data attribute C.x. It replaced it, so when we delete the method, x is missing completely within C, rather than the data attribute re-appearing under that name.

More Namespaces

Instantiation adds another namespace and thus another chance for shadowing:

a = A()
assert a.x == 111  # instance namespace includes class namespace

a.x = 1000
assert a.x == 1000
assert A.x == 111  # class attribute unchanged

del a.x
assert a.x == 111  # sees A.x again

In fact, all (nested) namespaces in Python work that way: packages, modules, classes, functions and methods, instance objects, inner classes, nested functions ...

When reading a variable, the namespace hierarchy is walked bottom-up until the name is found. (Reading here means finding the value (object, function/method or built-in) to which the variable's name is bound. If the value is mutable, this can also be used to change the value.)

On the other hand, when setting (defining or redefining) a variable, a name of the current namespace is used: Rebound to the new value if the name already exists in that very namespace (rather than only being included there from another namespace) or a newly created name if it didn't exist before.

das-g
  • 9,718
  • 4
  • 38
  • 80
  • 2
    There is also another way of overriding/shadowing that may happen: Method definitions end up in the class-dict (`obj.__class__.__dict__`), whereas instance variables end up in the instance-dict (`obj.__dict__`). Attribute lookup happens in the instance-dict first, so it may shadow a method with the same name, even though that method is still in the class-dict (no actual re-assignment). – Lukas Graf Aug 18 '16 at 19:56
  • 1
    @LukasGraf good point. I've added a another section about that. – das-g Aug 18 '16 at 20:12
3

Attributes override methods and vise-versa. A rule of thumb is, the latter overrides the former. So if you do

class C:
    x = 111
    def x(self):
        print('I am x')
    x = 112

c = C()
print(c.x)

You will get

112
Kostas Pelelis
  • 1,322
  • 9
  • 11
  • Does that mean that you agree that the official documentation is wrong on this matter? – Isaac To Aug 18 '16 at 19:28
  • 2
    @candleindark that sentence in the documentation is at least very misleading. Like Kostas pointed out, a later assignment overrides the former. And *usually*, data attributes (instance variables) are first assigned in `__init__()`, which is why they get assigned later than method definitions, which are assigned when the class declaration is parsed. In your example you're using a **class** level attribute, which is why the "usually" doesn't hold true. – Lukas Graf Aug 18 '16 at 19:46
0

Maybe this would be easier to understand: Data attributes shadow method attributes with the same name;

ctc chen
  • 1,336
  • 1
  • 7
  • 7
0

Your example is trying to compare a shared class data attribute C.x with an instance method c.x(). Since c is an instance, it makes sense it gets the latter. Below, I use the c.__dict__ introspection tool to check if the data attribute with key/value pair exists, True or False, in an instance namespace:

inDict = (lambda a, b: a in b.__dict__.keys())

class C:
    x = 111

    def x(self):
        print('I am x')

c = C()
print("C.x() hides shared class attribute x = 111")
print(c.x, '\n', c.__class__, inDict('x', c))
c.x()
C.x

c.x = 222
print("Now, c.x is an instance attribute.")
print(c.x, '\n', c.__class__, inDict('x', c))

import sys
try:
    c.x()
except:
    print("c.x hides c.x() => except error: {0}".format(sys.exc_info()[1:2]))
print("Instead, call C.x() in class namespace by passing instance c as self")
C.x(c)

class B:
    def __init__(self):
        self.x = 111
    def x(self):
        print('I am x')

b = B()
print(b.x, '\n', b.__class__, inDict('x', b))

Running result:

C.x() hides shared class attribute x = 111
<bound method C.x of <__main__.C object at 0x00000212DF8086A0>> 
 <class '__main__.C'> False
I am x
Now, c.x is an instance attribute.
222 
 <class '__main__.C'> True
c.x hides c.x() => except error: (TypeError("'int' object is not callable"),)
Instead, call C.x() in class namespace by passing instance c as self
I am x
111 
 <class '__main__.B'> True

Process finished with exit code 0

As you can see, in the beginning c.x is a method as your code showed. However, once we assign c.x a value 222, it becomes a data attribute because the rule "data attributes override method attributes with the same name" applies now as both of them are in an instance namespace c. Lastly, in the new class B, we have both data and method attributes in the b instance namespace and it again shows the rule holds true.

Leon Chang
  • 669
  • 8
  • 12
  • A similar [example](https://stackoverflow.com/questions/28798781/differences-between-data-attributes-and-method-attributes) shows instance attributes usually take precedence over class attribute.and an instance (data) attribute hides the method with the same name. – Leon Chang Oct 11 '20 at 22:56
-1

You were missing the second definition of x, that is why it seemed like the official documentation was wrong.

tcwissemann
  • 1
  • 1
  • 4