Further work on the python tut

This commit is contained in:
Griatch 2020-07-01 23:54:26 +02:00
parent 221caf5187
commit 131a9f89aa
6 changed files with 325 additions and 18 deletions

View file

@ -1,120 +0,0 @@
# Execute Python Code
The `@py` command supplied with the default command set of Evennia allows you to execute Python
commands directly from inside the game. An alias to `@py` is simply "`!`". *Access to the `@py`
command should be severely restricted*. This is no joke - being able to execute arbitrary Python
code on the server is not something you should entrust to just anybody.
@py 1+2
<<< 3
## Available variables
A few local variables are made available when running `@py`. These offer entry into the running
system.
- **self** / **me** - the calling object (i.e. you)
- **here** - the current caller's location
- **obj** - a dummy [Object](../../Component/Objects) instance
- **evennia** - Evennia's [flat API](../../Evennia-API) - through this you can access all of Evennia.
For accessing other objects in the same room you need to use `self.search(name)`. For objects in
other locations, use one of the `evennia.search_*` methods. See [below](Execute-Python-Code#finding-
objects).
## Returning output
This is an example where we import and test one of Evennia's utilities found in
`src/utils/utils.py`, but also accessible through `ev.utils`:
@py from ev import utils; utils.time_format(33333)
<<< Done.
Note that we didn't get any return value, all we where told is that the code finished executing
without error. This is often the case in more complex pieces of code which has no single obvious
return value. To see the output from the `time_format()` function we need to tell the system to
echo it to us explicitly with `self.msg()`.
@py from ev import utils; self.msg(str(utils.time_format(33333)))
09:15
<<< Done.
> Warning: When using the `msg` function wrap our argument in `str()` to convert it into a string
above. This is not strictly necessary for most types of data (Evennia will usually convert to a
string behind the scenes for you). But for *lists* and *tuples* you will be confused by the output
if you don't wrap them in `str()`: only the first item of the iterable will be returned. This is
because doing `msg(text)` is actually just a convenience shortcut; the full argument that `msg`
accepts is something called an *outputfunc* on the form `(cmdname, (args), {kwargs})` (see [the
message path](Messagepath) for more info). Sending a list/tuple confuses Evennia to think you are
sending such a structure. Converting it to a string however makes it clear it should just be
displayed as-is.
If you were to use Python's standard `print`, you will see the result in your current `stdout` (your
terminal by default, otherwise your log file).
## Finding objects
A common use for `@py` is to explore objects in the database, for debugging and performing specific
operations that are not covered by a particular command.
Locating an object is best done using `self.search()`:
@py self.search("red_ball")
<<< Ball
@py self.search("red_ball").db.color = "red"
<<< Done.
@py self.search("red_ball").db.color
<<< red
`self.search()` is by far the most used case, but you can also search other database tables for
other Evennia entities like scripts or configuration entities. To do this you can use the generic
search entries found in `ev.search_*`.
@py evennia.search_script("sys_game_time")
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
(Note that since this becomes a simple statement, we don't have to wrap it in `self.msg()` to get
the output). You can also use the database model managers directly (accessible through the `objects`
properties of database models or as `evennia.managers.*`). This is a bit more flexible since it
gives you access to the full range of database search methods defined in each manager.
@py evennia.managers.scripts.script_search("sys_game_time")
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
The managers are useful for all sorts of database studies.
@py ev.managers.configvalues.all()
<<< [<ConfigValue: default_home]>, <ConfigValue:site_name>, ...]
## Testing code outside the game
`@py` has the advantage of operating inside a running server (sharing the same process), where you
can test things in real time. Much of this *can* be done from the outside too though.
In a terminal, cd to the top of your game directory (this bit is important since we need access to
your config file) and run
evennia shell
Your default Python interpreter will start up, configured to be able to work with and import all
modules of your Evennia installation. From here you can explore the database and test-run individual
modules as desired.
It's recommended that you get a more fully featured Python interpreter like
[iPython](http://ipython.scipy.org/moin/). If you use a virtual environment, you can just get it
with `pip install ipython`. IPython allows you to better work over several lines, and also has a lot
of other editing features, such as tab-completion and `__doc__`-string reading.
$ evennia shell
IPython 0.10 -- An enhanced Interactive Python
...
In [1]: import evennia
In [2]: evennia.managers.objects.all()
Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]
See the page about the [Evennia-API](../../Evennia-API) for more things to explore.

View file

@ -1,6 +1,9 @@
# Overview of your new Game Dir
[prev lesson](Python-basic-introduction) | [next lesson]()
[prev lesson](Python-basic-introduction) | [next lesson](Python-classes-and-objects)
Next we will take a little detour to look at the _Tutorial World_. This is a little solo adventure
that comes with Evennia, a showcase for some of the things that are possible.
Now we have 'run the game' a bit and started with our forays into Python from inside Evennia.
It is time to start to look at how things look 'outside of the game'. Let's do a tour of your game-dir
@ -205,4 +208,4 @@ people change and re-structure this in various ways to better fit their ideas.
tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different
equipment, stats and looks.
[prev lesson](Python-basic-introduction) | [next lesson]()
[prev lesson](Python-basic-introduction) | [next lesson](Python-classes-and-objects)

View file

@ -1,12 +1,313 @@
# Python basic tutorial part two
# Continuing on with Python and Evennia
[In the first part](Part1/Python-basic-introduction) of this Python-for-Evennia basic tutorial we learned
[prev lesson](Gamedir-Overview) | [next lesson]()
We have now learned how to run some simple Python code from inside (and outside) your game server.
We have also taken a look at what our game dir looks and what is where. Now we'll start to use it.
## Importing
No one writes something as big as an online game in one single huge file. Instead one breaks up the
code into separate files (modules). Each module is dedicated to different purposes. Not only does
it make things cleaner, organized and easier to understand. It also makes it easier to re-use code -
you just import the resources you need and know you only get just what you requested. This makes
it much easier to find errors and to know what code is good and which has issues.
> Evennia itself uses your code in the same way - you just tell it where a particular type of code is,
and it will import and use it (often instead of its defaults).
We have already successfully imported things, for example:
> py import world.test ; world.test.hello_world(me)
Hello World!
In this example, on your hard drive, the files looks like this:
```
mygame/
world/
test.py <- inside this file is a function hello_world
```
If you followed earlier tutorial lessons, the `mygame/world/test.py` file should look like this (if
not, make it so):
```python
def hello_world(who):
who.msg("Hello World!")
```
```sidebar:: Remember:
- Indentation matters in Python
- So does capitalization
- Use 4 `spaces` to indent, not tabs
- Empty lines are fine
- Anything on a line after a `#` is a `comment`, ignored by Python
```
The _python_path_ describes the relation between Python resources, both between and inside
Python _modules_ (that is, files ending with .py). A python-path separates each part of the
path `.` and always skips the `.py` file endings. Also, Evennia already knows to start looking
for python resources inside `mygame/` so this should never be specified. Hence
import world.test
The `import` Python instruction loads `world.test` so you have it available. You can now go "into"
this module to get to the function you want:
world.test.hello_world(me)
Using `import` like this means that you have to specify the full `world.test` every time you want
to get to your function. Here's a more powerful form of import:
from world.test import hello_world
The `from ... import ...` is very, very common as long as you want to get something with a longer
python path. It imports `hello_world` directly, so you can use it right away!
> py from world.test import hello_world ; hello_world(me)
Hello World!
Let's say your `test.py` module had a bunch of interesting functions. You could then import them
all one by one:
from world.test import hello_world, my_func, awesome_func
If there were _a lot_ of functions, you could instead just import `test` and get the function
from there when you need (without having to give the full `world.test` every time):
> from world import test ; test.hello_world(me
Hello World!
You can also _rename_ stuff you import. Say for example that the module you import to already
has a function `hello_world` but we also want to use the one from `world/test.py`:
from world.test import hello_world as test_hello_world
The form `from ... import ... as ...` renames the import.
> from world.test import hello_world as hw ; hw(me)
Hello World!
> Avoid renaming unless it's to avoid a name-collistion like above - you want to make things as
> easy to read as possible, and renaming adds another layer of potential confusion.
In [the basic intro to Python](Python-basic-introduction) we learned how to open the in-game
multi-line interpreter.
> py
Evennia Interactive Python mode
Python 3.7.1 (default, Oct 22 2018, 11:21:55)
[GCC 8.2.0] on Linux
[py mode - quit() to exit]
You now only need to import once to use the imported function over and over.
> from world.test import hello_world
> hello_world()
Hello World!
> hello_world()
Hello World!
> hello_world()
Hello World!
> quit()
Closing the Python console.
The same goes when writing code in a module - in most Python modules you will see a bunch of
imports at the top, resources that are then used by all code in that module.
## On classes and objects
Now that we know about imports, let look at a real Evennia module and try to understand it.
Open `mygame/typeclasses/objects.py` in your text editor of choice.
```python
"""
module docstring
"""
from evennia import DefaultObject
class Object(DefaultObject):
"""
class docstring
"""
pass
```
```sidebar:: Docstrings vs Comments
A docstring is not the same as a comment (created by `#`). A
docstring is not ignored by Python but is an integral part of the thing
it is documenting (the module and the class in this case).
```
The real file is much longer but we can ignore the multi-line strings (`""" ... """`). These serve
as documentation-strings, or _docstrings_ for the module (at the top) and the `class` below.
Below the module doc string we have the import. In this case we are importing a resource
from the core `evennia` library itself. We will dive into this later, for now we just treat this
as a black box.
Next we have a `class` named `Object`, which _inherits_ from `DefaultObject`. This class doesn't
actually do anything on its own, its only code (except the docstring) is `pass` which means,
well, to pass and don't do anything.
To understand what we are looking at, we need to explain what a 'class', an 'object' and an 'instance' is.
```sidebar:: OOP
Classes, objects, instances and inheritance are fundamental to Python. This and some
other concepts are often clumped together under the term Object-Oriented-Programming (OOP).
```
### Classes and instances
A 'class' can be seen as a 'template' for a 'type' of object. The class describes the basic functionality
of everyone of that class. For example, we could have a class `Mobile` which has resources for moving itself
from room to room.
Open a new file `mygame/typeclasses/mymobile.py`. Add the following simple class:
```python
class Mobile:
key = "Monster"
def move_around(self):
print(f"{self.key} is moving!")
```
Above we have defined a `Mobile` class with one variable `key` (that is, the name) and one
_method_ on it. A method is like a function except it sits "on" the class. It also always has
at least one argument (almost always written as `self` although you could in principle use
another name), which is a reference back to itself. So when we print `self.key` we are referring
back to the `key` on the class.
A class is just a template. Before it can be used, we must create an _instance_ of the class. If
`Mobile` is a class, then an instance is Fluffy, the individual red dragon. You instantiate
by _calling_ the class, much like you would a function:
fluffy = Mobile()
Let's try it in-game (we use multi-line mode, it's easier)
> py
> from typeclasses.mymobile import Mobile
> fluffy = Mobile()
> fluffy.move_around()
Monster is moving!
We created an _instance_ of `Mobile`, which we stored in the variable `fluffy`. We then
called the `move_around` method on fluffy to get the printout.
> Note how we _didn't_ call the method as `fluffy.move_around(self)`. While the `self` has to be
> there when defining the method, we _never_ add it explicitly when we call the method (Python
> will add the correct `self` for us automatically behind the scenes).
Let's create the sibling of Fluffy, Cuddly:
> cuddly = Mobile()
> cuddly.move_around()
Monster is moving!
We now have two dragons and they'll hang around until with call `quit()` to exit this Python
instance. We can have them move as many times as we want. But no matter how many dragons we
create, they will all show the same printout since `key` is always fixed as "Monster".
Let's make the class a little more flexible:
```python
class Mobile:
def __init__(self, key):
self.key = key
def move_around(self):
print(f"{self.key} is moving!")
```
The `__init__` is a special method that Python recognizes. If given, this handles extra arguments
when you instantiate a new Mobile. We have it add an argument `key` that we store on `self`.
Now, for Evennia to see this code change, we need to reload the server. You can either do it this
way:
> quit()
Python Console is closing.
> reload
Or you can use a separate terminal and restart from outside the game:
```sidebar:: On reloading
Reloading with the python mode gets a little annoying since you need to redo everything
after every reload. Just keep in mind that during regular development you will not be
working this way. The in-game python mode is practical for quick fixes and experiments like
this, but actual code is normally written externally, in python modules.
```
$ evennia reload (or restart)
Either way you'll need to go into `py` again:
> py
> from typeclasses.mymobile import Mobile
fluffy = Mobile("Fluffy")
fluffy.move_around()
Fluffy is moving!
Now we passed `"Fluffy"` as an argument to the class. This went into `__init__` and set `self.key`, which we
later used to print with the right name! Again, note that we didn't include `self` when calling.
### What's so good about objects?
So far all we've seen a class do is to behave our first `hello_world` function but more complex. We
could just have made a function:
```python
def mobile_move_around(key):
print(f"{key} is moving!")
```
The difference between the function and an instance of a class (the object), is that the
object retains _state_. Once you called the function it forgets everything about what you called
it with last time. The object, on the other hand, remembers changes:
> fluffy.key = "Cuddly"
> fluffy.move_around()
Cuddly is moving!
The `fluffy` object's `key` was changed to "Cuddly" for as long as it's around. This makes objects
extremely useful for representing and remembering collections of data - some of which can be other
objects in turn:
- A player character with all its stats
- A monster with HP
- A chest with a number of gold coins in it
- A room with other objects inside it
- The current policy positions of a political party
- A rule with methods for resolving challenges or roll dice
- A multi-dimenstional data-point for a complex economic simulation
### Classes can have children
Classes can _inherit_ from each other. A "child" class will inherit everything from its "parent" class. But if
the child adds something with the same name as its parent, it will _override_ whatever it got from its parent.
[In the first part](Python-basic-introduction) of this Python-for-Evennia basic tutorial we learned
how to run some simple Python code from inside the game. We also made our first new *module*
containing a *function* that we called. Now we're going to start exploring the very important
containing a *function* that we called. Now we're going to start exploring the very important
subject of *objects*.
**Contents:**
- [On the subject of objects](Python-basic-tutorial-part-two#on-the-subject-of-objects)
- [On the subject of objects](Python-basic-tutorial-part-two#on-the-subject-of-objects)
- [Exploring the Evennia library](Python-basic-tutorial-part-two#exploring-the-evennia-library)
- [Tweaking our Character class](Python-basic-tutorial-part-two#tweaking-our-character-class)
- [The Evennia shell](Python-basic-tutorial-part-two#the-evennia-shell)
@ -224,7 +525,7 @@ is the same thing, just a little easier to remember.
> To access the shortcuts of the flat API you *must* use `from evennia import
> ...`. Using something like `import evennia.DefaultCharacter` will not work.
> See [more about the Flat API here](../../Evennia-API).
> See [more about the Flat API here](../../../Evennia-API).
### Tweaking our Character class
@ -283,8 +584,8 @@ brief summary of the methods we find in `DefaultCharacter` (follow in the code t
roughly where things happen)::
- `basetype_setup` is called by Evennia only once, when a Character is first created. In the
`DefaultCharacter` class it sets some particular [Locks](../../Component/Locks) so that people can't pick up and
puppet Characters just like that. It also adds the [Character Cmdset](../../Component/Command-Sets) so that
`DefaultCharacter` class it sets some particular [Locks](../../../Component/Locks) so that people can't pick up and
puppet Characters just like that. It also adds the [Character Cmdset](../../../Component/Command-Sets) so that
Characters always can accept command-input (this should usually not be modified - the normal hook to
override is `at_object_creation`, which is called after `basetype_setup` (it's in the parent)).
- `at_after_move` makes it so that every time the Character moves, the `look` command is
@ -484,7 +785,7 @@ convenient for quickly exploring code without having to go digging through the f
This should give you a running start using Python with Evennia. If you are completely new to
programming or Python you might want to look at a more formal Python tutorial. You can find links
and resources [on our link page](../../Links).
and resources [on our link page](../../../Links).
We have touched upon many of the concepts here but to use Evennia and to be able to follow along in
the code, you will need basic understanding of Python
@ -502,4 +803,6 @@ programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) an
Once you have familiarized yourself, or if you prefer to pick Python up as you go, continue to one
of the beginning-level [Evennia tutorials](Tutorials) to gradually build up your understanding.
Good luck!
Good luck!
[prev lesson](Gamedir-Overview) | [next lesson]()

View file

@ -25,8 +25,9 @@ own first little game in Evennia. Let's get started!
1. [Building stuff](Part1/Building-Quickstart)
1. [The Tutorial World](Part1/Tutorial-World-Introduction)
1. [Python basics](Part1/Python-basic-introduction)
1. [Game dir overview](Part1/Gamedir-Overview)
1. [Python classes](Python-basic-tutorial-part-two)
1. [Running Python in- and outside the game](Execute-Python-Code)
1. [Running Python in- and outside the game](../../Coding/Execute-Python-Code)
1. [Understanding errors](Understanding-Errors)
1. [Searching for things](Tutorial-Searching-For-Objects)
1. [A walkthrough of the API](Walkthrough-of-API)