Jak przekazać domyślną wartość argumentu członka instancji do metody?

Chcę przekazać domyślny argument do metody instancji używając wartości atrybutu instancji:

class C:
    def __init__(self, format):
        self.format = format

    def process(self, formatting=self.format):
        print(formatting)

Podczas próby otrzymuję następujący komunikat o błędzie:

NameError: name 'self' is not defined

Chcę aby metoda zachowywała się tak:

C("abc").process()       # prints "abc"
C("abc").process("xyz")  # prints "xyz"

W czym problem, dlaczego to nie działa? I jak mam to zrobić?

Author: poke, 2011-11-15

6 answers

Tak naprawdę nie można zdefiniować tej wartości jako wartości domyślnej, ponieważ wartość domyślna jest obliczana, gdy metoda jest zdefiniowana, czyli przed wystąpieniem jakichkolwiek instancji. Zwyczajowym wzorcem jest zrobienie czegoś takiego zamiast tego:

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 będzie używany tylko wtedy, gdy formatting jest None.


Aby zademonstrować, jak działają wartości domyślne, zobacz ten przykład:

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")

I wyjście tutaj:

mk_default has been called!
about to test functions
myfun has been called.
myfun has been called.

Zauważ jak mk_default została wywołana tylko raz, a stało się to przed funkcją nigdy nie dzwonili!

 90
Author: Adam Wagner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-13 16:24:25

W Pythonie nazwa self jest , a nie specjalna. Jest to po prostu konwencja dla nazwy parametru, dlatego w __init__ jest parametr self. (W rzeczywistości __init__ nie jest również bardzo specjalny, a w szczególności to Nie faktycznie tworzy obiekt... to dłuższa historia)

C("abc").process() tworzy instancję C, wyszukuje metodę process w klasie C i wywołuje tę metodę z instancją C jako pierwszym parametrem. Więc skończy się w self parametr, jeśli go podałeś.

Nawet gdybyś miał ten parametr, nie miałbyś prawa pisać czegoś takiego jak def process(self, formatting = self.formatting), ponieważ {[0] } nie jest jeszcze w zakresie w punkcie, w którym ustawiasz wartość domyślną. W Pythonie domyślna wartość parametru jest obliczana podczas kompilacji funkcji i "przyklejana" do funkcji. (Jest to ten sam powód, dla którego, jeśli użyjesz domyślnej opcji, takiej jak [], Ta lista będzie pamiętać zmiany między wywołaniami funkcji.)

Jak można Ja sprawiam, że to działa?

Tradycyjnym sposobem jest użycie None jako wartości domyślnej, a następnie sprawdzenie tej wartości i zastąpienie jej wewnątrz funkcji. Może się okazać, że bezpieczniej jest utworzyć specjalną wartość dla tego celu (instancja object jest wszystkim, czego potrzebujesz, o ile ją ukrywasz, aby kod wywołujący nie używał tej samej instancji) zamiast None. Tak czy inaczej, powinieneś sprawdzić tę wartość za pomocą is, a nie ==.

 10
Author: Karl Knechtel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-01 18:29:10

Ponieważ chcesz użyć self.format jako domyślnego argumentu, oznacza to, że metoda musi być specyficzna dla instancji (tzn. nie ma możliwości zdefiniowania tego na poziomie klasy). Zamiast tego możesz zdefiniować konkretną metodę podczas Klasy ' __init__ na przykład. Tutaj masz dostęp do atrybutów specyficznych dla instancji.

Jednym z podejść jest użycie functools.partial w celu uzyskania zaktualizowanej (konkretnej) wersji metody:

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')

Zauważ, że przy takim podejściu można tylko przejść odpowiadający argument według słowa kluczowego, ponieważ jeśli podałeś go według pozycji, spowodowałoby to konflikt w partial.

Innym podejściem jest zdefiniowanie i ustawienie metody w __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')

Pozwala to również na przekazywanie argumentu przez pozycję, jednak kolejność rozdzielczości metody staje się mniej widoczna (co może mieć wpływ na inspekcję IDE, na przykład, ale przypuszczam, że istnieją dla tego rozwiązania specyficzne dla IDE).

Innym podejściem byłoby stworzenie własnego typu dla tych rodzaj "instance attribute defaults" wraz ze specjalnym dekoratorem, który wykonuje odpowiednie wypełnienie argumentu 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
Author: a_guest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-17 13:37:01

"self" musi być przekazany jako pierwszy argument do dowolnej funkcji klasy, jeśli chcesz, aby zachowywały się jak niestatyczne metody.

Odnosi się do samego obiektu. Nie można przekazać" self " jako domyślny argument, ponieważ jego pozycja jest fix jako pierwszy argument.

W Twoim przypadku zamiast " formatowanie = self.Formatuj "użyj" formatowanie = None", a następnie przypisz wartość z kodu jak poniżej:

[edytuj]

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"

Uwaga: nie używaj "format" jako nazwy zmiennej, ponieważ jest to wbudowana funkcja w python

 1
Author: Yajushi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-15 06:04:02

Nie możesz uzyskać dostępu do self w definicji metody. Moje obejście Jest Takie -

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
 1
Author: amolk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-21 20:41:25

Zamiast tworzyć listę if-thens, która obejmuje domyślne argumenty, można użyć słownika 'defaults' i utworzyć nowe instancje klasy za pomocą 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]))

Rzucam to na początku każdej klasy, która tworzy instancje innej klasy jako domyślny argument. Unika on sprawdzania domyślnej wartości Pythona podczas kompilacji... Chciałbym czystsze podejście pythoniczne, ale lo'.

 0
Author: noPounch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-01 14:26:48