Как передать значение аргумента по умолчанию для члена экземпляра в метод? - PullRequest
49 голосов
/ 15 ноября 2011

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

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"

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

Ответы [ 6 ]

59 голосов
/ 15 ноября 2011

Вы не можете определить это как значение по умолчанию, так как значение по умолчанию оценивается, когда метод определен до того, как существуют какие-либо экземпляры. Обычный шаблон - сделать что-то вроде этого:

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 был вызван только один раз, и это произошло до того, как была вызвана функция!

8 голосов
/ 15 ноября 2011

В 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 голосов
/ 17 октября 2018

Поскольку вы хотите использовать 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:

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')
1 голос
/ 15 ноября 2011

"self" необходимо передать в качестве первого аргумента любым функциям класса, если вы хотите, чтобы они вели себя как нестатические методы.

это относится к самому объекту. Вы не могли передать «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

0 голосов
/ 01 июля 2019

Вместо создания списка if-thens, охватывающего ваши аргументы по умолчанию, можно использовать словарь 'defaults' и создавать новые экземпляры класса с помощью 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-подход, но вот '.

0 голосов
/ 21 февраля 2019

Вы не можете получить доступ к себе в определении метода. Мой обходной путь - это -

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
...