0

I've been on an OOP exploration kick lately.

I have two classes: one for an event and one to calculate some set of attributes of that event.

class Event(object):
    def __init__(self,name,info):
        self.name = name
        self.category = info.category
        self.classification = info.classification

    @property
    def attributes(self):
        return dict((k,v) for k,v in self.__dict__.items())

class class1(object):
    def __init__(self,some_number):
        self.number = some_number

    @property
    def category(self):
        #stuff
        return category

    @property
    def classification(self):
        #stuff
        return classification

Right now I can do something like

e = Event('myevent', class1(42))
e.attributes
{'category': 1,
 'classification': None,
 'name': 'myevent'}

But I want to have the option of giving Event() instances of any number/combination of future class2, class3, etc; and therefore I want Event()'s init() method to gracefully handle them so that e.attributes will return all appropriate attributes. Giving Event.__init__ a *param to unpack doesn't seem to bypass the need for it to know the attribute names it needs to assign to itself. Ultimately what I want to do is to be able to create this event and then create/update its varying sets of attributes as needed later, using this paradigm. Is it possible?

verbsintransit
  • 888
  • 3
  • 8
  • 18
  • 2
    First, why `dict((k,v) for k,v in self.__dict__.items())` just to copy a dict? Second, why are you faking a dict on top of a dict in the first place? Maybe you need to [better explain what you're actually trying to accomplish, instead of just showing your partial solution](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – abarnert Aug 14 '13 at 00:47
  • I'm trying to understand your question but it's very unclear. I suspect there's a good chance that if you explained it better you might come up with the answer yourself? – Mike Vella Aug 14 '13 at 01:00
  • I'm in over my head, so I may very well be guilty of the xy problem, but I will try to elaborate. Miku almost has what I want, except that I am after an additive process: I want to update `e.attributes` after the two example calls he mentioned so that its values would be `{'i_am_totally_different': 'yes, indeed', 'name': 'myevent', 'category': 'category value', 'classification': 'classification value', 'number': 42}`. And to answer Mike's question, it's because I might not have all the information required for all classes when I first create `e` but I don't want to piecemeal-update attributes. – verbsintransit Aug 14 '13 at 02:18
  • @verbsintransit this design still doesn't make any sense to me. If Class 1 and Class 2 are just behaving as dicts, why not use dicts? And if Class 1 and Class 2 are not simply dicts since they have getters and setters then why do you need the Event class at all? Its only function seems to be to report all the public properties of another class. If there is common logic between all these classes then they are subclasses anyway and you should inherit from Event. – Mike Vella Aug 14 '13 at 02:32
  • 1
    The key to decoupling is to limit how much knowledge one class has to have about the classes it consumes. Poking around in the innards of classes is, as Miku states, never completely safe - what if calling a property accessor on some info class triggers a database query or an HTTP get? Requiring the child classes to subclass some 'EventInfo' class is one way to make that work; looking for a particular method is another. In other languages you'd define an interface - in python the standard method is to expect methods by name - consider the 'file like object' as an example.... – theodox Aug 14 '13 at 16:38

3 Answers3

1

I guess, there is no totally clean way to get only user defined attributes of a python object, which is what you would need in your example to make the Event completely ignorant of the structure of info. And if a design is only realizable with hacks, it might not be the most robust one - even if it looks OOD-ish.


Below an example of an Event class, that is ignorant of the actual attributes of the info object passed in:

class Event(object):
    # instead of accepting attributes, just deal with a single object 'info'
    def __init__(self, name, info):
        self.name = name
        self.info = info

    @property
    def attributes(self):
        # attributes will consist of
        # A) Event's name - fixed
        # B) info objects hopefully 'public' attributes, which we need
        #    to lookup at runtime
        attrs = {'name': self.name}
        for attr in [name for name in dir(self.info) 
                     if not name.startswith('_')]:
            attrs[attr] = getattr(self.info, attr)
        return attrs

Class1 and Class2 do not really share much:

class Class1(object):
    def __init__(self, some_number):
        # some initial attribute
        self.number = some_number

    @property
    def category(self):
        # stuff
        return 'category value'

    @property
    def classification(self):
        # stuff
        return 'classification value'

class Class2(object):
    @property
    def i_am_totally_different(self):
        return 'yes, indeed'

Now you can pass any class into Event, but I am not sure, if that was really your question:

e = Event('myevent', Class1(42))
print(e.attributes)
# {'category': 'category value', 'name': 'myevent', 
#  'classification': 'classification value', 'number': 42}

e = Event('myevent', Class2())
print(e.attributes)
# {'i_am_totally_different': 'yes, indeed', 'name': 'myevent'}
Community
  • 1
  • 1
miku
  • 181,842
  • 47
  • 306
  • 310
0

You say 'I have two classes: one for an event and one to calculate some set of attributes of that event.' Do you have a good reason for doing this? If you have an event class why not have those calculated attributes as properties of the event?

Mike Vella
  • 10,187
  • 14
  • 59
  • 86
  • Please see my comment in my original question. – verbsintransit Aug 14 '13 at 02:20
  • @verbsintransit answered there - I suspect you are trying to find a *very* complex solution to a *very* simple problem. If you give more details on the actual problem you are trying to solve we can investigate a better way to solve it than using all this introspection. Huge and complex applications exist in Python which don't use any of this hacky internal-logic. – Mike Vella Aug 14 '13 at 02:39
0

The alternative approach toward this would not be to dump the entire contents of class2, class3 etc without knowing what they are. You can ask expect that class to publish it's 'public' attributes in some fashion, for example with a 'get_attributes' method. From a coding convenience standpoint it's less work if class2, class3 and so on inherit from a common ancestor but it's perfectly OK to 'duck access' them:

class Event(object):
    def __init__(self, name, info):
        self.Name = name
        self.Info = info

    @property 
    def attributes():
        result = { k: getattr(info, k) for k in info.get_attributes() } 
        return result

class Class1(object):
    def __init__ (name, classification, other)
        self.Name = name
        self.Other = other
        self.Classification = classification

    def get_attributes(self):
        return ("Name", "Classification" ) # in this case not "Other"...

class Class2(object):
    def __init__(self, privateName, orivateData, publicName):
        self.Name = privateName
        self.Data = privateData
        self.Public = publicName

    def get_attributes(self):
        return ("Public",)

This is the 'opt in' alternative to @miku's idea - it's probably a safer bet unless you're sure your the only one creating the code on both ends of the event > info relationship. A nice trick would be to provide a decorator to adapt existing classes with a get_attributes method.

Clarity of intention aside, this is also a little protection against running into an oddball class with a thousand lines of text in one of its fields :)

Edit In response to @mike's observation: this would be cleaner if get_attributes just returned a dictionary from the info objects, which would be a more pure decoupling without the reflection burden. I'm leaving the code as is or the discussion makes no sense, but the right way would be to call 'get_attributes' on the child if it existed and return a default value if it didn't. My bad :)

theodox
  • 12,028
  • 3
  • 23
  • 36
  • How are Name and Data private in Class2? Also, don't you need to pass the class to Event? Those problems aside, this kind of design is very hacky - there has to be a better solution to what the OP fundamentally wants to achieve. – Mike Vella Aug 14 '13 at 01:35
  • privacy is purely notional - it's just to indicate that they don't get published to the event system. My point is that what OP wants is so perfectly general that its likely to cause more problems than it solves. Perfect decoupling isn't super useful if the only way to achieve it is detailed reflection over the innards of unknown classes. – theodox Aug 14 '13 at 01:46
  • In Python the standard way to indicate privacy is a leading double underscore, e.g self.__Name in the case of your example. – Mike Vella Aug 14 '13 at 01:51
  • Like i said, the 'privacy' is purely for purposes of the Event class. Otherwise we're still reflecting / inspecting to figure out what to do; – theodox Aug 14 '13 at 01:53