verbsintransit
verbsintransit

Reputation: 908

Passing instances as optional arguments to another class

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?

Upvotes: 0

Views: 122

Answers (3)

theodox
theodox

Reputation: 12218

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 :)

Upvotes: 0

Mike Vella
Mike Vella

Reputation: 10585

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?

Upvotes: 0

miku
miku

Reputation: 188234

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'}

Upvotes: 1

Related Questions