Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #7882 > unrolled thread

What's the best way to write this base class?

Started byJohn Salerno <johnjsal@gmail.com>
First post2011-06-17 21:17 -0700
Last post2011-06-20 13:58 -0400
Articles 17 — 11 participants

Back to article view | Back to comp.lang.python


Contents

  What's the best way to write this base class? John Salerno <johnjsal@gmail.com> - 2011-06-17 21:17 -0700
    Re: What's the best way to write this base class? Chris Angelico <rosuav@gmail.com> - 2011-06-18 14:53 +1000
    Re: What's the best way to write this base class? "bruno.desthuilliers@gmail.com" <bruno.desthuilliers@gmail.com> - 2011-06-18 03:55 -0700
      Re: What's the best way to write this base class? Tim Chase <python.list@tim.thechases.com> - 2011-06-18 06:24 -0500
        Re: What's the best way to write this base class? "bruno.desthuilliers@gmail.com" <bruno.desthuilliers@gmail.com> - 2011-06-18 06:37 -0700
          Re: What's the best way to write this base class? Ian Kelly <ian.g.kelly@gmail.com> - 2011-06-18 08:51 -0600
    Re: What's the best way to write this base class? TheSaint <nobody@nowhere.net.no> - 2011-06-18 19:04 +0800
    Re: What's the best way to write this base class? Mel <mwilson@the-wire.com> - 2011-06-18 10:22 -0400
    Re: What's the best way to write this base class? Ethan Furman <ethan@stoneleaf.us> - 2011-06-18 08:37 -0700
      Re: What's the best way to write this base class? John Salerno <johnjsal@gmail.com> - 2011-06-18 09:26 -0700
        Re: What's the best way to write this base class? Chris Angelico <rosuav@gmail.com> - 2011-06-19 02:34 +1000
        Re: What's the best way to write this base class? Chris Kaynor <ckaynor@zindagigames.com> - 2011-06-19 18:52 -0700
          Re: What's the best way to write this base class? John Salerno <johnjsal@gmail.com> - 2011-06-19 21:04 -0700
            Re: What's the best way to write this base class? Benjamin Kaplan <benjamin.kaplan@case.edu> - 2011-06-20 00:12 -0700
            Re: What's the best way to write this base class? Mel <mwilson@the-wire.com> - 2011-06-20 07:57 -0400
              Re: What's the best way to write this base class? Ian Kelly <ian.g.kelly@gmail.com> - 2011-06-20 12:31 -0600
            Re: What's the best way to write this base class? Terry Reedy <tjreedy@udel.edu> - 2011-06-20 13:58 -0400

#7882 — What's the best way to write this base class?

FromJohn Salerno <johnjsal@gmail.com>
Date2011-06-17 21:17 -0700
SubjectWhat's the best way to write this base class?
Message-ID<142e76c3-b304-43ef-af24-919fa6146369@c9g2000yqp.googlegroups.com>
Let's say I'm writing a game (really I'm just practicing OOP) and I
want to create a "Character" base class, which more specific classes
will subclass, such as Warrior, Wizard, etc. Which of the following
ways is better, or is there another way?

Note: I have in mind that when a specific subclass (Warrior, Wizard,
etc.) is created, the only argument that will ever be passed to the
__init__ method is the name. The other variables will never be
explicitly passed, but will be set during initialization. With that in
mind, here are the ways I've come up with:

1)
class Character:

    def __init__(self, name, base_health=50, base_resource=10):
        self.name = name
        self.health = base_health
        self.resource = base_resource

2)
class Character:

    base_health = 50
    base_resource = 10

    def __init__(self, name):
        self.name = name
        self.health = base_health
        self.resource = base_resource

3)
BASE_HEALTH = 50
BASE_RESOURCE = 10

class Character:

    def __init__(self, name):
        self.name = name
        self.health = BASE_HEALTH
        self.resource = BASE_RESOURCE

[toc] | [next] | [standalone]


#7886

FromChris Angelico <rosuav@gmail.com>
Date2011-06-18 14:53 +1000
Message-ID<mailman.109.1308372833.1164.python-list@python.org>
In reply to#7882
On Sat, Jun 18, 2011 at 2:17 PM, John Salerno <johnjsal@gmail.com> wrote:
> 1)
> class Character:
>
>    def __init__(self, name, base_health=50, base_resource=10):
>        self.name = name
>        self.health = base_health
>        self.resource = base_resource

If you expect to override the health/resource, I'd use this model.

ChrisA

[toc] | [prev] | [next] | [standalone]


#7896

From"bruno.desthuilliers@gmail.com" <bruno.desthuilliers@gmail.com>
Date2011-06-18 03:55 -0700
Message-ID<e6841cf8-f365-4c23-9307-b69565025203@dq9g2000vbb.googlegroups.com>
In reply to#7882
On 18 juin, 06:17, John Salerno <johnj...@gmail.com> wrote:

> Note: I have in mind that when a specific subclass (Warrior, Wizard,
> etc.) is created, the only argument that will ever be passed to the
> __init__ method is the name. The other variables will never be
> explicitly passed, but will be set during initialization.

__init__ is actually supposed to be the initialization phase, but well
<g>

> 1)
> class Character:

If you using Python 2.x, make this:

class Character(object):

>     def __init__(self, name, base_health=50, base_resource=10):
>         self.name = name
>         self.health = base_health
>         self.resource = base_resource


If neither base_health nor base_resource are supposed to be passed in,
why make them arguments at all:

class Character(object):
    def __init__(self, name):
        self.name = name
        self.health = 50
        self.resource = 10



> 2)
> class Character:
>
>     base_health = 50
>     base_resource = 10
>
>     def __init__(self, name):
>         self.name = name
>         self.health = base_health
>         self.resource = base_resource

Did you at least tried this one ? Hint: it won't work.

> 3)
> BASE_HEALTH = 50
> BASE_RESOURCE = 10
>
> class Character:
>
>     def __init__(self, name):
>         self.name = name
>         self.health = BASE_HEALTH
>         self.resource = BASE_RESOURCE

This is probably what I'd do.

[toc] | [prev] | [next] | [standalone]


#7899

FromTim Chase <python.list@tim.thechases.com>
Date2011-06-18 06:24 -0500
Message-ID<mailman.116.1308396299.1164.python-list@python.org>
In reply to#7896
On 06/18/2011 05:55 AM, bruno.desthuilliers@gmail.com wrote:
> On 18 juin, 06:17, John Salerno<johnj...@gmail.com>  wrote:
>> class Character:
>>
>>      base_health = 50
>>      base_resource = 10
>>
>>      def __init__(self, name):
>>          self.name = name
>>          self.health = base_health
>>          self.resource = base_resource
>
> Did you at least tried this one ? Hint: it won't work.

If you want it, you can use

   self.health = Character.base_health

Though I'd treat them as semi-constants and capitalize them like 
your 3rd case:

   class Character(object):
     BASE_HEALTH = 50
     ...
     def __init__(...):
       ...
       self.health = Character.BASE_HEALTH

-tkc


[toc] | [prev] | [next] | [standalone]


#7905

From"bruno.desthuilliers@gmail.com" <bruno.desthuilliers@gmail.com>
Date2011-06-18 06:37 -0700
Message-ID<6c64a047-f137-4865-9dde-812461a4090e@u10g2000yqh.googlegroups.com>
In reply to#7899
On 18 juin, 13:24, Tim Chase <python.l...@tim.thechases.com> wrote:
> On 06/18/2011 05:55 AM, bruno.desthuilli...@gmail.com wrote:
>
> > On 18 juin, 06:17, John Salerno<johnj...@gmail.com>  wrote:
> >> class Character:
>
> >>      base_health = 50
> >>      base_resource = 10
>
> >>      def __init__(self, name):
> >>          self.name = name
> >>          self.health = base_health
> >>          self.resource = base_resource
>
> > Did you at least tried this one ? Hint: it won't work.
>
> If you want it, you can use
>
>    self.health = Character.base_health
>
> Though I'd treat them as semi-constants and capitalize them like
> your 3rd case:
>
>    class Character(object):
>      BASE_HEALTH = 50
>      ...
>      def __init__(...):
>        ...
>        self.health = Character.BASE_HEALTH
>


If you go that way, then using polymorphic dispatch might (or not,
depending on the game's rules <g>) be a good idea:


class Character(object):
    BASE_HEALTH = 50
    ...
    def __init__(self, name):
        ...
        self.health = type(self).BASE_HEALTH


This would allow different Character subclasses to have different
BASE_HEALTH etc..., defaulting to the base class values.

[toc] | [prev] | [next] | [standalone]


#7911

FromIan Kelly <ian.g.kelly@gmail.com>
Date2011-06-18 08:51 -0600
Message-ID<mailman.121.1308408694.1164.python-list@python.org>
In reply to#7905
On Sat, Jun 18, 2011 at 7:37 AM, bruno.desthuilliers@gmail.com
<bruno.desthuilliers@gmail.com> wrote:
> If you go that way, then using polymorphic dispatch might (or not,
> depending on the game's rules <g>) be a good idea:
>
>
> class Character(object):
>    BASE_HEALTH = 50
>    ...
>    def __init__(self, name):
>        ...
>        self.health = type(self).BASE_HEALTH

This of course is equivalent to a simple "self.health =
self.BASE_HEALTH" as long as you haven't explicitly assigned
BASE_HEALTH on the instance.

Tangentially, I wouldn't use inheritance at all for this game.  I know
the classic "is-a / has-a" test says that a wizard "is a" character,
but in my experience that method leans toward doing way too much
inheritance.  If you have subclasses for character classes, then you
will be tempted to also use subclasses for races, and then when you're
ready to make elf wizards you will have forced yourself into a
multiple inheritance situation, and down that path wait Agony and
Despair.

Instead, I would use composition here.  A character has a class (e.g.
Wizard(specialization='fire')) and a race (e.g. Elf(breed='high') --
or maybe just HighElf(), which inherits from Elf).  Save inheritance
for broad categories of what it means to be a character (e.g.
PlayerCharacter vs. NonPlayerCharacter or MobileCharacter vs.
MagicMirror, etc., any of which might have the Wizard character
class).

Cheers,
Ian

[toc] | [prev] | [next] | [standalone]


#7897

FromTheSaint <nobody@nowhere.net.no>
Date2011-06-18 19:04 +0800
Message-ID<iti0na$q3g$1@speranza.aioe.org>
In reply to#7882
John Salerno wrote:

> class Character:

I'd vote to point 1

-- 
goto /dev/null

[toc] | [prev] | [next] | [standalone]


#7907

FromMel <mwilson@the-wire.com>
Date2011-06-18 10:22 -0400
Message-ID<iticc1$o12$1@speranza.aioe.org>
In reply to#7882
John Salerno wrote:
[ ... ]
> 1)
> class Character:
>     def __init__(self, name, base_health=50, base_resource=10):
>         self.name = name
>         self.health = base_health
>         self.resource = base_resource
> 
> 2)
> class Character:
>     base_health = 50
>     base_resource = 10
>     def __init__(self, name):
>         self.name = name
>         self.health = base_health
>         self.resource = base_resource
> 
> 3)
> BASE_HEALTH = 50
> BASE_RESOURCE = 10
> class Character:
>     def __init__(self, name):
>         self.name = name
>         self.health = BASE_HEALTH
>         self.resource = BASE_RESOURCE

For completeness, there's also 4)

Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class Character (object):
...     health = 50
...     def __init__ (self, name):
...         self.name = name
...         print self.name, self.health
... 
>>> Character ('Eunice')
Eunice 50


where the class attribute is used until it's overridden in the instance.

	Mel.

[toc] | [prev] | [next] | [standalone]


#7912

FromEthan Furman <ethan@stoneleaf.us>
Date2011-06-18 08:37 -0700
Message-ID<mailman.122.1308411487.1164.python-list@python.org>
In reply to#7882
John Salerno wrote:
> 1)
> class Character:
> 
>     def __init__(self, name, base_health=50, base_resource=10):
>         self.name = name
>         self.health = base_health
>         self.resource = base_resource

You said above that health and resource will never be explicitly passed, 
yet here you have allowed for that possibility.  If you are going to 
have mosters, etc, also inherit from Character, with different health 
and resources, I would go this route with one change:

     def __init__(self, name, base_health, base_resoures):

and always specify those numbers on creation.


> 2)
> class Character:
> 
>     base_health = 50
>     base_resource = 10
> 
>     def __init__(self, name):
>         self.name = name
>         self.health = base_health
>         self.resource = base_resource

You do not need to assign health and resource here -- they are already 
assigned on the class, so the instance will see them automatically. 
When a change is made, the instance will automagically get its own copy.


> 3)
> BASE_HEALTH = 50
> BASE_RESOURCE = 10
> 
> class Character:
> 
>     def __init__(self, name):
>         self.name = name
>         self.health = BASE_HEALTH
>         self.resource = BASE_RESOURCE

If *all* characters (player, non-player, monster, etc) will have the 
same base health and resources then this is fine -- otherwise I would 
use option 1.

~Ethan~

[toc] | [prev] | [next] | [standalone]


#7913

FromJohn Salerno <johnjsal@gmail.com>
Date2011-06-18 09:26 -0700
Message-ID<fa2ee2a4-f46f-42c7-846c-b854977f9e38@16g2000yqy.googlegroups.com>
In reply to#7912
Whew, thanks for all the responses! I will think about it carefully
and decide on a way. I was leaning toward simply assigning the health,
resource, etc. variables in the __init__ method, like this:

def __init__(self, name):
    self.name = name
    self.health = 50
    self.resource = 10

I never did like the idea of using the parameters if I never intended
to pass them in...just seems wrong.   :)

The idea of not using a base Character class at all threw me for a
loop though, so I need to think about that too!

[toc] | [prev] | [next] | [standalone]


#7914

FromChris Angelico <rosuav@gmail.com>
Date2011-06-19 02:34 +1000
Message-ID<mailman.124.1308414900.1164.python-list@python.org>
In reply to#7913
On Sun, Jun 19, 2011 at 2:26 AM, John Salerno <johnjsal@gmail.com> wrote:
> The idea of not using a base Character class at all threw me for a
> loop though, so I need to think about that too!
>

It's easy to fall in love with a concept like inheritance, and use it
in all sorts of things. You then have a choice to make: Is the project
you're writing primarily for its own sake, or primarily so that you
can explore the programming concept? There's nothing wrong with
building a mediocre game on a mediocre basis and using it solely to
play around with OO and inheritance and class structures, but if you
want it to be a good game, sometimes you need to go back on decisions
like that.

And that's where mailing lists like this are awesome. I've learned so
much from the wisdom here... there is an amazing amount of expertise
being offered freely!

Chris Angelico

[toc] | [prev] | [next] | [standalone]


#7995

FromChris Kaynor <ckaynor@zindagigames.com>
Date2011-06-19 18:52 -0700
Message-ID<mailman.166.1308534779.1164.python-list@python.org>
In reply to#7913
On Jun 18, 2011, at 9:26, John Salerno <johnjsal@gmail.com> wrote:

> Whew, thanks for all the responses! I will think about it carefully
> and decide on a way. I was leaning toward simply assigning the health,
> resource, etc. variables in the __init__ method, like this:
> 
> def __init__(self, name):
>    self.name = name
>    self.health = 50
>    self.resource = 10
> 
> I never did like the idea of using the parameters if I never intended
> to pass them in...just seems wrong.   :)
> 
> The idea of not using a base Character class at all threw me for a
> loop though, so I need to think about that too!

Having a character class (along with possibly player character, non-player character, etc), make sense; however you probably want to make stuff like health, resources, damage, and any other attributes not be handles by any classes or inheritance in order to allow you to make such data-driven (ie, read from a file). Doing so makes the game much more extendable: using classes, you are likely limited to 5 or 'combinations and a few developers (plus, any designers need to know programming).

A basic way to determine between using subclasses over a data driven approach is: is there significantly different back-end behavior or merely attribute differences.

> --
> http://mail.python.org/mailman/listinfo/python-list

[toc] | [prev] | [next] | [standalone]


#7996

FromJohn Salerno <johnjsal@gmail.com>
Date2011-06-19 21:04 -0700
Message-ID<5c8be025-2d2c-42fc-a764-bd1ca03ba398@d14g2000yqb.googlegroups.com>
In reply to#7995
On Jun 19, 8:52 pm, Chris Kaynor <ckay...@zindagigames.com> wrote:

> Having a character class (along with possibly player character, non-player character, etc), make sense; however you probably want to make stuff like health, resources, damage, and any other attributes not be handles by any classes or inheritance in order to allow you to make such data-driven (ie, read from a file). Doing so makes the game much more extendable: using classes, you are likely limited to 5 or 'combinations and a few developers (plus, any designers need to know programming).
>
> A basic way to determine between using subclasses over a data driven approach is: is there significantly different back-end behavior or merely attribute differences.

Can you give a basic example of how this data-driven approach would
work? You don't have to provide any code, just a description would be
helpful. Such as, do I create a data file per character, and then have
each character instance read/write to that file? Is it good to have so
many files open at once, or would they only need to be read, closed,
then opened again at the end to write?

[toc] | [prev] | [next] | [standalone]


#8006

FromBenjamin Kaplan <benjamin.kaplan@case.edu>
Date2011-06-20 00:12 -0700
Message-ID<mailman.177.1308554162.1164.python-list@python.org>
In reply to#7996
On Sun, Jun 19, 2011 at 9:04 PM, John Salerno <johnjsal@gmail.com> wrote:
> On Jun 19, 8:52 pm, Chris Kaynor <ckay...@zindagigames.com> wrote:
>
>> Having a character class (along with possibly player character, non-player character, etc), make sense; however you probably want to make stuff like health, resources, damage, and any other attributes not be handles by any classes or inheritance in order to allow you to make such data-driven (ie, read from a file). Doing so makes the game much more extendable: using classes, you are likely limited to 5 or 'combinations and a few developers (plus, any designers need to know programming).
>>
>> A basic way to determine between using subclasses over a data driven approach is: is there significantly different back-end behavior or merely attribute differences.
>
> Can you give a basic example of how this data-driven approach would
> work? You don't have to provide any code, just a description would be
> helpful. Such as, do I create a data file per character, and then have
> each character instance read/write to that file? Is it good to have so
> many files open at once, or would they only need to be read, closed,
> then opened again at the end to write?
> --

I'm pretty sure he means that if the only difference between classes
is configuration (i.e. you aren't actually going to change code
between character classes, just base stats, growth rates, and a list
of available skills or something of that nature), then you should
store the configurations in a config file rather than making a new
class. So rather than having
class WizardCharacter(Character) :
    base_health = 50
    ...
class WarriorCharacter(Character) :
    base_health=70
    ...
You make a config file

--- characterclasses.ini ---
[Wizard]
base_health=50
[Warrior]
base_health=70

Then, when you make a new character, rather than doing a
WizardCharacter() or a WarriorCharacter(), you do a
Character(job='Wizard') and then look up the various defaults in your
config file. Doing it this way makes it trivial to add a new class. If
you want to use an old-fashioned INI file, you can use the
ConfigParser class to read them. If you want to nest attributes (for
instance, a list of sub-items), you'll probably want to go with XML
and ElementTree. I guess you can also use JSON (which uses a syntax
similar to Python's dictionaries) but I've never really tried to make
one of those by hand before so I'm not sure how well it will work out.

[toc] | [prev] | [next] | [standalone]


#8012

FromMel <mwilson@the-wire.com>
Date2011-06-20 07:57 -0400
Message-ID<itncj3$s1g$1@speranza.aioe.org>
In reply to#7996
John Salerno wrote:

> On Jun 19, 8:52 pm, Chris Kaynor <ckay...@zindagigames.com> wrote:
> 
>> Having a character class (along with possibly player character,
>> non-player character, etc), make sense; however you probably want to make
>> stuff like health, resources, damage, and any other attributes not be
>> handles by any classes or inheritance in order to allow you to make such
>> data-driven (ie, read from a file). Doing so makes the game much more
>> extendable: using classes, you are likely limited to 5 or 'combinations
>> and a few developers (plus, any designers need to know programming).
>>
>> A basic way to determine between using subclasses over a data driven
>> approach is: is there significantly different back-end behavior or merely
>> attribute differences.
> 
> Can you give a basic example of how this data-driven approach would
> work? You don't have to provide any code, just a description would be
> helpful. Such as, do I create a data file per character, and then have
> each character instance read/write to that file? Is it good to have so
> many files open at once, or would they only need to be read, closed,
> then opened again at the end to write?

Battle for Wesnoth is set up this way.  I don't know what the code does, but 
you can go wild creating new classes of character by mixing up new 
combinations of attribute settings in new configuration files, and injecting 
them into the standard game config files.

AFAIK you are stuck with the attributes the game is programmed for.  I've 
seen no way to create a new dimension for the game -- Conversation, for 
instance, with currently unknown attributes like vocabulary or tone.

	Mel.

[toc] | [prev] | [next] | [standalone]


#8024

FromIan Kelly <ian.g.kelly@gmail.com>
Date2011-06-20 12:31 -0600
Message-ID<mailman.188.1308594707.1164.python-list@python.org>
In reply to#8012
On Mon, Jun 20, 2011 at 5:57 AM, Mel <mwilson@the-wire.com> wrote:
> Battle for Wesnoth is set up this way.  I don't know what the code does, but
> you can go wild creating new classes of character by mixing up new
> combinations of attribute settings in new configuration files, and injecting
> them into the standard game config files.
>
> AFAIK you are stuck with the attributes the game is programmed for.  I've
> seen no way to create a new dimension for the game -- Conversation, for
> instance, with currently unknown attributes like vocabulary or tone.

The Dwarf Fortress data files are also well worth taking a look at in
this regard.  Virtually everything is configurable, from basic
attributes like size and language all the way down to intricate
details like types and quantity of body parts, what tissues said body
parts are made of and what roles they play.  To get an idea of the
level of customization possible have a look at:

http://df.magmawiki.com/index.php/Modding#Modding_the_creatures

[toc] | [prev] | [next] | [standalone]


#8022

FromTerry Reedy <tjreedy@udel.edu>
Date2011-06-20 13:58 -0400
Message-ID<mailman.186.1308592755.1164.python-list@python.org>
In reply to#7996
On 6/20/2011 3:12 AM, Benjamin Kaplan wrote:
> On Sun, Jun 19, 2011 at 9:04 PM, John Salerno<johnjsal@gmail.com>  wrote:
>> On Jun 19, 8:52 pm, Chris Kaynor<ckay...@zindagigames.com>  wrote:
>>
>>> Having a character class (along with possibly player character, non-player character, etc), make sense; however you probably want to make stuff like health, resources, damage, and any other attributes not be handles by any classes or inheritance in order to allow you to make such data-driven (ie, read from a file). Doing so makes the game much more extendable: using classes, you are likely limited to 5 or 'combinations and a few developers (plus, any designers need to know programming).
>>>
>>> A basic way to determine between using subclasses over a data driven approach is: is there significantly different back-end behavior or merely attribute differences.
>>
>> Can you give a basic example of how this data-driven approach would
>> work? You don't have to provide any code, just a description would be
>> helpful. Such as, do I create a data file per character, and then have
>> each character instance read/write to that file? Is it good to have so
>> many files open at once, or would they only need to be read, closed,
>> then opened again at the end to write?
>> --
>
> I'm pretty sure he means that if the only difference between classes
> is configuration (i.e. you aren't actually going to change code
> between character classes, just base stats, growth rates, and a list
> of available skills or something of that nature), then you should
> store the configurations in a config file rather than making a new
> class. So rather than having
> class WizardCharacter(Character) :
>      base_health = 50
>      ...
> class WarriorCharacter(Character) :
>      base_health=70
>      ...
> You make a config file
>
> --- characterclasses.ini ---
> [Wizard]
> base_health=50
int = 70

> [Warrior]
> base_health=70
int = 30

[Gandolf]
base_health = 60
int = 100

My point here being that with a data approach, non-programmers can also 
define named, unique NPCs with custom stats as well as generic classes

> Then, when you make a new character, rather than doing a
> WizardCharacter() or a WarriorCharacter(), you do a
> Character(job='Wizard') and then look up the various defaults in your
> config file. Doing it this way makes it trivial to add a new class. If
> you want to use an old-fashioned INI file, you can use the
> ConfigParser class to read them. If you want to nest attributes (for
> instance, a list of sub-items), you'll probably want to go with XML
> and ElementTree. I guess you can also use JSON (which uses a syntax
> similar to Python's dictionaries) but I've never really tried to make
> one of those by hand before so I'm not sure how well it will work out.


-- 
Terry Jan Reedy

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web