¿Cómo evitar el 'self' explícito en Python?

131

He estado aprendiendo Python siguiendo algunos tutoriales de pygame .

Allí encontré un uso extenso de la palabra clave self , y viniendo de un fondo principalmente de Java, encuentro que sigo olvidando escribir self . Por ejemplo, en lugar de self.rect.centerxescribir rect.centerx, porque, para mí, rect ya es una variable miembro de la clase.

El paralelo de Java en el que puedo pensar para esta situación es tener que prefijar todas las referencias a las variables miembro con esto .

¿Estoy atascado con el prefijo de todas las variables miembro con self , o hay alguna forma de declararlas que me permita evitar tener que hacerlo?

Incluso si lo que sugiero no es pitónico , me gustaría saber si es posible.

He echado un vistazo a estas preguntas SO relacionadas, pero no responden lo que busco:

bguiz
fuente
55
Vengo de un fondo de Java y lo encuentro natural, pero agrego explícitamente "esto" a cada llamada para aclarar que me estoy refiriendo a una variable de instancia.
Uri
44
¿Está familiarizado con la convención de un m_prefijo para todos los nombres de miembros observados por algunos programadores de C ++ / Java? El uso de self.ayuda a la legibilidad de manera similar. Además, debe leer dirtsimple.org/2004/12/python-is-not-java.html .
Beni Cherniavsky-Paskin
2
Aunque generalmente m_se usa solo para miembros de datos no públicos no estáticos (al menos en C ++).
@Beni gran artículo vinculado, y sí, de hecho, sigo la convención de usar mVariableName, para las variables miembro, al codificar en Java. Creo que el comentario de @ Anurag lo resume bastante bien, por lo que un desarrollador de Java debe hacer al aprender Python.
bguiz
11
Entonces, ¿cómo es que todos le dicen a OP por qué usar self es bueno / necesario / etc. pero nadie dice si se puede evitar o no de alguna manera? Incluso si por algún tipo de truco sucio?
einpoklum

Respuestas:

99

Python requiere especificarse a sí mismo. El resultado es que nunca hay confusión sobre qué es un miembro y qué no, incluso sin la definición de clase completa visible. Esto lleva a propiedades útiles, como: no puede agregar miembros que accidentalmente sombrean a los no miembros y, por lo tanto, rompen el código.

Un ejemplo extremo: puede escribir una clase sin saber qué clases base podría tener, y siempre saber si está accediendo a un miembro o no:

class A(some_function()):
  def f(self):
    self.member = 42
    self.method()

Ese es el código completo ! (alguna_función devuelve el tipo utilizado como base).

Otro, donde los métodos de una clase se componen dinámicamente:

class B(object):
  pass

print B()
# <__main__.B object at 0xb7e4082c>

def B_init(self):
  self.answer = 42
def B_str(self):
  return "<The answer is %s.>" % self.answer
# notice these functions require no knowledge of the actual class
# how hard are they to read and realize that "members" are used?

B.__init__ = B_init
B.__str__ = B_str

print B()
# <The answer is 42.>

Recuerde, ambos ejemplos son extremos y no los verá todos los días, ni le sugiero que a menudo escriba un código como este, pero muestran claramente aspectos de ser requeridos explícitamente.


fuente
44
Gracias. Esta respuesta da en el clavo porque también explica los beneficios del uso self.
bguiz 01 de
2
@Roger Pate: Deje de editar mi pregunta para eliminar Python de ella. Creo que pertenece allí. (y gracias por la respuesta!)
bguiz
3
@bguiz: es tan convencional no duplicar las etiquetas en el título. Sin embargo, cuando edité hace 2 días, no te vi revertir el título hace 7 meses.
1
Sería bueno si uno mismo pudiera reducirse a un solo personaje.
dwjohnston
55
Esa es en realidad una razón bastante tonta. Python podría no aceptar el sombreado y requerir que lo declare específicamente, es decir, 'bendiga' su campo de sombreado con algún tipo de __shadow__ myFieldName. Eso también evitaría el sombreado accidental, ¿no?
einpoklum
39

Las respuestas anteriores son básicamente variantes de "no puedes" o "no deberías". Si bien estoy de acuerdo con el último sentimiento, la pregunta técnicamente aún no tiene respuesta.

Además, existen razones legítimas por las cuales alguien podría querer hacer algo similar a lo que se está haciendo la pregunta real. Una cosa con la que me encuentro a veces son las largas ecuaciones matemáticas donde el uso de nombres largos hace que la ecuación sea irreconocible. Aquí hay un par de formas de cómo podría hacer esto en un ejemplo enlatado:

import numpy as np
class MyFunkyGaussian() :
    def __init__(self, A, x0, w, s, y0) :
        self.A = float(A)
        self.x0 = x0
        self.w = w
        self.y0 = y0
        self.s = s

    # The correct way, but subjectively less readable to some (like me) 
    def calc1(self, x) :
        return (self.A/(self.w*np.sqrt(np.pi))/(1+self.s*self.w**2/2)
                * np.exp( -(x-self.x0)**2/self.w**2)
                * (1+self.s*(x-self.x0)**2) + self.y0 )

    # The correct way if you really don't want to use 'self' in the calculations
    def calc2(self, x) :
        # Explicity copy variables
        A, x0, w, y0, s = self.A, self.x0, self.w, self.y0, self.s
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

    # Probably a bad idea...
    def calc3(self, x) :
        # Automatically copy every class vairable
        for k in self.__dict__ : exec(k+'= self.'+k)
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

g = MyFunkyGaussian(2.0, 1.5, 3.0, 5.0, 0.0)
print(g.calc1(0.5))
print(g.calc2(0.5))
print(g.calc3(0.5))

El tercer ejemplo, es decir, usar for k in self.__dict__ : exec(k+'= self.'+k)es básicamente lo que la pregunta realmente está pidiendo, pero permítanme aclarar que no creo que sea una buena idea.

Para obtener más información y formas de iterar a través de variables de clase, o incluso funciones, vea las respuestas y la discusión de esta pregunta . Para una discusión sobre otras formas de nombrar dinámicamente variables, y por qué esto generalmente no es una buena idea, vea esta publicación de blog .

ACTUALIZACIÓN: Parece que no hay forma de actualizar dinámicamente o cambiar locales en una función en Python3, por lo que calc3 y variantes similares ya no son posibles. La única solución compatible con python3 que se me ocurre ahora es usar globals:

def calc4(self, x) :
        # Automatically copy every class variable in globals
        globals().update(self.__dict__)
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

Lo cual, nuevamente, sería una práctica terrible en general.

argentum2f
fuente
44
¡Brillante! Esta es la respuesta más correcta (¿solo?). +1 También da una razón práctica única para hacerlo. + 1i
David Lotts
Después de crear una clase y mover el código dentro de ella: ahora ya no se reconocen todos los métodos y variables (no uno mismo ...) Me recuerda otra razón más por la que Python no me llevo bien. Gracias por esto como una idea. No soluciona el problema / dolor de cabeza / ilegibilidad en general, pero proporciona una solución modesta.
javadba
¿Por qué no simplemente actualizar en localslugar de usar exec?
Nathan
Intenté locals().update(self.__dict__)en Python 2 y 3, pero no funcionó. En python3, incluso el truco 'exec' ya no es una opción. Por otro lado, globals().update(self.__dict__)funciona, pero sería una práctica terrible en general.
argentum2f
26

En realidad selfno es una palabra clave, es solo el nombre dado convencionalmente al primer parámetro de los métodos de instancia en Python. Y ese primer parámetro no se puede omitir, ya que es el único mecanismo que tiene un método para saber en qué instancia de su clase se está llamando.

Michał Marczyk
fuente
1
Esta respuesta, en especial la segunda frase, es mucho más útil que la respuesta aceptada para mí porque puedo saber 'auto explícita' es sólo una de las limitaciones en Python y no evitables
QuestionDriven
21

Puedes usar el nombre que quieras, por ejemplo

class test(object):
    def function(this, variable):
        this.variable = variable

o incluso

class test(object):
    def function(s, variable):
        s.variable = variable

pero está atrapado con el uso de un nombre para el alcance.

No recomiendo que uses algo diferente a ti mismo a menos que tengas una razón convincente, ya que lo haría extraño para los pitonistas experimentados.

Esteban Küber
fuente
26
¡Puedes hacer esto, pero no lo hagas ! No hay razón para hacer que su código sea más extraño de lo que debe ser. Sí, puede llamarlo de cualquier forma, pero la convención es llamarlo self, y debe seguir la convención. Hará que su código sea más fácil de entender para cualquier programador experimentado de Python que lo vea. (Esto incluye, dentro de seis meses, tratando de averiguar lo que hace su programa de edad!)
steveha
3
Aún más alienígena:def function(_, variable): _.variable = variable
Bob Stein
1
@ BobStein-VisiBone aún más alienígena:def funcion(*args): args[0].variable = args[1]
Aemyl
44
@steveha él no lo recomienda, esta información es muy útil para personas como yo que no sabían que puedes usar palabras clave diferentes a las tuyas y se preguntan por qué se pasa un objeto de la propia clase.
Sven van den Boogaart el
> "más raro". Python es extraño, especialmente sus estructuras de clases y este uso de self es un canario para apoyar la legibilidad anti. Sé que muchos defensores vendrán después de esto, pero eso no cambia la veracidad.
javadba
9

Sí, siempre debe especificar self, porque explícito es mejor que implícito, de acuerdo con la filosofía de Python.

También descubrirá que la forma en que programa en python es muy diferente de la forma en que programa en java, por lo tanto, el uso de selftiende a disminuir porque no proyecta todo dentro del objeto. Más bien, hace un mayor uso de la función de nivel de módulo, que se puede probar mejor.

por cierto. Al principio lo odiaba, ahora odio lo contrario. lo mismo para el control de flujo impulsado por sangría.

Stefano Borini
fuente
2
"Se hace un mayor uso de la función de nivel de módulo, que se puede probar mejor" es dudoso, y tendría que estar en total desacuerdo. Es cierto que no está obligado a hacer de todo un método (estático o no) de alguna clase, independientemente de si es "lógicamente a nivel de módulo", pero eso no tiene nada que ver con uno mismo y no tiene ningún impacto significativo en probando de una forma u otra (para mí).
Surge de mi experiencia. No lo sigo como un mantra cada vez, pero hace que las cosas sean más fáciles de probar si pones algo que no requiere acceso a la variable miembro como un método separado e independiente. sí, separa la lógica de los datos, lo cual sí, está en contra de OOP, pero están juntos, solo a nivel de módulo. No le doy a uno u otro la "mejor" marca, es solo cuestión de gustos. A veces me encuentro especificando métodos de clase que no tienen nada que ver con la clase en sí, ya que no se tocan selfde ninguna manera. Entonces, ¿qué sentido tiene tenerlos en la clase?
Stefano Borini
No estoy de acuerdo con ambas partes (parece que uso "no métodos" con menos frecuencia que en otros idiomas), pero "lo que se puede probar mejor" implica que una forma es superior a la otra (es así como yo leerlo? no parece probable), aunque no encuentro soporte para eso en mi experiencia. Tenga en cuenta que no estoy diciendo que siempre deba usar uno u otro, solo estoy diciendo que los métodos y los que no son métodos también se pueden probar.
55
Sí, por supuesto que puede, pero el enfoque es diferente. Un objeto tiene un estado, un método de nivel de módulo no. Si descubre que una prueba falla mientras prueba un método de nivel de clase, dos cosas podrían haber estado mal: 1) el estado del objeto en el momento de la llamada 2) el método en sí. Si tiene un método de nivel de módulo sin estado, solo puede ocurrir el caso 2. Movió la configuración desde el objeto (donde se trata de un recuadro negro en lo que respecta a la prueba, ya que se rige por la lógica eventualmente compleja dentro del objeto) a la prueba. Está reduciendo la complejidad y manteniendo un control más estricto de la configuración.
Stefano Borini
1
"Si tiene un método de nivel de módulo sin estado", ¿qué pasa con un método de nivel de módulo con estado? Todo lo que me está diciendo es que las funciones sin estado son más fáciles de probar que las con estado, y estoy de acuerdo con eso, pero no tiene nada que ver con métodos versus no métodos. Vea el autoparámetro como exactamente eso: solo otro parámetro para la función.
4

El "self" es el marcador de posición convencional de la instancia de objeto actual de una clase. Se utiliza cuando desea hacer referencia a la propiedad, el campo o el método del objeto dentro de una clase como si se estuviera refiriendo a "sí mismo". Pero para acortar, alguien en el ámbito de la programación de Python comenzó a usar "self", otros reinos usan "this" pero lo hacen como una palabra clave que no puede ser reemplazada. Prefiero usar "its" para aumentar la legibilidad del código. Es una de las cosas buenas de Python: tiene la libertad de elegir su propio marcador de posición para la instancia del objeto que no sea "self". Ejemplo para uno mismo:

class UserAccount():    
    def __init__(self, user_type, username, password):
        self.user_type = user_type
        self.username = username            
        self.password = encrypt(password)        

    def get_password(self):
        return decrypt(self.password)

    def set_password(self, password):
        self.password = encrypt(password)

Ahora reemplazamos 'self' con 'its':

class UserAccount():    
    def __init__(its, user_type, username, password):
        its.user_type = user_type
        its.username = username            
        its.password = encrypt(password)        

    def get_password(its):
        return decrypt(its.password)

    def set_password(its, password):
        its.password = encrypt(password)

¿Cuál es más legible ahora?

LEMUEL ADANE
fuente
por qué no solo s(o alguna otra letra) en lugar deits
javadba
'its' tiene un significado y 's' no.
LEMUEL ADANE
stiene el mismo significado: un alias para la instancia de clase. tendría que buscar lo que itssignifica en el contexto de la misma manera
javadba
Ambas no son legibles para mí. Todavía es ilógico obtener "usted mismo" como un parámetro externo, no tiene ningún sentido.
Cesar
3

self es parte de la sintaxis de python para acceder a los miembros de los objetos, así que me temo que te quedas atascado

Charles Ma
fuente
2
self es una forma de decirle al modificador de acceso sin realmente usar uno. +1
Perpetualcoder
1

En realidad, puedes usar la receta "Yo implícito" de la presentación de Armin Ronacher "5 años de malas ideas" (google it).

Es una receta muy inteligente, como casi todo de Armin Ronacher, pero no creo que esta idea sea muy atractiva. Creo que preferiría explícitamente esto en C # / Java.

Actualizar. Enlace a "receta de mala idea": https://speakerdeck.com/mitsuhiko/5-years-of-bad-ideas?slide=58

Alex Yu
fuente
¿Es este el enlace al que se refiere a lo que se refería? Si es así, por favor incluya en su respuesta
reubenjohn
No, la "mala idea" de Armin parece más divertida para mi gusto. Incluí enlace.
Alex Yu
Antes de hacer clic en el enlace de la receta, tenga en cuenta que esto lo hace implícito en la lista de parámetros def method(<del> self </del> ), pero self.variableaún es necesario con este truco inteligente.
David Lotts
0

Sí, el auto es tedioso. Pero, ¿es mejor?

class Test:

    def __init__(_):
        _.test = 'test'

    def run(_):
        print _.test

fuente
10
_tiene un significado especial en el shell de Python, donde contiene el último valor devuelto. Es seguro usarlo así, pero potencialmente confuso; Lo evitaría
Cairnarvon el
Sin eso no mejor, pero por qué no es una sola letra en lugar, por ejemplo s, o m(a C ++ mímica)
javadba
0

De: Self Hell - Más funciones con estado.

... un enfoque híbrido funciona mejor. Todos los métodos de clase que realmente hacen cálculos deben trasladarse a cierres, y las extensiones para limpiar la sintaxis deben mantenerse en clases. Rellene los cierres en clases, tratando la clase como un espacio de nombres. Los cierres son esencialmente funciones estáticas, por lo que no requieren selfs *, incluso en la clase ...

usuario5554473
fuente
Los cierres están bien para casos de uso pequeños, pero el uso extendido de ellos aumentará la sobrecarga de memoria de un programa en gran medida (ya que está utilizando OO basado en prototipos en lugar de OO basado en clases, por lo que cada objeto requiere su propio conjunto de funciones en lugar de mantener un conjunto común de funciones en una clase). Además, evitará que pueda usar métodos mágicos / de inmersión (por ejemplo, __str__y similares) ya que estos se invocan de una manera diferente a los métodos normales.
Dunas
0

Creo que sería más fácil y más legible si hubiera una declaración "miembro", así como "global", para que pueda decirle al intérprete cuáles son los objetos miembros de la clase.

Pablo Villar
fuente