0

Present Scenario :

I have a set of classes that all take a common argument context in constructor, and all the classes inherit from a common base.

class base:
    def common_method(self):
        pass

class a(base):
    def __init__(self,context, aa):
        pass

class b(base):
    def __init__(self, context, bb):
        pass

# ...

class z(base):
    def __init__(self, context, zz):
        pass

Usage in main:

context = get_context_from_some_method()

A = a(context, 1)
B = b(context, 2)
C = c(context, 3)
D = d(context, 4)
.
.
.
Z = z(context, 26)

Problem:

  • Is there a tricky way possible to supply context to all the classes implicitly ?
  • We have a common base class, but I don't see a obvious way to let it set the context for all the classes.
  • It can be assumed that I want to keep context common to all my class instances.
  • Just a random thought - Can meta class help somehow ?

I know it seems silly, but I just want to get rid of the redundancy in some way, so that I can set a global context somehow and concentrate on my objects.

Please suggest some way around ?

** Update to Question **

I can-not set a context in the base class, since this is to be used in a web application. So, many pages with different context will be using the class structure. So, If I set a context in base then it will conflict to the context that will be set by another instance of webpage that will use the same base. Since, in a web-application all the above classes will be in memory common to all pages.

Yugal Jindle
  • 44,057
  • 43
  • 129
  • 197
  • You mean like in, global variable?! That rings some alarm bells here. – Niklas B. Mar 22 '12 at 02:17
  • Not a global variable, I mean some-how common to all. – Yugal Jindle Mar 22 '12 at 02:24
  • Call it like you want, it's still a global value with all the disadvantages (preventing mocking, can't use different sets of instance with different context instance, ...). – Niklas B. Mar 22 '12 at 02:25
  • Your update makes it even worse. So you want to set some global state, afterwards create some instances and then change the global state again so future instances will use a different value? This is not only not thread-safe, it's a disaster to debug and maintain later on. I encourage you very strongly to just keep it the way it currently is, explicitely providing the correct context through the constructor. – Niklas B. Mar 22 '12 at 02:31
  • @NiklasB. With a factory function you don't have to repeat yourself and it doesn't create global state. – agf Mar 22 '12 at 02:44
  • @agf: Yeah I know, but it also wouldn't really save a lot of typing. The question is: What's the purpose of this all? – Niklas B. Mar 22 '12 at 02:45
  • The purpose is to keep the interface clean and avoid the overhead, since the context is just some helping data. – Yugal Jindle Mar 22 '12 at 04:57

4 Answers4

2

Edit: If you don't want to / can't use a class variable, your best bet is to use a factory function. Either make it a static method on the base class, or a module level function.

def make_instances(context, *instances):
    return [cls(context, *args) for cls, args in instances]

A, B, ..., Z = make_instances(get_context_from_some_method(), 
                 (a, (1,)), (b, (2,)), ..., (z, (26,)))

# or
instances = make_instances(get_context_from_some_method(), 
             zip(list_of_subclasses, ((x,) for x in range(1, 27))))

Alternatively, and I don't know if this works in your situation, just use a module level global variable:

class z(base):
    def __init__(self, zz):
        self.zz = zz
        self.context = context

context = 'abc'
Z = z(26)

In addition to the advice to use class variables from the other answer, I advise you to copy the context onto the instances, so that you can later change the context without affecting already created instances.

class base:
    context = None # if you want to be able to create without context.
    # just omit the previous line if you want an error
    # when you haven't set a context and you try to instantiate a subclass

class a(base):
    def __init__(self, aa):
        self.aa = aa
        self.context = self.context # sets an instance variable
        # so the class variable can be changed

class b(base):
    def __init__(self, bb):
        self.bb = bb
        self.context = self.context

base.context = 'context'

A = a(1)
B = b(2)

base.context = 'newcontext'

print A.context # context
agf
  • 171,228
  • 44
  • 289
  • 238
  • @YugalJindle I added a version that doesn't use global state and a version that uses a module-level global variable. – agf Mar 22 '12 at 03:01
  • You didn't got me, `base.context` may get changed between the the formation of `A` and `B`. – Yugal Jindle Mar 22 '12 at 05:10
  • Anyways your reasonings seems perfectly logical and I think my question is somewhat not fair. – Yugal Jindle Mar 22 '12 at 05:11
  • @YugalJindle Please read the top half of my answer. I proposed _two_ methods that don't use a class variable. The first requires that the same context be used for a group of instances, the second will always just take the context from the environment. – agf Mar 22 '12 at 05:13
  • Ya.. I missed that part thanks. Although, I don't feel like following this approach, since I think I am demanding something wired. – Yugal Jindle Mar 22 '12 at 05:15
1

You can use class variables:

base.context = 'context'
A = a(1)
B = b(2)
#...
Theron Luhn
  • 3,974
  • 4
  • 37
  • 49
  • So much better than what I was going to write! – Collin Green Mar 22 '12 at 02:19
  • 1
    Just for completeness: Every instance can access that variable using `self.context`. Not that it's a good idea to do it, but it answers the question. – Niklas B. Mar 22 '12 at 02:20
  • This, by itself, doesn't allow you to later change the context but allow previously created instances to maintain the previous context. – agf Mar 22 '12 at 02:27
  • @agf: Which wasn't a requirement. The question as such is not very sensible, so I'm not sure how sensible it is to discuss these kinds of details. – Niklas B. Mar 22 '12 at 02:29
-1

The way to eliminate redundancy is to identify repeated patterns, and factor out the pattern into some separate bit of code.

Not sure if it's your real code, but from your example the pattern is that you're instantiating a whole bunch of classes in a similar way. Even if you removed the 9 characters from each line required to type context,, you're still doing:

A = a(1)
B = b(2)
C = c(3)
...

Which would drive me nuts. The problem with the redundancy is not so much the typing, but that if you want to change this (say you want to start at 0, or you want to add an additional argument to each class, or whatever) you have to change every line.

So I would rather do something like this:

classes = [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]
instances = [cls(context, i) for i, cls in enumerate(classes, 1)]

Here I've got code that clearly says I'm instantiating a whole bunch of classes, and that I am passing context to each one, along with an integer from 1 to the number of classes I have (increasing for each class). And it's easy to independently decide to change what the list of classes is, or what I'm passing to each class.

It does mean my instances have names like instances[0] and instances[13] instead of A and N. There are ways around that too, but I find it rare to be instantiating a collection of things and then use them as independent individual things rather than as a collection.

Ben
  • 68,572
  • 20
  • 126
  • 174
  • I covered this option in the edit to my answer as well. Note `enumerate` take a `start` optional argument. – agf Mar 22 '12 at 02:51
  • @agf Huh, I'd always just assumed that was for deciding at what position to start in the sequence, without ever reading the docs to confirm that. Thanks! – Ben Mar 22 '12 at 02:58
-2

You should define context as a class attribute of base. Modifying your example would be:

class base:

    context = None

    def common_method(self):
        pass

class A(base):
    def __init__(self, aa):
        pass

class B(base):
    def __init__(self, bb):
       pass

.
.
.

class Z(base):
    def __init__(self, zz):
        pass

base.context = get_context_from_some_method()
A = a(1)
B = b(2)
Z = z(3)    

All A, B and Z instances shares the same context property.

pablodcar
  • 798
  • 1
  • 6
  • 15
  • @Niklas B.: The first answer is not complete for a novice, It doesn't work without modifying the definition of the classes and the context attribute does not exists after setting it. – pablodcar Mar 22 '12 at 02:30
  • pablodcar: Then you should have written a comment, instead of posting another answer with the exact same idea. Also, your code contains a lot of unnecessary whitespace. – Niklas B. Mar 22 '12 at 02:34
  • @NiklasB.: Then you should consider the completeness of the answer or show why has a `lot of unnecessary whitespace` or ignoring it before saying this is only a duplicate. You first comment has a lot of unnecessary words ;). – pablodcar Mar 22 '12 at 03:00
  • I left a comment because I don't like downvoting without leaving an explanation, especially for new users. – Niklas B. Mar 22 '12 at 03:03