Object type casting in Python (design suggestion)

2019-05-18 14:25发布

问题:

Let's say there is a library function called get_pack() which returns a Pack object:

class Pack(object):
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight
        ...

What I want to do is to wrap this object into my object which is let's say CommercialPack which has a couple of useful functions:

class CommercialPack(Pack):
    def get_delivery_price(self, distance):
        return self.weight * PER_KM_PRICE * distance
    ...

And then create a function which returns CommercialPack instead of Pack. In some other languages like Delphi or Java, you can type cast that object on the fly. So I want to have something like:

def get_commercial_pack():
    return CommercialPack(get_pack())

and then you have all you need with no hassle. Because I would like my CommercialPack object to have all the properties and functions that Pack object has. I just want to wrap it inside my new class. Basically I don't want to do something like this:

class CommercialPack(object):
    def __init__(self, pack):
        self.name = pack.name
        self.weight = pack.weight
        ...

OR

class CommercialPack(object):
    def __init__(self, pack):
        self.pack = pack

I'm looking for an elegant solution instead like I said, some kind of type casting or whatever I can do elegantly in Python.

Thanks very much.

回答1:

Perhaps something like this

class CommercialPack(object):
    def __init__(self, pack):
        self.__dict__.update(pack.__dict__)

You can even do this if you don't mind sharing state between the pack and the commercial pack

class CommercialPack(object):
    def __init__(self, pack):
        self.__dict__ = pack.__dict__

It should be ok in your example since you aren't keeping any other references to the pack object

eg.

PER_KM_PRICE = 100

class Pack(object):
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight


class CommercialPack(Pack):
    def __init__(self, pack):
        self.__dict__ = pack.__dict__

    def get_delivery_price(self, distance):
        return self.weight * PER_KM_PRICE * distance

def get_pack():
    return Pack("pack", 20)

cp = CommercialPack(get_pack())
print cp.get_delivery_price(3)


回答2:

Will this work for you?

#!/usr/bin/python
PER_KM_PRICE = 10

class Pack(object):
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight

    def test_fn(self):
        print "test"

class CommercialPack(Pack):
    def __init__(self, pack):
        self.pack = pack

    def get_delivery_price(self, distance):
        return self.weight * PER_KM_PRICE * distance

    def __getattr__(self, attr):
        return getattr(self.pack,attr)

You can use this like:

>>> p = Pack(10, 20)
>>> cp = CommercialPack(p)
>>> cp.weight
20
>>> cp.get_delivery_price(10)
2000
>>> cp.test_fn()
test


回答3:

Note: this is a duplicate of this answer nearby (hopefully above.)

So you don't want to copy a boring list of Pack's fields, but want to add a bit. There's an easy way to delegate resolution of unknown names using __getattr__:

class CommercialPack(object):
  def __init__(self, pack):
  self.pack = pack
  self.bar = 10

class CommercialPack(object):
  def __init__(self, pack):
    self.pack = pack
    self.bar = 10

  def __getattr__(self, name):
    return getattr(self.pack, name) # not called for bar!

Now the magic works:

>>> p = Pack('I am foo')
>>> p.foo
'I am foo'
>>> cp = CommercialPack(p)
>>> cp.foo
'I am foo'
>>> cp.bar
10
>>> _

So you can add methods and any other attributes to CommercialPack and transparently access those of Pack.

Please note that if you add a name that is already present in Pack, CommercialPack's attribute will shadow the same-named Pack's attribute. But you can always access it via the pack attribute.