¿Cómo puedo usar un atributo de la instancia como argumento predeterminado para un método? [duplicate]

8 minutos de lectura

Avatar de usuario de Yugal Jindle
Jindle yugal

Quiero pasar un argumento predeterminado a un método de instancia usando el valor de un atributo de la instancia:

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

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

Al intentar eso, me sale el siguiente mensaje de error:

NameError: name 'self' is not defined

Quiero que el método se comporte así:

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

¿Cuál es el problema aquí, por qué esto no funciona? ¿Y cómo podría hacer que esto funcione?

  • no use format como nombre de variable, ya que es una función integrada en python.

    – Yajushi

    15 de noviembre de 2011 a las 5:43

  • Editando el error de self en process method

    – Yugal Jindle

    15 de noviembre de 2011 a las 5:54

  • Hay un proyecto de PEP en la tubería, lo que haría esto posible usando => en vez de =. Actualmente está programado para aparecer en Python 3.12.

    – Karl Knechtel

    5 de septiembre de 2022 a las 9:45

Avatar de usuario de Adam Wagner
Adán Wagner

Realmente no puede definir esto como el valor predeterminado, ya que el valor predeterminado se evalúa cuando se define el método, que es antes de que existan instancias. El patrón habitual es hacer algo como esto en su lugar:

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 sólo se utilizará si formatting es None.


Para demostrar el punto de cómo funcionan los valores predeterminados, vea este ejemplo:

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

Y la salida aquí:

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

Date cuenta cómo mk_default fue llamado solo una vez, ¡y eso sucedió antes de que se llamara a la función!

  • Yo creo mk_default fue llamado antes de que las funciones fueran llamadas desde foo=mk_default() lo llamó, debido al paréntesis. ¿No debería ser foo=mk_default? Entonces tu ejemplo podría cambiar a myfun("testing") seguido por myfun().

    – gary

    4 de enero de 2013 a las 2:28

  • Tenga en cuenta que formatting = formatting or self.formatestablecerá formatting a self.format si formatting es un valor falso, como 0. Esto me molesta. Una forma más segura es escribir formatting = formatting if formatting is not None else self.format o equivalente.

    – Godsmith

    2 oct 2017 a las 19:11

  • @Godsmith, ¡buen punto! He actualizado mi respuesta para dar cuenta de esto … ¡gracias!

    – Adán Wagner

    2 oct 2017 a las 20:09

Avatar de usuario de Karl Knechtel
Carlos Knechtel

En Python, el nombre self es no especial. Es solo una convención para el nombre del parámetro, por lo que hay un self parámetro en __init__. (De hecho, __init__ tampoco es muy especial, y en particular lo hace no en realidad crear el objeto… esa es una historia más larga)

C("abc").process() crea un C ejemplo, busca el process método en el C clase, y llama a ese método con el C instancia como el primer parámetro. Entonces terminará en el self parámetro si lo proporcionó.

Sin embargo, incluso si tuviera ese parámetro, no se le permitiría escribir algo como def process(self, formatting = self.formatting)porque self aún no está dentro del alcance en el punto en el que establece el valor predeterminado. En Python, el valor predeterminado de un parámetro se calcula cuando se compila la función y se “pega” a la función. (Esta es la misma razón por la que, si usa un valor predeterminado como []esa lista recordará los cambios entre las llamadas a la función).

¿Cómo podría hacer que esto funcione?

La forma tradicional es utilizar None como predeterminado, verifique ese valor y reemplácelo dentro de la función. Puede encontrar que es un poco más seguro hacer un valor especial para el propósito (un object instancia es todo lo que necesita, siempre que lo oculte para que el código de llamada no use la misma instancia) en lugar de None. De cualquier manera, debe verificar este valor con isno ==.

  • Su solución alternativa no cumple con el resultado deseado al usar Ninguno.

    – Yugal Jindle

    15 de noviembre de 2011 a las 5:56

  • Si None es un valor válido para formattingentonces tendrás que elegir otra cosa, como expliqué.

    – Karl Knechtel

    15 de noviembre de 2011 a las 6:01

avatar de usuario de a_guest
un invitado

Ya que quieres usar self.format como argumento predeterminado, esto implica que el método debe ser específico de la instancia (es decir, no hay forma de definir esto a nivel de clase). En su lugar, puede definir el método específico durante la clase’ __init__ Por ejemplo. Aquí es donde tiene acceso a los atributos específicos de la instancia.

Un enfoque es utilizar functools.partial para obtener una versión actualizada (específica) del método:

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

Tenga en cuenta que con este enfoque solo puede pasar el argumento correspondiente por palabra clave, ya que si lo proporcionó por posición, esto crearía un conflicto en partial.

Otro enfoque es definir y establecer el método en __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')

Esto también permite pasar el argumento por posición, sin embargo, el orden de resolución del método se vuelve menos evidente (lo que puede afectar la inspección de IDE, por ejemplo, pero supongo que hay soluciones específicas de IDE para eso).

Otro enfoque sería crear un tipo personalizado para este tipo de “valores predeterminados de atributos de instancia” junto con un decorador especial que realice el correspondiente getattr relleno de argumentos:

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

No puede acceder a sí mismo en la definición del método. Mi solución es esta:

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

Avatar de usuario de Yajushi
Yajushi

“self” debe pasarse como primer argumento a cualquier función de clase si desea que se comporten como métodos no estáticos.

se refiere al objeto mismo. No podría pasar “self” como argumento predeterminado, ya que su posición es fija como primer argumento.

En su caso, en lugar de “formatting=self.format”, use “formatting=None” y luego asigne el valor del código como se muestra a continuación:

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

Nota: no use “formato” como nombre de variable, porque es una función integrada en python

  • Bueno, corregí el self problema. Pero su respuesta no cumple con el resultado deseado.

    – Yugal Jindle

    15 de noviembre de 2011 a las 5:55

Avatar de usuario de noPounch
noPounch

En lugar de crear una lista de si-entonces que abarque sus argumentos predeterminados, se puede usar un diccionario ‘predeterminado’ y crear nuevas instancias de una clase usando 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]))

Lanzo esto al comienzo de cada clase que crea una instancia de otra clase como argumento predeterminado. Evita que Python evalúe el valor predeterminado en la compilación … Me encantaría un enfoque pitónico más limpio, pero he aquí.

  • Bueno, corregí el self problema. Pero su respuesta no cumple con el resultado deseado.

    – Yugal Jindle

    15 de noviembre de 2011 a las 5:55

¿Ha sido útil esta solución?