Самый питонический способ организации атрибутов класса, аргументов конструктора и значений по умолчанию конструктора подкласса?

Будучи относительно новым для Python 2, я не уверен, как лучше всего организовать мои файлы классов наиболее «питоновским» способом. Я бы не спрашивал об этом, если бы не тот факт, что 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"

Если оставить в стороне легкую глупость (я не могу помочь себе, когда готовлю эти искусственные примеры классов), предполагая, что у меня есть реальная потребность в наборе классов с таким количеством атрибутов, я полагаю, что мои вопросы таковы:

  • Каков самый, гм, «питоновский» способ объявления класса с таким количеством атрибутов? Лучше ли поместить их против класса, если значение по умолчанию неизменяемое, аля Pants.pockets, или лучше поместить их в конструктор, аля ClassWithLotsOfAttributes.noisiness?

  • Есть ли способ устранить необходимость переопределять значения по умолчанию для всех аргументов конструктора подкласса, как в Dog.__init__? Должен ли я вообще включать такое количество аргументов со значениями по умолчанию?


person Shabbyrobe    schedule 13.07.2009    source источник
comment
+1 Должен понравиться атрибут вашего примера. Кроме того, хороший вопрос.   -  person balpha    schedule 13.07.2009


Ответы (1)


  • Если атрибуты будут варьироваться от экземпляра к экземпляру, сделайте их атрибутами экземпляра, т. е. создайте их внутри__init__ с помощью self, иначе, если они должны быть разделены между экземплярами класса, как константа, поместите их на уровне класса.

  • Если вашему классу действительно нужно пройти, так много аргументов в __init__, пусть производный класс использует список аргументов и аргументы ключевого слова, например.

class Dog(ClassWithLotsOfAttributes):
    def __init__(self, *args , **kwargs):
        super(ClassWithLotsOfAttributes,    self).__init__(*args , **kwargs)
        self.coolness = "really cool!!!
  • Нет необходимости передавать все переменные, кроме нескольких важных, в __init__ класс может принимать некоторые значения по умолчанию, и пользователь может изменить их позже, если это необходимо.
  • Используйте 4 пробела вместо табуляции.

  • если вам нужно добавить дополнительный аргумент аргумента, в Dog и ключевое слово arg 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)
person Anurag Uniyal    schedule 13.07.2009
comment
о, я не знал, что вы можете использовать *args и **kwargs таким образом! «крутость» определенно должна быть повышена до «действительно круто!!!» :) - person Shabbyrobe; 13.07.2009
comment
также, как насчет случая, когда Dog.__init__ нужно добавить дополнительный аргумент конструктора? Является ли def__init__(self, leg=4, *args, **kwargs) правильным способом сделать это? - person Shabbyrobe; 13.07.2009
comment
да, вы можете специально проверить имя var в kargs, я добавлю пример - person Anurag Uniyal; 13.07.2009
comment
«сделайте их атрибутом экземпляра, т. е. создайте их внутри __init__» -> Что действительно важно, так это использовать self вместо класса. Я думаю, что это может ввести в заблуждение начинающих. - person Bastien Léonard; 13.07.2009
comment
в чем основное различие между self.old = kwargs.pop('old', False) и def __init__(self, bit, old=False, *args, **kwargs)? - person Shabbyrobe; 13.07.2009
comment
нет никакой разницы, вы можете использовать это, но я добавил pop, чтобы вы могли получить доступ к аргументам базового класса, если это необходимо. - person Anurag Uniyal; 13.07.2009
comment
Во втором случае old займет одну позицию позиционного аргумента (поэтому вы указываете его как второй аргумент, если вы не указываете аргументы по имени). Извлекая его из словаря аргументов ключевого слова, он не может быть позиционным аргументом, который иногда требуется для аргументов. - person Blixt; 13.07.2009