Variable becomes string without reason

Asked

Viewed 224 times

-3

I made a program where one should guess a number, and in the end it works normally, but I ended up encountering a somewhat strange event.

In the code I transform a string with a number inside, in a kind int, but the variable is again of the type string for no reason.

The code is this:

import random

lancar = 1

n_secreto = random.randint(1,20)
print(n_secreto)


def inicializar():

   print('Para sair digite "sair" ')
   return input("Digite um número entre 1 e 20 para jogar: ")


def valida_lancar(lancar):

    while lancar != int:

        if lancar == "s" :
            print("Jogo fechado")
            break

        else:
            try:
                # Aqui eu transformo em int
                lancar = int(lancar)
                print(type(lancar))
                break

            except ValueError:
                print()
                print(f"{lancar} Não é um input válido!")
                lancar = input("Digite um número entre 1 e 20 para jogar: ")


def testa_lancar(lancar):

    print(type(lancar))

    if lancar == n_secreto:
        print()
        print("Acertou")

    elif n_secreto != lancar and lancar != "s":
        print()
        print("Errou")

    else:
        lancar = "s"

while lancar != "s":

    lancar = inicializar()
    print(type(lancar))

    valida_lancar(lancar)

    # Ao chegar aqui a variável já é uma string novamente

    print(type(lancar))

    testa_lancar(lancar)

Putting testa_lancar(lancar) after the call valida_lancar(lancar), everything works normally. But I would still like to know why the variable turn string out of nowhere.

3 answers

5

Consider the code below:

lancar = 'abc'

def faz_algo(lancar):
    lancar = 1

faz_algo(lancar)
print(lancar) # aqui vai imprimir o que?

In his mind, he should print 1. After all, I created the variable lancar with the value 'abc', and passed it to the function faz_algo. But within the function I set her value to 1, then after the function performs, its value should be 1, right?

Wrong. The above code prints "abc" (can check out).


To understand what happens, there are a number of concepts to be understood. I will give a more "simplistic" explanation and try to be didactic without sticking to too many technical terms, and not even go into Python implementation details (but I will put some links for you to delve into the subject). The idea is to understand in general what is happening.

First, just think that the variable lancar created out of function nay is the same variable lancar which has been declared as a function parameter. They happen to have the same name, but this is circumstantial, after all, you could call the function in other ways, without having to create the variable lancar:

# sem usar variável
faz_algo('abc')

# usando variável com outro nome
x = 'abc'
faz_algo(x)

# usando uma outra função que retorna algum valor
faz_algo(outra_funcao()) # assumindo que outra_funcao() retorne alguma coisa

That is, the value that is passed to the function is copied to the parameter lancar, and what happens inside does not interfere with the variable lancar outside (if it exists, because in the above examples it does not exist, and this does not prevent the function to perform anyway).

I mean, when I do faz_algo(lancar), what happens is more or less the following:

  • the value of the variable lancar (external, which was created outside the function) is passed to the function faz_algo
  • this value is copied to the parameter lancar
  • even if within the function I assign another value to lancar, I am only changing his "internal version" (the one that exists inside the function), without interfering with the variable lancar external

As I said, it is a simplified explanation and without clinging to the correct terminology for all things, nor to the internal details of language implementation.

Basically, that’s why you’re having this behavior in your code. Within the function you convert the parameter lancar for int, but the external variable remains string (it is not affected by what happens within the function, since it is not the same variable - they only have the same name, but one exists only within the function and the other exists only outside).


Every rule has exceptions

Of course you can access the external variable (which in this case we call "global") within the function, as already explained another answer. Of course, there are cases where it is possible to change the data within the function, just by changing the type (like a list, for example):

def muda_lista(lista):
    lista.append(3)

x = [1, 2]
muda_lista(x)
print(x) # [1, 2, 3]

But it doesn’t work the same way if we assign another value to the list:

def muda_lista(lista):
    lista = [1, 2, 3]

x = [1, 2]
muda_lista(x)
print(x) # [1, 2]

Good, but maybe I’m already confusing more than helping. Leave this example of the lists aside and let’s get back to your case.


There are two concepts you should understand: passing by value and passing by reference. So well summarized, when you pass parameters to a function, you can pass only the values, and the function receives a copy of them, and whatever it does inside does not interfere with the values outside (this is the passage by value). And in the reference passage, it is possible to change the value within the function. To delve into the subject, I suggest starting around here (the question has examples in Java and C#, but serves to understand the general idea of the concept).

As for Python, there are those who argue that it has no passage by value or by reference, but a "third type" of passing parameters. I will not get into the merit of discussing the correct terminology (the documentation calls "pass by assignment"), but you can read here and here (Regardless of terminology, these links are interesting to better understand how it works). Anyway, to delve into the details of the language, I suggest reading also the Data Model of the same.


That said, we already know the problem of your code, which is to assume that the external variable could be changed within the function, just passing it as parameter. There are other problems as well, which is - in my opinion - complicate the algorithm for no reason (maybe not even break in so many functions), besides depending on the type of variable (and not its value) to determine program actions.

To another answer already gave a well simplified version of your code, for you see how it did not need to have complicated so much. I just wanted to add some more details.

A message from your code says: Para sair digite "sair". But in the code you check if it was typed only "s". So it was already inconsistent, because if it is typed "exit", it will not leave the program. A another answer suggested using startswith (before it is edited, has now been corrected), but this is not a good solution because the program will accept any text that starts with "s" (that is, if you type "sapo", "s abc 123" and anything else that starts with "s", it leaves the program), and it doesn’t seem to be quite what you want it to be.

Another option would be:

import random
import sys

segredo = random.randint(1,20)
print(segredo)

def get_palpite():
    try:
        print('Para sair, digite "sair"')
        entrada = input("Digite um número entre 1 e 20 para jogar: ")
        if entrada == 'sair':
            print("Jogo fechado")
            sys.exit() # sai do programa

        # não foi digitado "sair", tenta converter para número
        palpite = int(entrada)
        if 1 <= palpite <= 20:
            return palpite
        else: print(f'Erro: o número deve estar entre 1 e 20 e você digitou {palpite}')
    except ValueError:
        print(f"Erro: '{entrada}' não é um número")

while True:
    if get_palpite() == segredo:
        print("Acertou!")
    else:
        print("Errou!")

In the case, if entrada == 'sair' only exits the program if it is typed exactly "exit". If you want to accept also "EXIT", "Exit" and other lower case combinations, you can use:

if entrada.lower() == 'sair':

If you want to accept both "leave" and only "s", you can switch to:

if entrada in ('sair', 's'):

Or even (if you want to accept "S", "Exit", "EXIT", etc):

if entrada.lower() in ('sair', 's'):

I also checked the values of the guess before returning it (if it is not a number, or if it is but it is not between 1 and 20, I ask you to type again).

  • 1

    That’s what I wanted @Jeanextreme002 to have explained, but he wouldn’t listen.

  • @Augustovasques I understood where is the error of my answer, only I do not even know how to edit my answer because hkotsubo already answered in detail and as I said, I do not know much about this subject. I am in a dilemma between erasing or not my answer because it is not as if it were 100% wrong, and deleting the answer would erase the comments with their explanations with the subject of passing value and reference.

  • @Jeanextreme002 that explanation was an orientation for you to improve the answer, the other day I saw that you were upset by taking a few downvotes. But the comments were like a gift is your and you make the use you want, after I sent it I no longer control the fate .

  • Okay then, I’ll try to edit.

0

First of all, I’d like to say that in programming, nothing happens out of nowhere like magic. If something didn’t go as expected, it means there was a bug caused by some programmer. That being said, let’s understand what happens in your code :)

Maybe you think that by passing the variable lancar in the function call, her reference will also be passed. So you could manipulate it into function is not really... but you are wrong!

When you pass a variable in the call of a function, what you’re actually doing is just passing the value of the variable.

So you can understand better, if we have a variable x with the value 7, only the number 7 would be passed to the function and not the variable itself. Example:

# Abaixo o parâmetro "n" não recebe a referência da  
# sua variável, mas sim o valor dela.

def func(n): 
    print(n)

x = 7
func(x)

Hold on a second... If the function parameter receives only one copy, because I can change a list or other objects created outside the function from within the function?

Well this happens because by passing the list or other object as parameter, you are passing the memory address of that object. In short, the function parameter would not receive the object itself but rather the reference of the object and not the variable such as another answer explained.

def func(lista):

    # Alterou o objeto fora da função pois "lista" faz referência ao objeto criado.
    lista.append(5) 

    # Não altera o objeto pois aqui a "lista" perde a referência do 
    # objeto anterior e passa a ter um endereço novo.
    lista = [1, 2, 3]

lista = [7, 8, 9]

func(lista)
print(lista) # [7, 8, 9, 5] 

An analogy I just created is this::

Imagine that you have a drawer (variable) that stores tools (value) and you want to use these tools in your work. Instead of you taking the drawer along with the tools, you just take the tools.

That way you can not paint, destroy or do anything else with the drawer, you can just manipulate the tools that the drawer kept.

We can then say that the variable lancar within the function is nothing more or less than a copy of the variable outside the function, both of which have no connection at all.

This means that I cannot manipulate the variable outside the function from within the function ?

Of course you can and we’ll do it! Hold on and read the next part of the answer :)


Another thing you should know is that the variable lancar in and out of their function, they are different not only for the previous reason, but also because they are in scopes different!

While one is in the scope global the other is in the scope local. So your function doesn’t change the variable that’s outside, it just changes the variable that’s inside.

See this simple example below:

n = 0

def muda():
    n = 1
    print("Dentro:", n) # Dentro: 1

muda()
print("Fora:", n) # Fora: 0

To change the variable outside the function, you must use the declaration global, to tell Python that you want to use the scope variable global (scope outside the function).

Following the example above, the code would look like this:

n = 0

def muda():
    global n # Diz ao Python que quer usar a variável fora da função

    n = 1
    print("Dentro:", n) # Dentro: 1

muda()
print("Fora:", n) # Fora: 1

The problem is that in your code, you cannot simply add the global to its function. This is because its function already receives the parameter lancar that has the same name as its global variable (this generates a conflict).

What you should do in this case is return the new value of lancar created within the function with a return, or else remove the parameter from your function (which I do not recommend).

See what your code would look like:

Using the statement "global":

def valida_lancar():
    global lancar

    # Código ...

valida_lancar()

Using the "Return":

def valida_lancar(lancar):

    # Código ...

    return lancar

lancar = valida_lancar(lancar)

To put an end to this subject of scopes, a very interesting thing that we can also see, is that just as we cannot manipulate a variable in the global scope without using the declaration global, also we cannot have access to variables created within the function.

This is because all variables created within the function die in the function. See:

def func():
    n = 15

func()
print(n) 

# Gera um erro porque "n" não existe fora da função. 
# A variável "n" morreu após a função ser encerrada.

Another problem in your code that I might notice was on the parole below:

while lancar != int:

For now, this condition does not affect your program as it is not necessary since you use the break if the if whether an exception within the block is true or not try.

The problem is that if you write more code and eventually need this conditional, it will not work as it should.

This is because you check whether the value of lancar is just like the guy int. Maybe the condition you want is this:

while type(lancar) != int:

In addition to the problems I mentioned above, there is another problem regarding the option to quit. You tell the user that to quit, he must type "sair", but this does not work since in the code you check if what he typed was "s".

To fix the problem, update the information on print() or use a comparison operator to verify that the user input is equal to "sair". Example:

if lancar.lower() == "sair":
    # Código ...

Since I had a lot of time on my hands today, I did a redo on your code. Decrease functions and rename them to be more cohesive, fix bugs, etc.

I hope you like it and keep improving :)

import random

def imprime_resultado(jogada, n_sorteado):

    print("\nAcertou!\n") if jogada == n_sorteado else print("\nErrou!\n")

def obter_jogada():

    print('Para sair digite "sair" ')
    return input("Digite um número entre 1 e 20 para jogar: ")

def valida_jogada(jogada):
   
    try: return True if 1 <= int(jogada) <= 20 else False
    except: return False

while True:
    
    n_sorteado = random.randint(1, 20)
    jogada = obter_jogada()

    if jogada.lower() == "sair": break
    
    if valida_jogada(jogada):
        imprime_resultado(jogada, n_sorteado)
        
    else:
        print(f'\n"{jogada}" não é uma entrada válida!\n')
  • 1

    That explanation Primeiro de tudo, você deve saber que a variável lançar dentro da função e fora da sua função, são diferentes! Essas duas variáveis são diferentes, pois estão em escopos diferentes e não possuem ligação alguma.. Yes they are different but it doesn’t explain what really happens. AP is trying to pass a variable by reference and should be taught that the Python language does not support passing parameters by reference.

  • I might as well not. Python was my first language and I also didn’t know that you can’t change a variable in another scope just as I didn’t know about reference variables either. And as for not explaining what really happens, I left linked on the part of escopos an answer that explains this better.

  • 1

    I have no knowledge of many languages, so I may end up interpreting "passing parameters by reference" in a wrong way, but I will still ask. How so Python does not support ? Of course it is not possible to pass reference of variables of primitive type, but it is possible to pass objects.

  • 3

    In python the passage is made by assignment what it means is passed to function or method always a reference, even if the type is primitive or not, but this reference is passed by value. If you pass a mutable object by assignment you will be able to touch the properties and methods and the external references will be impacted but if you create a new object inside and try to replace in the attribute parameter there is a dissociation and the function parameter points to the new object and external references shall be dissociated...

  • 2

    ... If you pass an immutable object by assignment, which is this case, automatically the assignment will be dissociated from any external references then a change in the external assignment or reference passes beaten. The answer that lynched is good, but it is not the case of the question

  • In short, I change the object but I don’t change the variable itself. I get it, thanks for the explanation.

  • Thank you for asking and being interested in the subject.

  • @Augustovasques There are those who argue that Python has no passage by value or by reference, but a "third type" (defended idea here and here). Anyway, Jean, your answer speaks of the local variable created within the function, which is not the same as a parameter passed to it (a subtle difference, but important for the case of the question).

  • @hkotsubo was what I said, the mechanism is called Passage by Assignment. It is a hybridous model where the reference is passed by value.

  • @Augustovasques Oh yeah, it’s just that in the articles I’ve seen they use other names (because there doesn’t seem to be consensus on the exact name of the mechanism)

  • 1

    @hkotsubo, The term in English is Pass by Assignment which I translated as "Assignment Pass".

  • 1

    @Augustovasques Wow, I had not found this in the documentation. I will update my reply, thank you!

  • I already understood that it’s not about scope and I put it up there with the explanation. Not as detailed as hkotsubo, but I think I explained it correctly and still with a small analogy. Are you sure you read everything ?

  • I would like you to re-evaluate the reply, thank you.

  • Improved @Jeanextreme002. But as I said I had already given you the positive, so it will depend on the other voters.

  • 1

    A better analogy than the drawer: I have a paper with written things. When calling the function, it takes a copy and I get the original. The function can scrawl the copy at will that the original remains intact. But in the case of lists, imagine that on paper has the address where I live and the function sends something to this address. No matter if it has the original or the copy, the delivery will be made in the same place. More or less that...

Show 11 more comments

-2

It turns out that the function input() returns a type variable str and this needs to be treated/converted properly to avoid this kind of confusion.

Simplifying your idea:

import random

segredo = random.randint(1,20)
print(segredo)

while True:
    try:
        # A funcao input() sempre retorna str
        entrada = input("Digite um número entre 1 e 20 para jogar: ")

        # Comparacao de string
        if entrada == 's':
            print("Jogo fechado")
            break

        # Conversao de str para int
        # Se nao for possivel, lança
        # uma exceção do tipo ValueError
        palpite = int(entrada)

        # Comparacao do palpite com o segredo
        if palpite == segredo:
            print("Acertou!")
        else:
            print("Errou!")

    except ValueError:
        print(f"Erro: '{entrada}' Nao eh um input valido!")

Possible Exit:

6
Digite um número entre 1 e 20 para jogar: 3
Errou!
Digite um número entre 1 e 20 para jogar: 5
Errou!
Digite um número entre 1 e 20 para jogar: 6
Acertou!
Digite um número entre 1 e 20 para jogar: s
Jogo fechado
  • 1

    I’m sure he knows that the input() returns a string and its answer does not explain why the variable’s value remains a string.

  • 1

    Code complexity can also be a problem! My answer was drafted taking into account the PEP-20. The complexity of the code presented is sufficient to confuse the questioner to the point that he cannot debug it!

  • @Jeanextreme002 and Lacobus , I gave the upvote to both because they are both right and both are wrong. First upvote was because the AP algorithm is spaghetti and its proper structuring needs to be addressed. The second upvote was due to the identification of the dissociation between the global variable and the parameter, although Jean the concept he used to identify dissociation is mistaken so in his answer I will leave an orientation.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.