Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do python classes work?

Tags:

python

I have a code file from the boto framework pasted below, all of the print statements are mine, and the one commented out line is also mine, all else belongs to the attributed author.

My question is what is the order in which instantiations and allocations occur in python when instantiating a class? The author's code below is under the assumption that 'DefaultDomainName' will exist when an instance of the class is created (e.g. __init__() is called), but this does not seem to be the case, at least in my testing in python 2.5 on OS X.

In the class Manager __init__() method, my print statements show as 'None'. And the print statements in the global function set_domain() further down shows 'None' prior to setting Manager.DefaultDomainName, and shows the expected value of 'test_domain' after the assignment. But when creating an instance of Manager again after calling set_domain(), the __init__() method still shows 'None'.

Can anyone help me out, and explain what is going on here. It would be greatly appreciated. Thank you.



# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish, dis-
# tribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the fol-
# lowing conditions:
#
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

import boto
from boto.utils import find_class

class Manager(object):

    DefaultDomainName = boto.config.get('Persist', 'default_domain', None)

    def __init__(self, domain_name=None, aws_access_key_id=None, aws_secret_access_key=None, debug=0):
        self.domain_name = domain_name
        self.aws_access_key_id = aws_access_key_id
        self.aws_secret_access_key = aws_secret_access_key
        self.domain = None
        self.sdb = None
        self.s3 = None
        if not self.domain_name:
            print "1: %s" % self.DefaultDomainName
            print "2: %s" % Manager.DefaultDomainName
            self.domain_name = self.DefaultDomainName
            #self.domain_name = 'test_domain'
            if self.domain_name:
                boto.log.info('No SimpleDB domain set, using default_domain: %s' % self.domain_name)
            else:
                boto.log.warning('No SimpleDB domain set, persistance is disabled')
        if self.domain_name:
            self.sdb = boto.connect_sdb(aws_access_key_id=self.aws_access_key_id,
                                        aws_secret_access_key=self.aws_secret_access_key,
                                        debug=debug)
            self.domain = self.sdb.lookup(self.domain_name)
            if not self.domain:
                self.domain = self.sdb.create_domain(self.domain_name)

    def get_s3_connection(self):
        if not self.s3:
            self.s3 = boto.connect_s3(self.aws_access_key_id, self.aws_secret_access_key)
        return self.s3

def get_manager(domain_name=None, aws_access_key_id=None, aws_secret_access_key=None, debug=0):
    return Manager(domain_name, aws_access_key_id, aws_secret_access_key, debug=debug)

def set_domain(domain_name):
    print "3: %s" % Manager.DefaultDomainName
    Manager.DefaultDomainName = domain_name
    print "4: %s" % Manager.DefaultDomainName

def get_domain():
    return Manager.DefaultDomainName

def revive_object_from_id(id, manager):
    if not manager.domain:
        return None
    attrs = manager.domain.get_attributes(id, ['__module__', '__type__', '__lineage__'])
    try:
        cls = find_class(attrs['__module__'], attrs['__type__'])
        return cls(id, manager=manager)
    except ImportError:
        return None

def object_lister(cls, query_lister, manager):
    for item in query_lister:
        if cls:
            yield cls(item.name)
        else:
            o = revive_object_from_id(item.name, manager)
            if o:
                yield o

like image 926
Mark Avatar asked Dec 22 '22 12:12

Mark


1 Answers

A few python notes

When python executes the class block, it creates all of the "attributes" of that class as it encounters them. They are usually class variables as well as functions (methods), and the like.

So the value for "Manager.DefaultDomainName" is set when it is encountered in the class definition. This code is only ever run once - never again. The reason for that is that it is just "defining" the class object called "Manager".

When an object of class "Manager" is instantiated, it is an instance of the "Manager" class. (that may sound repetitive). To be perfectly clear, the value:

self.DefaultDomainName

does not exist. Following the rules of classes, python says "hmm, that does not exist on this object instance, I'll look at the class object(s)". So python actually finds the value at:

Manager.DefaultDomainName

# also referenced by
self.__class__.DefaultDomainName

All of that to exemplify the point that the class attribute "Manager.DefaultDomainName" is only created once, can only exist once, and can only hold one value at once.


In the example above, run the builtin function id() on each of the values:

print "1: %s" % id(self.DefaultDomainName)
print "2: %s" % id(Manager.DefaultDomainName)

You should see that they are referring to exactly the same memory location.


Now, in (non)answer to the original question... I don't know from perusing the code above. I would suggest that you try a couple of techniques to find it out:

# Debug with pdb.  Follow every step of the process to ensure that you are 
# setting valeus as you thought, and that the code you thought would be 
# called is actually being called.  I've had many problems like this where 
# the error was in procedure, not in the actual code at hand.
import pdb; pdb.set_trace()

# check to see if id(Manager) is the same as id(self.__class__)

# in the set_domain() function:
# check to see what attributes you can see on Manager, 
# and if they match the attributes on Manager and self.__class__ in __init__

Please update here when you figure it out.

like image 182
gahooa Avatar answered Jan 08 '23 00:01

gahooa