Я пытаюсь изучить и понять, как использовать super в Python, я следил за книгой «Путешествие Python от новичка к эксперту», и хотя я чувствую, что понимаю концепцию, у меня проблемы с выполнением super в моем собственном коде.
Например, у меня работает такой метод:
class Employee:
def __init__(self, firstname, lastname, age, sex, dob):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.sex = sex
self.dob = dob
self.all_staff.append(self)
class Hourly(Employee):
def __init__(self, firstname, lastname, age, sex, dob, rate, hours):
self.rate = rate
self.hours = hours
super().__init__(firstname, lastname, age, sex, dob)
def __str__(self):
return "{} {}\nAge: {}\nSex: {}\nDOB: {}\n".format(self.firstname, self.lastname, self.age,
self.sex, self.dob)
def get_rate(self):
print('The hourly rate of {} is {} '.format(self.firstname, self.rate))
hourlystaff1 = Hourly('Bob', 'Foo', '23', 'M', '12/1/1980', '$15', '30')
print(hourlystaff1)
print(hourlystaff1.get_rate())
возвращает следующее:
Bob Foo
Age: 23
Sex: M
DOB: 12/1/1980
The hourly rate of Bob is $15
None
Это то, что я ожидал (хотя я не уверен, почему также возвращается «None», возможно, кто-нибудь сможет объяснить?).
А потом я хотел попробовать это с помощью super, но с такими ** kwargs:
class Employee:
def __init__(self, firstname='', lastname='', age='', dob='', **kwargs):
super().__init__(**kwargs)
self.firstname = firstname
self.lastname = lastname
self.age = age
self.dob = dob
class Hourly(Employee):
def __init__(self, rate=''):
self.rate = rate
super().__init__(**kwargs)
def __str__(self):
return "{} {}\nAge: {}\nSex: {}".format(self.firstname, self.lastname, self.age,
self.sex, self.dob, self.rate)
def get_rate(self):
print('The hourly rate of {} is {} '.format(self.firstname, self.rate))
bob = Hourly('Bob', 'Bar', '23', '12/1/2019')
bob.get_rate('$12')
возвращает эту ошибку:
File "staff_b.py", line 33, in <module>
bob = Hourly('Bob', 'Bar', '23', '12/1/2019')
TypeError: __init__() takes from 1 to 2 positional arguments but 5 were given
что я делаю не так во втором подходе? Как мне здесь правильно использовать ** kwargs и super?
Редактировать:
это скриншот примера из книги, за которой я следил:
в чем разница между тем, как я использую ** kwargs и super во втором примере?
Это также исчерпывающий пример из той же книги и главы. Это работает для меня, и я понимаю, как это работает, но, похоже, я не могу перевести это в свою собственную работу.
Хорошо, спасибо, так что для второго я должен просто удалить ** kwargs из Employee в этом, а также super, и он должен работать? Я сделал это, но все равно получаю ту же ошибку.
Удалите их из Employee и добавьте их в Hourly: def __init__(self, rate='', **kwargs):
Как этот pastebin.com/xsrEBDpx? Это по-прежнему возвращает ту же ошибку. Также, пожалуйста, смотрите мои изменения в сообщениях выше, спасибо.
Ах, это потому, что вы передаете их как позиционные аргументы, а не аргументы ключевого слова. Вы можете поменять все свои **kwargs на *args, **kwargs. Но обратите внимание, что вы передаете 'Bob' в качестве значения параметра rate, поэтому, возможно, вам следует просто использовать аргументы ключевого слова, чтобы избежать подобных ошибок.
Хорошо, теперь я понимаю вроде, как бы, что-то вроде, но что эти примеры из книги делают по-другому с ** kwargs?
Не уверен, что вы имеете в виду. Не думаю, что они делают что-то по-другому.
Ах, думаю, я понимаю, о чем вы говорите. Классы в книге предназначены для множественного наследования. (Вы можете видеть, что Friend наследуется как от Contact, так и от AddressHolder.) Вы можете найти мой ответ здесь полезным.
Хорошо, спасибо, я прочитаю этот ответ, до сих пор не понимаю всего этого, но добираюсь до цели медленно! Не стесняйтесь отвечать, и я приму это :)
Я не уверен, стоит ли мне это делать. В вашем коде так много отдельных мелких проблем, что он, вероятно, слишком широк, чтобы хорошо подходить для StackOverflow.






Это должно работать
class Employee:
def __init__(self, firstname='', lastname='', age='', dob=''):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.dob = dob
Тогда у вас есть дочерний класс
class Hourly2(Employee):
def __init__(self,*args, **kwargs):
super(Hourly2,self).__init__(*args, **kwargs)
def get_rate(self,rate=None):
self.data=rate
print ('My hourly rate is {}'.format(self.data))
Теперь давайте создадим пример
bob = Hourly2('Bob', 'Bar', '23', '12/1/2019')
Мы можем проверить атрибуты
dir(bob)
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'age',
'dob',
'firstname',
'get_rate',
'lastname']
Ну наконец то
bob.age
'23'
А также
bob.get_rate('$12')
My hourly rate is $12
Этот дочерний класс совершенно бессмыслен. Зачем нужен метод, который принимает аргумент и возвращает его? И если вы собираетесь присвоить этот аргумент атрибуту, почему он называется «получать_rate»? И вообще, почему бы не использовать rate в качестве параметра конструктора?
Да, но он сделал нечто совершенно иное, чем ваше.
Я внес правку, можете ли вы привести пример или ссылку относительно параметра конструктора.
здесь не рассматривается, как работать с бородавками и super, что, если я не хочу явно указывать параметры в __init __ () super?
Проблема, которую вы здесь видите, на самом деле не специфична для super, а более специфична для kwargs. Если мы выбросим большую часть вашего кода и удалим супер, это будет выглядеть так:
class Hourly(Employee):
def __init__(self, rate=''):
self.rate = rate
some_crazy_function(**kwargs)
hourlystaff1 = Hourly('Bob', 'Foo', '23', 'M', '12/1/1980', '$15', '30')
Есть две очевидные проблемы: функция __init__ получает больше аргументов, чем ожидалось, а в теле функции __init__ есть ссылка на kwargs, которая нигде не определена. Хотя здесь понимания **kwargs (и его брата *args) достаточно, чтобы решить проблему, здесь super и **kwargs очень полезны вместе. Давайте сначала посмотрим, чем полезен super. Давайте представим, что мы пишем несколько оболочек для подпроцессов с некоторыми хорошими вспомогательными методами (архитектура, возможно, не наилучшим образом подходит для проблемы, но только когда-либо видеть животных с наследованием также не очень полезно. Множественное наследование - действительно редкий случай, поэтому это сложно чтобы придумать хорошие примеры, которые не являются животными, GameEntities или GUIwidgets):
class Process:
def __init__(self, exe):
self.exe = exe
self.run()
class DownloadExecutableBeforeProcess(Process):
def __init__(self, exe):
self.download_exe(exe)
Process.__init__(self, exe)
Здесь мы выполняем наследование, и нам даже не нужно использовать super - мы можем просто явно использовать имя суперкласса и иметь желаемое поведение. Мы могли бы переписать здесь, чтобы использовать super, но это не изменило бы поведения. Если вы наследуете только один класс, вам не обязательно super, хотя это может помочь вам не повторять имя класса, от которого вы наследуете. Давайте добавим к нашему классу иерархию и включим наследование от более чем одного класса:
class AuthenticationCheckerProcess(Process):
def __init__(self, exe, use_sha=True):
self.check_if_authorized(exe, use_sha)
Process.__init__(self, exe)
class DownloadAndCheck(DownloadExecutableBefore, AuthenticationCheckerProcess):
def __init__(self, exe):
DownloadExecutableBefore.__init__(exe)
AuthenticationCheckerProcess.__init__(exe, use_sha=False)
Если мы проследим за инициализацией DownloadAndCheck, мы увидим, что Process.__init__ вызывается дважды: один раз через DownloadExecutableBefore.__init__ и один раз через AuthenticationCheckerProcess.__init__! Итак, наш процесс, который мы хотим обернуть, также запускается дважды, а это не то, что нам нужно. Здесь, в этом примере, мы могли бы легко исправить это, не вызывая self.run() в инициализации процесса, но в реальных случаях это не всегда так легко исправить, как здесь. В данном случае вызов Process.__init__ кажется неправильным. Можно как-то это исправить?
class DownloadAndCheck(DownloadExecutableBefore, AuthenticationCheckerProcess):
def __init__(self, exe):
super().__init__(exe, use_sha=False)
# also replace the Process.__init__ cals in the other classes with super
super устраняет эту проблему и вызывает Process.__init__ только один раз. Он также позаботится о порядке, в котором должна выполняться функция, но здесь это не является большой проблемой. У нас все еще есть проблема: use_sha=False будет передан инициализаторам все, но на самом деле он нужен только одному. На самом деле мы не можем передать переменную только тем функциям, которые в ней нуждаются (потому что выяснить это было бы кошмаром), но мы можем научить другие __init__ просто игнорировать клавиатуру:
class Process:
def __init__(self, exe, **kwargs):
# accept arbitrary keywoards but ignore everything but exe
# also put **kwargs in all other initializers
self.exe = exe
self.run()
class DownloadExecutableBeforeProcess(Process):
def __init__(self, exe, **kwargs):
self.download_exe(exe)
# pass the keywoards into super so that other __init__s can use them
Process.__init__(self, exe, **kwargs)
Теперь вызов super().__init__(exe, use_sha=False) будет успешным, каждый инициализатор берет только те клавиатуры, которые он понимает, и просто передает остальные ниже.
Итак, если у вас есть множественное наследование и вы используете разные (ключевые) аргументы, super и kwargs могут решить вашу проблему. Но супер- и множественное наследование - это сложно, особенно если у вас больше слоев наследования, чем здесь. Иногда порядок, в котором должны вызываться функции, даже не определен (и тогда python должен выдать ошибку, см., Например, объяснение изменения алгоритма ТОиР). Примеси могут даже потребовать вызова super().__init__(), хотя они даже не наследуются ни от одного класса. В целом, вы усложняете свой код, если используете множественное наследование, поэтому, если оно вам действительно не нужно, часто лучше подумать о других способах моделирования вашей проблемы.
Хороший ответ. Хотя это может быть более подходящим для этот вопрос. Я не думаю, что здесь много пользы, учитывая, насколько запутан вопрос. Рассмотрите возможность переноса ответа на вопрос, который я связал; там его увидят гораздо больше людей (и вам нужно только внести некоторые незначительные изменения).
Моя единственная жалоба по этому вопросу связана с вашим заявлением: «Если вы наследуете только один класс super, вам не нужен super». Это определенно верно, но использование super по-прежнему является хорошей практикой, хотя бы потому, что это позволяет избежать необходимости жесткого кодирования имени родительского класса. Я боюсь, что то, как вы сформулировали это предложение, может отговорить людей от использования super без уважительной причины.
@ Аран-Фей Спасибо. Мне кажется, что для понимания вопроса в цепочке, которую вы связали, уже требуется базовое понимание super, которое я попытался передать в своем ответе. Я действительно не нашел SO-вопроса, который требует базовых знаний, представленных в моем ответе, хотя связанная запись в блоге, вероятно, лучший ресурс по super, который я нашел на сегодняшний день. Я немного изменил формулировку, чтобы включить вашу жалобу, хотя лично я предпочитаю избегать использования super, если он не нужен.
спасибо, отличный ответ, и мне определенно нужно больше читать / практиковаться с этим
@goblin_rocket Если вы еще не видели его, эта статья в блоге, также связанный в ответе, на который ссылается Аран-Фей, действительно хорош. Единственное, чего не хватает, - это миксины, где вам нужно вызвать super, хотя у него нет суперкласса.
Что касается
None, это потому, что ваш методget_rateничего не возвращает. См. Чем возврат вывода функции отличается от его печати?. Что касается**kwargs: вы их не туда положили. Их следует использовать в методеHourly.__init__. ИEmployeeне должен называтьsuper().__init__(**kwargs)илиsuper().__init__()или что-то в этом роде.