evennia/docs/source/Howto/Starting/Python-basic-introduction.md
2020-06-21 00:02:14 +02:00

13 KiB

Starting to code Evennia

prev lesson | next lesson

Time to dip our toe into some coding! Evennia is written and extended in Python, which is a mature and professional programming language that is very fast to work with.

That said, even though Python is widely considered easy to learn, we can only cover the most immediately important aspects of Python in this series of starting tutorials. Hopefully we can get you started but then you'll need to continue learning from there. See our link section for finding more reference material and dedicated Python tutorials.

While this will be quite basic if you are an experienced developer, you may want to at least stay around for the first few sections where we cover how to run Python from inside Evennia.

First, if you were quelling yourself to play the tutorial world, make sure to get your superuser powers back:

   unquell 

Evennia Hello world

The py Command (or !, which is an alias) allows you as a superuser to execute raw Python from in- game. This is useful for quick testing. From the game's input line, enter the following:

> py print("Hello World!")

    The lines with a `>` indicate input to enter in-game, while the lines below are the 
    expected return from that input.

You will see

> print("Hello world!")
Hello World!

To understand what is going on: some extra info: The print(...) function is the basic, in-built way to output text in Python. We are sending "Hello World" as an argument to this function. The quotes "..." mean that you are inputting a string (i.e. text). You could also have used single-quotes '...', Python accepts both.

The print command is a standard Python structure. We can use that here in the py command since we can se the output. It's great for debugging and quick testing. But if you need to send a text to an actual player, print won't do, because it doesn't know who to send to. Try this:

> py me.msg("Hello world!")
Hello world!

This looks the same as the print result, but we are now actually messaging a specific object, me. The me is a shortcut to 'us', the one running the py command. It is not some special Python thing, but something Evennia just makes available in the py command for convenience (self is an alias).

The me is an example of an Object instance. Objects are fundamental in Python and Evennia. The me object also contains a lot of useful resources for doing things with that object. We access those resources with '.'.

One such resource is msg, which works like print except it sends the text to the object it is attached to. So if we, for example, had an object you, doing you.msg(...) would send a message to the object you.


    Function:
        Stand-alone in a python module, like `print()`
    Method:
        Something that sits "on" an object, like `.msg()`

For now, print and me.msg behaves the same, just remember that print is mainly used for debugging and .msg() will be more useful for you in the future.

For fun, try printing other things. Also try this:

> py self.msg("|rThis is red text!")

Adding that |r at the start will turn our output red. Enter the command color ansi or color xterm to see which colors are available.

Importing code from other modules

As we saw in the previous section, we could use me.msg to access the msg method on me. This use of the full-stop character is used to access all sorts of resources, including that in other Python modules. If you've been following along, this is what we've referred to as the "Python path".

Keep your game running, then open a text editor of your choice. If your game folder is called mygame, create a new text file test.py in the subfolder mygame/world. This is how the file structure should look:

mygame/
    world/
        test.py

For now, only add one line to test.py:

print("Hello World!")
   
    This is a text file with the `.py` file ending. A module
    contains Python source code and from within Python one can 
    access its contents by importing it via its python-path.

Don't forget to save the file. We just created our first Python module! To use this in-game we have to import it. Try this:

> py import world.test
Hello World

If you make some error (we'll cover how to handle errors below), fix the error in the module and run the reload command in-game for your changes to take effect.

So importing world.test actually means importing world/test.py. Think of the period . as replacing / (or \ for Windows) in your path. The .py ending of test.py is also never included in this "Python-path", but only files with that ending can be imported this way. Where is mygame in that Python-path? The answer is that Evennia has already told Python that your mygame folder is a good place to look for imports. So we don't include mygame in the path - Evennia handles this for us.

When you import the module, the top "level" of it will execute. In this case, it will immediately print "Hello World".

If you look in the folder you'll also often find new files ending with .pyc. These are compiled Python binaries that Python auto-creates when running code. Just ignore them, you should never edit those anyway.

Now try to run this a second time:

> py import world.test

You will not see any output this second time or any subsequent times! This is not a bug. Rather it is because Python is being clever - it stores all imported modules and to be efficient it will avoid importing them more than once. So your print will only run the first time, when the module is first imported.

Try this:

> reload 

And then

> py import world.test
Hello World!

Now we see it again. The reload wiped the server's memory of what was imported, so it had to import it anew. You'd have to do this every time you wanted the print to show though, which is not very useful.

We'll get back to more advanced ways to import code in later tutorial sections - this is an important topic. But for now, let's press on and resolve this particular problem.

Parsing Python errors

Running print only on import is not too helpful. We want to print whenever we like to! Go back to your test.py file and erase the single print statement you had. Replace it with this instead:

me.msg("Hello World!")

As you recall we used this with py earlier - it echoed "Hello World!" in-game. Save your file and reload your server - this makes sure Evennia knows to re-import it (with our new, fresh code this time).

To test it, import it using py as before

 > py import world.test

No go - this time you get an error!

File "./world/test.py", line 1, in <module>
    me.msg("Hello world!")
NameError: name 'me' is not defined

    In regular use, tracebacks will often appear in the log rather than
    in the game. Use `evennia --log` to view the log in the terminal. Make
    sure to scroll back if you expect an error and don't see it. Use 
    `Ctrl-C` (or `Cmd-C` on Mac) to exit the log-view.

This is called a traceback. Python's errors are very friendly and will most of the time tell you exactly what and where things go wrong. It's important that you learn to parse tracebacks so you know how to fix your code.

A traceback is to be read from the bottom up:

  • (line 3) An error of type NameError is the problem ...
  • (line 3) ... more specifically it is due to the variable me not being defined.
  • (line 2) This happened on the line me.msg("Hello world!") ...
  • (line 1) ... which is on line 1 of the file ./world/test.py.

In our case the traceback is short. There may be many more lines above it, tracking just how different modules called each other until the program got to the faulty line. That can sometimes be useful information, but reading from the bottom is always a good start.

The NameError we see here is due to a module being its own isolated thing. It knows nothing about the environment into which it is imported. It knew what print is because that is a special reserved Python keyword. But me is not such a reserved word (as mentioned, it's just something Evennia came up with for convenience in the py command). As far as the module is concerned me is an unfamiliar name, appearing out of nowhere. Hence the NameError.

Our first own function

Let's see if we can resolve that NameError from the previous section. We know that me is defined at the time we use the py command. We know this because if we do py me.msg("Hello World!") directly in-game it works fine. What if we could send that me to the test.py module so it knows what it is? One way to do this is with a function.

Change your mygame/world/test.py file to look like this:

def hello_world(who):
    who.msg("Hello World!")

As we are moving to multi-line Python code, there are some important things to remember:

  • Capitalization matters in Python. It must be def and not DEF, who is not the same as Who.
  • Indentation matters in Python. The second line must be indented or it's not valid code. You should also use a consistent indentation length. We strongly recommend that you, for your own sanity's sake, set up your editor to always indent 4 spaces (not a single tab-character) when you press the TAB key.

So about that function. Line 1:

  • def is short for "define" and defines a function (or a method, if sitting on an object). This is a reserved Python keyword; try not to use these words anywhere else.
  • A function name can not have spaces but otherwise we could have called it almost anything. We call it hello_world. Evennia follows Python's standard naming style with lowercase letters and underscores. We recommend you do the same.
  • who is what we call the argument to our function. Arguments are variables passed to the function. We could have named it anything and we could also have multiple arguments separated by commas. What who is depends on what we pass to this function when we call it later (hint: we'll pass me to it).
  • The colon (:) at the end of line 1 indicates that the header of the function is complete.

Line 2:

  • The indentation marks the beginning of the actual operating code of the function (the function's body). If we wanted more lines to belong to this function those lines would all have to start at least at this indentation level.
  • In the function body we take the who argument and treat it as we would have treated me earlier- we expect it to have a .msg method we can use to send "Hello World" to.

Now let's try this out. First reload your game to have it pick up our updated Python module when we reload.

> reload
> py import world.test

Nothing happened! That is because the function in our module won't do anything just by importing it. It will only act when we call it. So we need to first import the module and then access the function within:

> py import world.test ; world.test.hello_world(me)
Hello world!

There is our "Hello World"! Using ; is the way to put multiple Python-statements on one line.


    A common issue is that some MUD clients use the semi-colon `;` for their own purposes, 
    namely to separate client-inputs. If so, you'll get a `NameError` above, stating that 
    `world` is not defined. Check so you understand why this is! Most clients allow you to 
    remap to use some other separator than `;`. You can use the Evennia web client if this 
    is problem remains.

So what happened there? First we imported world.test as usual. But this time we continued and accessed the hello_world function inside the newly imported module.

We call the function with me, which becomes the who variable we use inside the hello_function (they are be the same object). And since me.msg works, so does who.msg inside the function.

Extra Credit: As an exercise, try to pass something else into hello_world. Try for example to pass the number 5 or the string "foo". You'll get errors telling you that they don't have the attribute msg. They don't care about me itself not being a string or a number. If you are familiar with other programming languages (especially C/Java) you may be tempted to start validating who to make sure it's of the right type before you send it. This is usually not recommended in Python. Python philosophy is to handle the error if it happens rather than to add a lot of code to prevent it from happening. See duck typing and the concept of Leap before you Look.

This gives you some initial feeling for how to run Python and import Python modules. We also tried to put a module in module/world/. Now let's look at the rest of the stuff you've got inside that mygame/ folder ...

prev lesson | next lesson