Python Class Attributes: An Overly Thorough Guide


In a present cellphone show display, I decided to utilize a class attribute in my implementation of a positive Python API. My interviewer challenged me, questioning whether or not or not my code was syntactically legit, when it was executed, and so forth. In actuality, I wasn’t constructive of the options myself. So I did some digging.

Editor’s bear in mind: This article was updated on 11/29/22 by our editorial crew. It has been modified to include present sources and to align with our current editorial necessities.

I not too way back had a programming interview cellphone show display throughout which we used a collaborative textual content material editor and it acquired me fascinated with Python class attributes.

I was requested to implement a positive API and chosen to take motion in Python. Abstracting away the problem assertion, let’s say I needed a class whose conditions saved some information and some other_data.

I took a deep breath and commenced typing. After a few strains, I had one factor like this:

    class Service(object):
        information = []

        def __init__(self, other_data):
            self.other_data = other_data
        ...

My interviewer stopped me:

  • Interviewer: “That line data = []. I don’t think that’s valid Python.”

  • Me: “I’m pretty sure it is. It’s just setting a default value for the instance attribute.”

  • Interviewer: “When does that code get executed?”

  • Me: “I’m not really sure. I’ll just fix it up to avoid confusion.”

For reference, and to supply you an considered what I was going for, proper right here’s how I amended the code:

    class Service(object):

        def __init__(self, other_data):
            self.information = []
            self.other_data = other_data
        ...

As it appears, we have now been every flawed. The precise reply lay in understanding the excellence between Python class attributes and Python event attributes.

Note: If you might need an expert cope with on Python class attributes, you’ll skip ahead to utilize situations.

Python Class Attributes

My interviewer was flawed in that the above code is syntactically legit.

I was flawed in that the code isn’t setting a “default value” for the event attribute. Instead, it’s defining information as a class attribute with value [].

In my experience, Python class attributes are a topic that many people know one factor about, nonetheless few understand completely.

Python Class Variables vs. Instance Variables: What’s the Difference?

A Python class attribute is an attribute of the class (spherical, I do know), comparatively than an attribute of an event of a class.

Let’s use a Python class occasion as an example the excellence. Here, class_var is a class attribute, and i_var is an event attribute:

{:lang='python'}
    class MyClass(object):
        class_var = 1

        def __init__(self, i_var):
            self.i_var = i_var

Note that every one conditions of the class have entry to class_var, and that it’ll even be accessed as a property of the class itself:

{:lang='python'}
    foo = MyClass(2)
    bar = MyClass(3)

    foo.class_var, foo.i_var
    ## 1, 2
    bar.class_var, bar.i_var
    ## 1, 3
    MyClass.class_var ## <— This is important
    ## 1

For Java or C++ programmers, the class attribute is analogous—nonetheless not an an identical—to the static member. We’ll see how they differ later.

Python Class Properties vs. Instance Namespaces

To understand what’s going down proper right here, let’s converse briefly about Python namespaces.

A namespace is a mapping from names to issues, with the property that there is zero relation between names in quite a few namespaces. They’re typically carried out as Python dictionaries, although that’s abstracted away.

Depending on the context, you would need to entry a namespace using dot syntax (e.g., object.name_from_objects_namespace) or as an space variable (e.g., object_from_namespace). As a concrete occasion:

{:lang='python'}
    class MyClass(object):
        ## No need for dot syntax
        class_var = 1

        def __init__(self, i_var):
            self.i_var = i_var

    ## Need dot syntax as we’ve left scope of sophistication namespace
    MyClass.class_var
    ## 1

Python classes and conditions of classes each have their very personal distinct namespaces represented by the pre-defined attributes MyClass.__dict__ and instance_of_MyClass.__dict__, respectively.

When you try to entry Python attributes from an event of a class, it first seems at its event namespace. If it finds the attribute, it returns the associated value. If not, it then seems throughout the class namespace and returns the attribute (if it’s present, in every other case throwing an error). For occasion:

{:lang='python'}
    foo = MyClass(2)

    ## Finds i_var in foo’s event namespace
    foo.i_var
    ## 2

    ## Doesn’t uncover class_var in event namespace…
    ## So seems at school namespace (MyClass.__dict__)
    foo.class_var
    ## 1

The event namespace takes precedence over the class namespace: If there is a Python attribute with the an identical determine in every, the event namespace will most likely be checked first and its value returned. Here’s a simplified version of the code for attribute lookup:

{:lang='python'}
    def instlookup(inst, determine):
        ## simplified algorithm
        if inst.__dict__.has_key(determine):
            return inst.__dict__[name]
        else:
            return inst.__class__.__dict__[name]

And, in seen type:

attribute lookup in visual form

How Python Class Attributes Handle Assignment

With this in ideas, we’ll make sense of how class attributes cope with challenge:

At the namespace stage we’re setting MyClass.__dict__['class_var'] = 2. (Note: This isn’t the exact code which is likely to be setattr(MyClass, 'class_var', 2) as __dict__ returns a dictproxy, an immutable wrapper that forestalls direct challenge, however it helps for demonstration’s sake). Then, as soon as we entry foo.class_var, class_var has a model new value throughout the class namespace and thus 2 is returned.

  • If a Python class variable is about by accessing an event, it’s going to override the value only for that event. This principally overrides the class variable and turns it into an event variable accessible intuitively only for that event. For occasion:

    foo = MyClass(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    MyClass.class_var
    ## 1
    

At the namespace stage we’re together with the class_var attribute to foo.__dict__, so as soon as we seek for foo.class_var, we return 2. Meanwhile, completely different conditions of MyClass will not have class_var of their event namespaces, so that they proceed to go looking out class_var in MyClass.__dict__ and thus return 1.

Mutability

What in case your class attribute has a mutable type? You can manipulate the class attribute by accessing it via a specific event and, in flip, end up manipulating the referenced object that every one conditions are accessing (as recognized by Timothy Wiseman).

Let’s return to the Service I outlined earlier and see how my use of a class variable might need led to points down the road:

{:lang='python'}
    class Service(object):
        information = []

        def __init__(self, other_data):
            self.other_data = other_data
        ...

My function was to have the empty guidelines ([]) as a result of the default value for information, and for each event of Service to have its private information that might be altered over time on an instance-by-instance basis. But on this case, we acquired the subsequent conduct (bear in mind that other_data is bigoted on this occasion):

{:lang='python'}
    s1 = Service(['a', 'b'])
    s2 = Service(['c', 'd'])

    s1.information.append(1)

    s1.information
    ## [1]
    s2.information
    ## [1]

    s2.information.append(2)

    s1.information
    ## [1, 2]
    s2.information
    ## [1, 2]

This isn’t any good—altering our Python class variable by means of one event alters it for all the others!

At the namespace stage all conditions of Service are accessing and modifying the an identical guidelines in Service.__dict__ with out making their very personal information attributes of their event namespaces.

We would possibly get spherical this using challenge; that is, instead of exploiting the guidelines’s mutability, we would assign our Service objects to have their very personal lists, as follows:

{:lang='python'}
    s1 = Service(['a', 'b'])
    s2 = Service(['c', 'd'])

    s1.information = [1]
    s2.information = [2]

    s1.information
    ## [1]
    s2.information
    ## [2]

In this case, we’re together with s1.__dict__['data'] = [1], so the distinctive Service.__dict__['data'] stays unchanged.

Unfortunately, this requires that Service prospects have intimate data of its variables, and this workaround is certainly liable to errors. In a approach, we’d be addressing the indicators comparatively than the set off. It’s preferable to have one factor that is proper by constructing.

My non-public decision: If you’re merely using a class variable to assign a default value to a would-be Python event variable, don’t use mutable values. In this case, every event of Service was going to override Service.information with its private event attribute finally, so using an empty guidelines as a result of the default led to a tiny bug that was merely uncared for. Instead of the above, we would have each:

  1. Stuck to event attributes solely, as demonstrated throughout the introduction.

  2. Avoided using the empty guidelines (a mutable value) as our “default.”

For occasion:

       class Service(object):
           information = None

           def __init__(self, other_data):
               self.other_data = other_data
           ...

Of course, we’d ought to cope with the None case appropriately, nonetheless that’s a small value to pay.

When Should You Use Python Class Attributes?

Class attributes are robust, nonetheless let’s check out a few situations the place they’d develop into helpful:

  1. Storing constants. As class attributes might be accessed as attributes of the class itself, it’s usually good to utilize them for storing class-wide, class-specific constants. For occasion:

     class Circle(object):
         pi = 3.14159
    
         def __init__(self, radius):
             self.radius = radius
    
         def area(self):
             return Circle.pi * self.radius * self.radius
    
     Circle.pi
     ## 3.14159
    
     c = Circle(10)
     c.pi
     ## 3.14159
     c.area()
     ## 314.159
    
  2. Defining default values. As a small occasion, we might create a bounded guidelines (i.e., a listing which will solely preserve a positive number of elements or fewer) and choose to have a default cap of 10 devices:

     class MyClass(object):
         limit = 10
    
         def __init__(self):
             self.information = []
    
         def merchandise(self, i):
             return self.information[i]
    
         def add(self, e):
             if len(self.information) >= self.limit:
                 enhance Exception("Too many elements")
             self.information.append(e)
    
     MyClass.limit
     ## 10
    

    We would possibly then create conditions with their very personal specific limits too, by assigning them to the event’s limit attribute.

     foo = MyClass()
     foo.limit = 50
     ## foo can now preserve 50 elements—completely different conditions can preserve 10
    

    This solely is wise if you need your typical event of MyClass to hold merely 10 elements or fewer—for many who’re giving your complete conditions completely completely different limits, then limit should be an event variable. (Remember to be careful when using mutable values as your defaults.)

  3. Tracking all information all through all conditions of a given class. This is kind of specific, nonetheless I would see a state of affairs throughout which you will want to entry a bit of knowledge related to every current event of a given class.

    To make the state of affairs additional concrete, let’s say we now have a Person class, and every specific individual has a determine. We want to maintain monitor of all the names which have been used. One technique is probably to iterate over the garbage collector’s list of objects, however it’s simpler to make use of sophistication variables.

    Note that, on this case, names will solely be accessed as a class variable, so the mutable value default is suitable.

     class Person(object):
         all_names = []
    
         def __init__(self, determine):
             self.determine = determine
             Person.all_names.append(determine)
    
     joe = Person('Joe')
     bob = Person('Bob')
     print Person.all_names
     ## ['Joe', 'Bob']
    

    We would possibly even use this design pattern to hint all current conditions of a given class, comparatively than merely some associated information.

     class Person(object):
         all_people = []
    
         def __init__(self, determine):
             self.determine = determine
             Person.all_people.append(self)
    
     joe = Person('Joe')
     bob = Person('Bob')
     print Person.all_people
     ## [<__main__.Person object at 0x10e428c50>, <__main__.Person object at 0x10e428c90>]
    
  4. Considering Performance

Related: Python Best Practices and Tips by Toptal Developers

Under the Hood

Note: If you’re worrying about effectivity at this stage, you might not want to be use Python throughout the first place, as a result of the variations will most likely be on the order of tenths of a millisecond—however it’s nonetheless pleasing to poke spherical a bit and it helps for illustration’s sake.

Recall {{that a}} class’s namespace is created and stuffed in on the time of the class’s definition. That signifies that we do just one challenge for a given class variable, whereas event variables need to be assigned every time a model new event is created.

Let’s take an occasion:

{:lang='python'}
    def called_class():
        print "Class challenge"
        return 2

    class bar(object):
        y = called_class()

        def __init__(self, x):
            self.x = x

    ## "Class challenge"

    def called_instance():
        print "Instance challenge"
        return 2

    class foo(object):
        def __init__(self, x):
            self.y = called_instance()
            self.x = x

    bar(1)
    bar(2)
    foo(1)
    ## "Instance challenge"
    foo(2)
    ## "Instance challenge"

We assign to Bar.y merely as quickly as, nonetheless instance_of_Foo.y on every title to __init__.

As further proof, let’s use the Python disassembler:

{:lang='python'}
    import dis

    class bar(object):
        y = 2

        def __init__(self, x):
            self.x = x

    class foo(object):
        def __init__(self, x):
            self.y = 2
            self.x = x

    dis.dis(Bar)
    ##  Disassembly of __init__:
    ##  7          0 LOAD_FAST                1 (x)
    ##              3 LOAD_FAST                0 (self)
    ##              6 STORE_ATTR               0 (x)
    ##              9 LOAD_CONST               0 (None)
    ##             12 RETURN_VALUE

    dis.dis(Foo)
    ## Disassembly of __init__:
    ## 11          0 LOAD_CONST               1 (2)
    ##              3 LOAD_FAST                0 (self)
    ##              6 STORE_ATTR               0 (y)

    ## 12          9 LOAD_FAST                1 (x)
    ##             12 LOAD_FAST                0 (self)
    ##             15 STORE_ATTR               1 (x)
    ##             18 LOAD_CONST               0 (None)
    ##             21 RETURN_VALUE

When we check out the byte code, it’s as soon as extra obvious that Foo.__init__ has to do two assignments, whereas Bar.__init__ does just one.

In apply, what does this obtain really look like? I’ll be the first to admit that timing assessments are extraordinarily relying on usually uncontrollable parts and the variations between them are typically onerous to make clear exactly.

However, I consider these small snippets (run with the Python timeit module) help as an example the variations between class and event variables, so I’ve included them anyway.

Note: I’m on a MacBook Pro with OS X 10.8.5 and Python 2.7.2.

Initialization

10000000 calls to `Bar(2)`: 4.940s
10000000 calls to `Foo(2)`: 6.043s

The initializations of Bar are sooner by over a second, so the excellence proper right here does appear to be statistically important.

So why is that this the case? One speculative clarification: We do two assignments in Foo.__init__, nonetheless just one in Bar.__init__.

Assignment

10000000 calls to `Bar(2).y = 15`: 6.232s
10000000 calls to `Foo(2).y = 15`: 6.855s
10000000 `Bar` assignments: 6.232s - 4.940s = 1.292s
10000000 `Foo` assignments: 6.855s - 6.043s = 0.812s

Note: There’s no approach to re-run your setup code on each trial with timeit, so we now need to reinitialize our variable on our trial. The second line of events represents the above events with the beforehand calculated initialization events deducted.

From the above, it seems like Foo solely takes about 60% as long as Bar to cope with assignments.

Why is that this the case? One speculative clarification: When we assign to Bar(2).y, we first look throughout the event namespace (Bar(2).__dict__[y]), fail to go looking out y, after which look throughout the class namespace (Bar.__dict__[y]), then make the proper challenge. When we assign to Foo(2).y, we do half as many lookups, as we immediately assign to the event namespace (Foo(2).__dict__[y]).

In summary, though these effectivity helpful properties gained’t matter genuinely, these assessments are attention-grabbing on the conceptual stage. If one thing, I hope these variations help illustrate the mechanical distinctions between class and event variables.

In Conclusion

Class attributes seem like underused in Python; loads of programmers have completely completely different impressions of how they work and why they is probably helpful.

My take: Python class variables have their place inside the school of fantastic code. When used with care, they’ll simplify points and improve readability. But when carelessly thrown proper right into a given class, they’re constructive to journey you up.

Appendix: Private Instance Variables

One additional variable to say: personal event variables.

Python doesn’t have personal variables so to speak, nonetheless one different attention-grabbing relationship between class and event naming comes with determine mangling.

In the Python trend data, it’s said that pseudo-private variables should be prefixed with a double underscore: ‘__’. This is not solely a sign to others that your variable is meant to be dealt with privately, however moreover a method to cease entry to it, of sorts. Here’s what I indicate:

class Bar(object):
    def __init__(self):
    self.__zap = 1

a = Bar()
a.__zap
## Traceback (latest title closing):
##   File "<stdin>", line 1, in <module>
## AttributeError: 'Bar' object has no attribute '__baz'

## Hmm. So what’s throughout the namespace?
a.__dict__
{'_Bar__zap': 1}
a._Bar__zap
## 1

Look at that: The event attribute __zap is mechanically prefixed with the class determine to yield _Bar__zap.

While nonetheless settable and gettable using a._Bar__zap, this determine mangling is a approach of creating a “private” variable as a result of it prevents you and others from accessing it by likelihood or via ignorance.

Edit: As Pedro Werneck kindly recognized, this conduct is principally supposed to help out with subclassing. In the PEP 8 style guide, they see it as serving two features: (1) stopping subclasses from accessing positive attributes, and (2) stopping namespace clashes in these subclasses. While useful, variable mangling shouldn’t be seen as an invitation to write down down code with an assumed public-private distinction, corresponding to is present in Java.

  • Ensuring Clean Code: A Look at Python, Parameterized
  • Python Design Patterns: For Sleek And Fashionable Code
  • Top 10 Mistakes That Django Developers Make
  • An Introduction to Mocking in Python
  • The Six Commandments of Good Code: Write Code That Stands the Test of Time
Subscribe to Ai Blogify Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 477 other subscribers
Share:
Facebook
Twitter
Pinterest
LinkedIn
Reddit
VK
OK
Tumblr
Digg
Mix
Telegram
Pocket
XING
WhatsApp
Email

Leave a Reply

Your email address will not be published. Required fields are marked *

Table of Contents

Checkout more articles
 - 
Arabic
 - 
ar
Armenian
 - 
hy
Azerbaijani
 - 
az
Bengali
 - 
bn
Bulgarian
 - 
bg
Chinese (Simplified)
 - 
zh-CN
Czech
 - 
cs
Danish
 - 
da
Dutch
 - 
nl
English
 - 
en
Filipino
 - 
tl
Finnish
 - 
fi
French
 - 
fr
Georgian
 - 
ka
German
 - 
de
Greek
 - 
el
Gujarati
 - 
gu
Hebrew
 - 
iw
Hindi
 - 
hi
Indonesian
 - 
id
Italian
 - 
it
Japanese
 - 
ja
Korean
 - 
ko
Kurdish (Kurmanji)
 - 
ku
Latin
 - 
la
Lithuanian
 - 
lt
Malayalam
 - 
ml
Marathi
 - 
mr
Myanmar (Burmese)
 - 
my
Nepali
 - 
ne
Persian
 - 
fa
Portuguese
 - 
pt
Punjabi
 - 
pa
Romanian
 - 
ro
Russian
 - 
ru
Spanish
 - 
es
Swedish
 - 
sv
Tamil
 - 
ta
Telugu
 - 
te
Turkish
 - 
tr
Ukrainian
 - 
uk
Urdu
 - 
ur
Vietnamese
 - 
vi
Skip to content