1

I have a function make_package returning a class Package. In another file, I want to import class Package, so I can do type check. My question is, how to import a class that's inside a function?

Following is not the exact code, but similar in structure.

# project/package.py
def make_package(ori, dest):
    class Package:
        origin = ori
        destination = dest
        def __init__(self, item):
            self.item = item
        def to_str(self):
            return self.origin + '-' + self.destination + ' : ' + self.item
    return Package

# project/shipment.py
# Don't know how to import Package from package.py
class Shipment:
    def __init__(self, **kwargs):
        self.shipment = dict()
        for container in kwargs.keys():
            self.shipment[container] = list()
            for item in kwargs[key]:
                if type(item) != Package:
                    raise TypeError('Item must be packed in Package.')
                self.shipment[container].append(item.to_str())

# project/main.py
from .package import make_package
from .shipment import Shipment
us_fr = make_package('US', 'FR')
fr_cn = make_package('FR', 'CN')
shipment = Shipment(container1=[us_fr('item1'), fr_cn('item2')], container2=[us_fr('item3'), fr_cn('item4')])
print(shipment.shipment)
# {
#     'container1' : [
#         'US-FR : item1',
#         'FR-CN' : 'item2'
#     ],
#     'container2' : [
#         'US-FR : item3',
#         'FR-CN' : 'item4'
#     ]
# }

I know one way I can achieve type check is make a dummy variable with make_package inside Shipment, then compare type(item) to type(dummy). However it seems more like a hack. I wonder if there's a better way?

5
  • 5
    Why is the class inside the function? Why not leave it at the global scope? What are you trying to achieve by putting it inside the function? Commented Aug 12, 2019 at 23:54
  • The best solution is to just move Package outside of the function. Unless you are going to use make_package as a factory function in the future, you don't even need that function. Commented Aug 12, 2019 at 23:58
  • Because I want the class inherit ori and dest value from the function, but only create object with item. This way I can reuse the variable, in example, us_fr many times, since I will be use it a lot more later on. If I put the class outside, I probably have to pass 3 arguments __init__(ori, dest, item) every time. Which later makes the code super messy. Commented Aug 13, 2019 at 0:04
  • @KevinWelch in my real code, there're a lot more class variables Package inherit from make_package's parameters. It gets really messy if I create Package object with all those parameters every time. I feel like it's way cleaner if I can use make_package to assign those params first, then only pass in item when create new instance, since item is the only variable that's different every time. Commented Aug 13, 2019 at 0:22
  • 1
    @Phoenix-C: Would using functools.partial to bind the common parameters to make a constructor that only needs item work? That would keep Package a singleton, while still letting you have simplified interfaces that construct it with common data. Commented Aug 13, 2019 at 0:34

1 Answer 1

4

There is no way to "import" your class from outside the function, because in reality, there is no one Package type. Every time you call make_package(), it's creating a new type, which happens to have the same name as all the other Package types. However, it is still a unique type, so it will never compare equal to another Package type, even if origin and destination are the same.

You could make all Package types inherit from a "marker" class, and then use isinstance to check if an item is a package:

# project/package.py
class PackageMarker:
    pass

def make_package(ori, dest):
    class Package(PackageMarker):
        ...  # Package body here
    return Package

# project/shipment.py
class Shipment:
    def __init__(self, **kwargs):
        self.shipment = dict()
        for container in kwargs.keys():
            self.shipment[container] = list()
            for item in kwargs[key]:
                if not isinstance(item, PackageMarker):
                    raise TypeError('Item must be packed in Package.')
                self.shipment[container].append(item.to_str())

In my opinion, the way you are creating classes with this factory function is confusing, but perhaps I simply need more context to see why you're doing it this way.

If you want my opinion on how I would refactor it, I would remove the factory function, and do something like this:

# project/package.py
class Package:
    def __init__(self, route, item):
        self.route = route
        self.item = item

    def to_str(self):
        return self.route.to_str() + ' : ' + self.item

# project/route.py
class Route:
    def __init__(self, origin, destination):
        self.origin = origin
        self.destination = destination

    def to_str(self):
        return self.origin + ' - ' + self.destination

# project/shipment.py
from .package import Package

class Shipment:
    def __init__(self, **kwargs):
        self.shipment = dict()
        for container in kwargs.keys():
            self.shipment[container] = list()
            for item in kwargs[key]:
                if not isinstance(item, Package):
                    raise TypeError('Item must be packed in Package.')
                self.shipment[container].append(item.to_str())

# project/main.py
from .package import Package
from .route import Route
from .shipment import Shipment
us_fr = Route('US', 'FR')
fr_cn = Route('FR', 'CN')
shipment = Shipment(
    container1=[Package(us_fr, 'item1'), Package(fr_cn, 'item2')],
    container2=[Package(us_fr, 'item3'), Package(fr_cn, 'item4')]
)
Sign up to request clarification or add additional context in comments.

2 Comments

So I'm writing this package for my company internal use (so treat the main.py as testing script). The reason I didn't have a parent class outside is because this way, others can write a custom class that extends PackageMarker and it'll bypass the type check. I kinda really want to restrict the type to be Package only. I'm open to suggestion to rewrite the whole thing.
@Phoenix-C in light of that, I've added a possible implementation which changes your original design more radically. In my opinion, this version is more understandable in terms of the traditional modelling we use in OOP.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.