最“Pythonic”的类属性、构造函数参数及子类构造函数默认值的组织方式?

19 投票
1 回答
9551 浏览
提问于 2025-04-15 12:52

我刚接触Python 2,对如何以最“Pythonic”的方式组织我的类文件还有些不确定。之所以问这个问题,是因为Python有很多做事情的方法,跟我以前用的语言差别很大。

最开始,我是按照在C#或PHP中处理类的方式来做的,结果在发现可变值的问题时,真是让我摔了个大跟头:

class Pants(object):
    pockets = 2
    pocketcontents = []

class CargoPants(Pants):
    pockets = 200

p1 = Pants()
p1.pocketcontents.append("Magical ten dollar bill")
p2 = CargoPants()

print p2.pocketcontents

哎呀!没想到会这样!

我花了很多时间在网上搜索,看看其他项目的源代码,想找一些关于如何安排类的提示。我注意到很多人会在构造函数里声明他们的实例变量,无论是可变的还是不可变的,而且还会给默认构造函数的参数加得很厚。

这样开发了一段时间后,我还是有点摸不着头脑。考虑到Python语言为了让事情看起来更直观和明显所做的努力,当我有很多属性或很多默认构造函数参数时,尤其是在子类化的时候,这种情况让我觉得很奇怪:

class ClassWithLotsOfAttributes(object):
    def __init__(self, jeebus, coolness='lots', python='isgoodfun', 
             pythonic='nebulous', duck='goose', pants=None, 
             magictenbucks=4, datawad=None, dataload=None,
             datacatastrophe=None):

        if pants is None: pants = []
        if datawad is None: datawad = []
        if dataload is None: dataload = []
        if datacatastrophe is None: datacatastrophe = []
        self.coolness = coolness
        self.python = python
        self.pythonic = pythonic
        self.duck = duck
        self.pants = pants
        self.magictenbucks = magictenbucks
        self.datawad = datawad
        self.dataload = dataload
        self.datacatastrophe = datacatastrophe
        self.bigness = None
        self.awesomeitude = None
        self.genius = None
        self.fatness = None
        self.topwise = None
        self.brillant = False
        self.strangenessfactor = 3
        self.noisiness = 12
        self.whatever = None
        self.yougettheidea = True

class Dog(ClassWithLotsOfAttributes):
    def __init__(self, coolness='lots', python='isgoodfun', pythonic='nebulous', duck='goose', pants=None, magictenbucks=4, datawad=None, dataload=None, datacatastrophe=None):
        super(ClassWithLotsOfAttributes, self).__init__(coolness, python, pythonic, duck, pants, magictenbucks, datawad, dataload, datacatastrophe)
        self.noisiness = 1000000

    def quack(self):
        print "woof"

虽然这有点搞笑(我总是忍不住想出这些虚构的示例类),但假设我真的需要一组有这么多属性的类,我想问的问题是:

  • 声明一个有这么多属性的类,最“Pythonic”的方式是什么?如果默认值是不可变的,是把它们放在类里,比如Pants.pockets,还是放在构造函数里,比如ClassWithLotsOfAttributes.noisiness更好?

  • 有没有办法消除在所有子类构造函数中重新声明默认值的需要,比如Dog.__init__?我真的需要包含这么多带默认值的参数吗?

1 个回答

7
  • 如果属性在不同的实例之间会有所不同,那就把它们当作实例属性来处理,也就是说在__init__方法里用self来创建它们。如果这些属性需要在类的多个实例之间共享,比如常量,那就把它们放在类的层级上。

  • 如果你的类在__init__方法里需要传递很多参数,可以让子类使用参数列表和关键字参数,比如:

class Dog(ClassWithLotsOfAttributes):
    def __init__(self, *args , **kwargs):
        super(ClassWithLotsOfAttributes,    self).__init__(*args , **kwargs)
        self.coolness = "really cool!!!
  • __init__里不需要传递所有的变量,只需要传递几个重要的。类可以假设一些默认值,用户如果需要的话可以在后面再修改。
  • 使用4个空格来代替制表符(tab)。

  • 如果你需要给Dog类添加一个额外的参数bite,以及一个关键字参数old。

class CoolDog(ClassWithLotsOfAttributes):
    def __init__(self, bite, *args , **kwargs):
        self.old = kwargs.pop('old', False) # this way we can access base class args too
        super(ClassWithLotsOfAttributes,    self).__init__(*args , **kwargs)
        self.bite = bite
        self.coolness = "really really cool!!!

使用CoolDog的各种方法

CoolDog(True)
CoolDog(True, old=False)
CoolDog(bite=True, old=True)
CoolDog(old=True, bite=False)

撰写回答