Подтвердить что ты не робот

Как передать значение аргумента по умолчанию для члена экземпляра методу?

Я хочу передать аргумент по умолчанию методу экземпляра, используя значение атрибута экземпляра:

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"

В чем проблема, почему это не работает? И как я могу сделать эту работу?

4b9b3361

Ответ 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, но вот ".