Как передать значение аргумента по умолчанию для члена экземпляра методу?
Я хочу передать аргумент по умолчанию методу экземпляра, используя значение атрибута экземпляра:
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=self.format):
print(formatting)
При попытке этого появляется следующее сообщение об ошибке:
NameError: name 'self' is not defined
Я хочу, чтобы метод вел себя так:
C("abc").process() # prints "abc"
C("abc").process("xyz") # prints "xyz"
В чем проблема, почему это не работает? И как я могу сделать эту работу?
Ответы
Ответ 1
Вы не можете действительно определить это как значение по умолчанию, так как значение по умолчанию оценивается, когда метод определен до того, как существуют какие-либо экземпляры. Обычный шаблон - сделать что-то вроде этого:
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=None):
if formatting is None:
formatting = self.format
print(formatting)
self.format
будет использоваться только если formatting
None
.
Чтобы продемонстрировать, как работают значения по умолчанию, посмотрите этот пример:
def mk_default():
print("mk_default has been called!")
def myfun(foo=mk_default()):
print("myfun has been called.")
print("about to test functions")
myfun("testing")
myfun("testing again")
И вывод здесь:
mk_default has been called!
about to test functions
myfun has been called.
myfun has been called.
Обратите внимание, как mk_default
вызывался только один раз, и это произошло до того, как функция была вызвана!
Ответ 2
В Python имя self
имеет значение не. Это просто соглашение для имени параметра, поэтому в __init__
есть параметр self
. (На самом деле, __init__
тоже не очень особенный, и, в частности, он не фактически создает объект... что более длинный рассказ)
C("abc").process()
создает экземпляр C
, ищет метод process
в классе C
и вызывает этот метод с экземпляром C
в качестве первого параметра. Таким образом, это будет указано в параметре self
, если вы предоставили его.
Даже если у вас есть этот параметр, вам не будет разрешено писать что-то вроде def process(self, formatting = self.formatting)
, потому что self
пока не находится в области, где вы задали значение по умолчанию. В Python значение по умолчанию для параметра вычисляется при компиляции функции и "приклеивается" к функции. (Это по той же причине, если вы используете по умолчанию, как []
, этот список будет помнить изменения между вызовами функции.)
Как я могу сделать эту работу?
Традиционный способ - использовать None
по умолчанию и проверить это значение и заменить его внутри функции. Вы можете обнаружить, что для этой цели было бы намного безопаснее сделать специальное задание (экземпляр object
- это все, что вам нужно, если вы его спрячете, чтобы вызывающий код не использовал один и тот же экземпляр) вместо None
, В любом случае, вы должны проверить это значение с помощью is
, а не ==
.
Ответ 3
Поскольку вы хотите использовать self.format
в качестве аргумента по умолчанию, это означает, что метод должен быть специфичным для экземпляра (то есть нет способа определить это на уровне класса). Вместо этого вы можете определить конкретный метод в классе, например, __init__
. Здесь у вас есть доступ к специфическим атрибутам экземпляра.
Один из подходов заключается в использовании functools.partial
для получения обновленной (определенной) версии метода:
from functools import partial
class C:
def __init__(self, format):
self.format = format
self.process = partial(self.process, formatting=self.format)
def process(self, formatting):
print(formatting)
c = C('default')
c.process()
# c.process('custom') # Doesn't work!
c.process(formatting='custom')
Обратите внимание, что при таком подходе вы можете передавать соответствующий аргумент только по ключевому слову, поскольку, если вы предоставите его по позиции, это приведет к partial
конфликту.
Другой подход заключается в определении и установке метода в __init__
:
from types import MethodType
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=self.format):
print(formatting)
self.process = MethodType(process, self)
c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
Это также позволяет передавать аргумент по позиции, однако порядок разрешения метода становится менее очевидным (что может, например, повлиять на проверку IDE, но я полагаю, что для этого есть специальные обходные пути для IDE).
Другой подход заключается в создании пользовательского типа для такого рода "значений по умолчанию атрибута экземпляра" вместе со специальным декоратором, который выполняет соответствующее getattr
аргумента getattr
:
import inspect
class Attribute:
def __init__(self, name):
self.name = name
def decorator(method):
signature = inspect.signature(method)
def wrapper(self, *args, **kwargs):
bound = signature.bind(*((self,) + args), **kwargs)
bound.apply_defaults()
bound.arguments.update({k: getattr(self, v.name) for k, v in bound.arguments.items()
if isinstance(v, Attribute)})
return method(*bound.args, **bound.kwargs)
return wrapper
class C:
def __init__(self, format):
self.format = format
@decorator
def process(self, formatting=Attribute('format')):
print(formatting)
c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
Ответ 4
"self" необходимо передать в качестве первого аргумента для любых функций класса, если вы хотите, чтобы они вели себя как нестатические методы.
он относится к самому объекту. Вы не могли передать "я" в качестве аргумента по умолчанию, поскольку его позиция является фиксированной как первый аргумент.
В вашем случае вместо "formatting = self.format" используйте "formatting = None", а затем присвойте значение из кода, как показано ниже:
[EDIT]
class c:
def __init__(self, cformat):
self.cformat = cformat
def process(self, formatting=None):
print "Formating---",formatting
if formatting == None:
formatting = self.cformat
print formatting
return formatting
else:
print formatting
return formatting
c("abc").process() # prints "abc"
c("abc").process("xyz") # prints "xyz"
Примечание: не используйте "формат" в качестве имени переменной, потому что это встроенная функция в python
Ответ 5
Вы не можете получить доступ к себе в определении метода. Мой обходной путь - это -
class Test:
def __init__(self):
self.default_v = 20
def test(self, v=None):
v = v or self.default_v
print(v)
Test().test()
> 20
Test().test(10)
> 10
Ответ 6
Вместо создания списка if-thens, охватывающего ваши аргументы по умолчанию, можно использовать словарь "по умолчанию" и создавать новые экземпляры класса с помощью eval():
class foo():
def __init__(self,arg):
self.arg = arg
class bar():
def __init__(self,*args,**kwargs):
#default values are given in a dictionary
defaults = {'foo1':'foo()','foo2':'foo()'}
for key in defaults.keys():
#if key is passed through kwargs, use that value of that key
if key in kwargs: setattr(self,key,kwargs[key])
#if no key is not passed through kwargs
#create a new instance of the default value
else: setattr(self,key, eval(defaults[key]))
Я добавляю это в начале каждого класса, который создает другой класс в качестве аргумента по умолчанию. Это позволяет избежать оценки Python по умолчанию при компиляции... Я бы хотел более чистый подход к Python, но вот ".