【问题标题】:Python - Getting subclass from input. Do I have to write a separate function?Python - 从输入中获取子类。我必须编写一个单独的函数吗?
【发布时间】:2014-12-05 19:07:01
【问题描述】:

我正在使用 Python 2.7.8。以下是我正在处理的问题的一个轻微变体。

我已经编写了大量自定义类,其中继承就像一棵树。以下示例很好地封装了该行为:

import random

class Animal(object):
    def __init__(self, name):
        self.name = name
        self.can_own_pets = False #most Animals cannot own pets
        self.get_features()

    def give_pet(self, pet):
        if not self.can_own_pets:
            print(self.name+' cannot own a pet!')
        else:
            self.pets.append(pet)

    def is_hungry(self):
        return random.choice([True, False])

    def get_features(self):
        """
        In some classes, get features will be a function
        that uses self.name to extract features.
        In my problem, the features are extracted
        with regular expressions that are determined by
        by the class.
        """
        pass

class Human(Animal):
    def __init__(self, name):
        super(Human, self).__init__(name)
        self.can_own_pets = True
        self.pets = []

class Dog(Animal):
     def __init__(self, name):
        super(Dog, self).__init__(name)

     def bark(self):
         print 'WOOF'

     def get_features(self):
         if 'chihuahua' in self.name:
             self.is_annoying = True
         elif 'corgi' in self.name:
             self.adorable = True

我的程序需要接收大量动物并将它们分配给正确的类——我需要正确的属性和方法。我希望 做的是修改 Animal 构造函数,这样如果 name 参数类似于“Finn the Dog”或“Jake the Human”,它(构造函数)会返回该类的实例“狗”或“人”,带有适当的方法和属性。现在,我知道我可以轻松编写一个函数,该函数将字符串和类作为参数,构造一个字典,其中键是给定类的子类的名称,查找包含在字符串中的字典元素, 并返回 that 类的对象。我的问题是是否有办法将它编码到 Animal 类本身中,这对我来说似乎更优雅(也更容易维护)。

【问题讨论】:

  • 为什么你觉得在 Animal 类中编码更优雅?一般来说,让一个类的行为依赖于它的子类有点可疑。您最好创建一个选择正确子类的工厂函数。
  • 您想要的是 C++ 中所谓的“虚拟构造函数”,是的,可以用 Python 实现它(就像在 C++ 中一样)。大多数人只是编写一个“类工厂”函数,它知道所有可能的类并选择合适的类。我会看看能不能想出一个简单的例子来按照你想要的方式做。
  • @martineau 我很感激!

标签: python oop inheritance


【解决方案1】:

这是一个实现 --

def _get_all_subclasses(cls):
  for scls in cls.__subclasses__():
    yield scls
    for scls in _get_all_subclasses(scls):
      yield scls


class Animal(object):

  @staticmethod
  def from_string(s):
    for cls in _get_all_subclasses(Animal):
      # Somehow pick the class based on the string... This is a really simple example...
      if cls.__name__ in s:
        return cls()
    raise ValueError('Bummer.  Animal has not been discovered.')


class Dog(Animal):
  pass


class Cat(Animal):
  pass


class Lion(Cat):
  pass

print Animal.from_string('is a Dog')
print Animal.from_string('is a Cat')
print Animal.from_string('Lions!!!')
print Animal.from_string('Lockness Monster')

这里有限制

  • 所有的构造函数都需要几乎相同,这意味着Cat.__init__ 需要基本上做与Human.__init__ 相同的事情。
  • 创建实例后,您的代码需要具有处理CatHumanDog 等的逻辑。在某些情况下没关系(例如,代码真的只关心它是否与@ 一起工作987654327@),但通常不是(毕竟,猫可以在栅栏上行走,但人类不能)。

一般来说,我喜欢遵循的原则是尝试让我的函数的输入是允许的(它是列表还是元组?谁在乎呢!鸭子打字 FTW!)但要尝试有非常明确的输出.我认为这使得接口更容易长期使用,如果我是审阅者,我上面编写的代码可能无法通过代码审查:-)。

【讨论】:

  • 谢谢!这正是我一直在寻找的东西。实际上,我有点自责,因为我没有考虑创建静态方法,但我昨天才知道它们。
  • @MTrenfield -- 你也可以在__new__ 中做类似的事情,但会更麻烦。
  • 在我的例子中,Cat、Human、Dog 等的逻辑都被整合到所有类都保证拥有的方法中。我得到一个数据集,其中包含“Walrus(700)-Brown-”之类的条目,我需要专门针对海象做一些操作,一些专门针对哺乳动物,一些专门针对 700 磅海象等。我正在使用熊猫,但是有足够的分析级别来自动化分组和来自 Pandas 的分层索引不再完全独立了。
【解决方案2】:

以 mgilson 的回答为基础

您可以重写__new__ 方法,这样您就可以像平常一样在没有静态方法的情况下实例化类。

class Animal(object):

    @classmethod
    def _get_all_subclasses(cls):
        for scls in cls.__subclasses__():
            yield scls
            for scls in scls._get_all_subclasses():
                yield scls

    def __new__(cls, name):
        cls_ = cls
        for subcls in Animal._get_all_subclasses():
            if subcls.__name__ in name:
                cls_ = subcls
                break
        instance = object.__new__(cls_)
        if not issubclass(cls_, cls):
            instance.__init__(name)
        return instance

【讨论】:

  • 谢谢!如果将来有人看到这个,你可能也想参考这个。 stackoverflow.com/questions/674304/pythons-use-of-new-and-init
  • 这里要小心。 . .如果cls 是Animal 的子类,但您的__new__ 决定实际类型应该不同于cls(并且不同的东西不是cls 的子类),那么__init__ 不会'不会被调用。例如如果CatDog 都是Animal 的(直接)子类,并且用户执行Cat('Dog') 之类的操作,那么用户将获得Dog 实例,但不会调用Dog.__init__
猜你喜欢
  • 2019-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多