100% found this document useful (1 vote)
123 views55 pages

Full download Using Asyncio in Python 3 Caleb Hattingh pdf docx

Python

Uploaded by

vikkiheyenvj
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
123 views55 pages

Full download Using Asyncio in Python 3 Caleb Hattingh pdf docx

Python

Uploaded by

vikkiheyenvj
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 55

Download the Full Version of textbook for Fast Typing at textbookfull.

com

Using Asyncio in Python 3 Caleb Hattingh

https://textbookfull.com/product/using-asyncio-in-
python-3-caleb-hattingh/

OR CLICK BUTTON

DOWNLOAD NOW

Download More textbook Instantly Today - Get Yours Now at textbookfull.com


Recommended digital products (PDF, EPUB, MOBI) that
you can download immediately if you are interested.

Using Asyncio in Python Understanding Python s


Asynchronous Programming Features Caleb Hattingh

https://textbookfull.com/product/using-asyncio-in-python-
understanding-python-s-asynchronous-programming-features-caleb-
hattingh/
textboxfull.com

Practical Programming An Introduction to Computer Science


Using Python 3 6 3rd Edition Paul Gries

https://textbookfull.com/product/practical-programming-an-
introduction-to-computer-science-using-python-3-6-3rd-edition-paul-
gries/
textboxfull.com

Python Basics A Practical Introduction to Python 3


Fletcher Heisler

https://textbookfull.com/product/python-basics-a-practical-
introduction-to-python-3-fletcher-heisler/

textboxfull.com

Supervised Learning with Python: Concepts and Practical


Implementation Using Python Vaibhav Verdhan

https://textbookfull.com/product/supervised-learning-with-python-
concepts-and-practical-implementation-using-python-vaibhav-verdhan/

textboxfull.com
Python 2 and 3 Compatibility: With Six and Python-Future
Libraries Nanjekye

https://textbookfull.com/product/python-2-and-3-compatibility-with-
six-and-python-future-libraries-nanjekye/

textboxfull.com

Advanced Guide to Python 3 Programming Hunt

https://textbookfull.com/product/advanced-guide-to-
python-3-programming-hunt/

textboxfull.com

Python Programming Using Problem Solving Approach Thareja


Reema

https://textbookfull.com/product/python-programming-using-problem-
solving-approach-thareja-reema/

textboxfull.com

Supervised Learning with Python Concepts and Practical


Implementation Using Python 1st Edition Vaibhav Verdhan

https://textbookfull.com/product/supervised-learning-with-python-
concepts-and-practical-implementation-using-python-1st-edition-
vaibhav-verdhan/
textboxfull.com

Python deeper insights into machine learning leverage


benefits of machine learning techniques using Python a
course in three modules Hearty
https://textbookfull.com/product/python-deeper-insights-into-machine-
learning-leverage-benefits-of-machine-learning-techniques-using-
python-a-course-in-three-modules-hearty/
textboxfull.com
Playlists

Using Asyncio in Python 3


History
by Caleb Hattingh

Topics
Copyright © 2018 O’Reilly Media, Inc. All rights reserved.

Tutorials
Printed in the United States of America.

Offers & Deals


Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.

Highlights
O’Reilly books may be purchased for educational, business, or sales promotional use. Online
editions are also available for most titles (http://oreilly.com/safari). For more information,
Settings
contact our corporate/institutional sales department: 800­998­9938 or [email protected].

Support
Editors: Jeff Bleiel and Susan Conant

Sign Out
Production Editor: Nicholas Adams

Copyeditor: Dwight Ramsey

Interior Designer: David Futato

Cover Designer: Karen Montgomery

Illustrator: Rebecca Demarest

Tech Reviewer: Yury Selivanov

March 2018: First Edition

Revision History for the First Edition

2018­03­08: First Release

The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Using Asyncio in Python 3,
the cover image, and related trade dress are trademarks of O’Reilly Media, Inc.
While the publisher and the author have used good faith efforts to ensure that the information
and instructions contained in this work are accurate, the publisher and the author disclaim all
responsibility for errors or omissions, including without limitation responsibility for damages
resulting from the use of or reliance on this work. Use of the information and instructions
contained in this work is at your own risk. If any code samples or other technology this work
contains or describes is subject to open source licenses or the intellectual property rights of
others, it is your responsibility to ensure that your use thereof complies with such licenses
and/or rights.

978­1­491­99968­4

[LSI]
y

Chapter 1. Introduction
History

Topics
My story is a lot like yours, only more interesting ’cause it involves robots.
—Bender, Futurama episode “30% Iron Chef”
Tutorials
The most common question I receive about Asyncio in Python 3 is this: “What is it, and what do
I do
Offers with
& Deals it?” The following story provides a backdrop for answering these questions. The
central focus of Asyncio is about how to best perform multiple concurrent tasks at the same
Highlights
time. And not just any sort of tasks, but specifically tasks that involve waiting periods. The key
insight required with this style of programming is that while you wait for this task to complete,
Settings
work on other tasks can be performed.

Support
The Restaurant of ThreadBots
Sign Out
The year is 2051, and you find yourself in the restaurant business! Automation, largely by robot
workers, powers most of the economy, but it turns out that humans still enjoy going out to eat
once in a while. In your restaurant, all the employees are robots; humanoid, of course, but
unmistakably robots. The most successful manufacturer of robots is of course Threading Inc.,
and robot workers from this company have come to be called “ThreadBots.”

Except for this small robotic detail, your restaurant looks and operates like one of those old­time
restaurants from, say, 2018. Your guests will be looking for that vintage experience. They want
fresh food prepared from scratch. They want to sit at tables. They want to wait for their meals—
but only a little. They want to pay at the end, and they sometimes even want to leave a tip, for
old­time’s sake, of course.

Naturally, being new to the robotic restaurant business, you do what every other restaurateur
does, and you hire a small fleet of robots: one to greet new diners at the front desk (hostbot);
one to wait tables and take orders (waitbot); one to do the cooking (chefbot); and one to manage
the bar (winebot).

Hungry diners will arrive at the front desk, and will be greeted by your front­of­house threadbot.
They are then directed to a table, and once they are seated, your waiter threadbot will take their
order. Then, the waiter threadbot will take that order to the kitchen on a slip of paper (because
you want to preserve that old­time experience, remember?). The chefbot will look up the order
on the slip and begin preparing the food. The waitbot will periodically check whether the food is
ready, and if so, will immediately take the dish to the customer’s table. When the guests are
ready to leave, they return to greetbot who calculates the bill, and graciously wishes them a
pleasant evening further.

You soon open your restaurant, and exactly as you had anticipated, your menu is a hit and you
soon grow a large customer base. Your robot employees do exactly what they’re told, and they
are perfectly good at the tasks you assigned them. Everything is going really well, and you
really couldn’t be happier.

Over time, however, you do begin to notice some problems. Oh, it’s nothing truly serious. Just a
few things that seem to go wrong. Every other robotic restaurant owner seems to have similar
niggling problems. It is a little worrying that these problems seem to get worse the more
successful you become.

Though rare, there are the occasional collisions that are very unsettling: sometimes, when a
plate of food is ready in the kitchen, the waitbot will grab it before the chefbot has even let go of
the plate! This usually shatters the plate and leaves a big mess. Chefbot cleans it up of course,
but still, you’d think that these top­notch robots would know how to be a bit more synchronized
with each other. This happens at the bar too: sometimes winebot will place a new drink order on
the bar and waitbot will grab it before winebot has let go, resulting in broken glass and spilled
Nederburg Cabernet Sauvignon!

Sometimes greetbot will seat new diners at exactly the same moment that waitbot has decided to
clean what it thinks was an empty table. It’s pretty awkward for the diners! You’ve tried adding
delay logic to the waitbot’s cleaning function, or delays to the greetbot’s seating function, but
these don’t really help, because the collisions still occur. At least these are only rare events.

Well, these used to be rare events. Your restaurant got so popular that you’ve had to hire a few
more threadbots. For very busy Friday and Saturday evenings, you’ve had to add a second
greetbot and two extra waitbots. Unfortunately the hiring contracts for threadbots mean that you
have to hire for a whole week, so this effectively means that for most of the quiet part of the
week, you’re carrying three extra threadbots that you don’t really need.

The other resource problem, in addition to the extra cost, of course, is that it’s more work for
you to deal with these extra threadbots. It was fine to keep tabs on just four bots, but now you’re
up to seven! Keeping track of seven threadbots is a lot more work, and because your restaurant
keeps getting more and more famous, you become worried about taking on even more
threadbots. It’s going to become a full­time job just to keep track of what each threadbot is
doing! Another thing: these extra theadbots are using up a lot more space inside your restaurant.
It’s becoming a tight squeeze for your customers, what with all these robots zipping around.
You’re worried that if you need to add even more bots, this space problem is going to get even
worse. You want to use the space in your restaurant for customers, not threadbots!

The collisions have also become worse since you added more threadbots. Now, sometimes two
waitbots take the exact same order from the same table at the same time. It’s as if they both
noticed that the table was ready to order and moved in to take it, without noticing that the other
waitbot was doing the exact same thing. As you can imagine, this results in duplicated food
orders which causes extra load on the kitchen and increases the chance of collisions when
picking up the ready plates. You’re worried that if you added more waitbots, this problem might
get worse.

Time passes.

Then, during one very, very busy Friday night service, you have a singular moment of clarity:
time slows, lucidity overwhelms you and you see a snapshot of your restaurant frozen in time.
My threadbots are doing nothing! Not really nothing, to be fair, but they’re just…waiting.

Each of your three waitbots at different tables is waiting for one of the diners at their table to
give their order. The winebot already prepared 17 drinks which are now waiting to be collected
(it took only a few seconds), and is now waiting for a new drink order. One of the hostbots has
greeted a new party of guests, told them they need to wait a minute to be seated, and is waiting
for the guest to respond. The other hostbot, now processing a credit card payment for another
guest that is leaving, is waiting for confirmation on the payment gateway device. Even the
chefbot, who is currently cooking 35 meals, is not actually doing anything at this moment, but is
simply waiting for one of the meals to complete cooking so that it can be plated up and handed
over to a waitbot.

You realize that even though your restaurant is now full of threadbots, and you’re even
considering getting more (with all the problems that entails), the ones that you currently have
are not even being fully utilized.

The moment passes, but not the realization. You wait for weekend service to pass, and the first
thing you do is add a data collection module to your threadbots. For each threadbot, you’re
measuring how much time is spent waiting and how much is spent actively doing work. Over
the course of the following week, the data is collected and then on Sunday evening you analyze
the results. It turns out that even when your restaurant is at full capacity, the most hardworking
threadbot is idle for about 98% of the time! The threadbots are so enormously efficient that they
can perform any task in fractions of a second.

As an entrepreneur, this inefficiency really bugs you. You know that every other robotic
restaurant owner is running their business the same as you, with many of the same problems.
But, you think, slamming your fist on your desk, “There must be a better way!”

So the very next day, which is a quiet Monday, you try something very bold: you program a
single threadbot to do all the tasks. But every time it begins to wait, even for a second, instead
of waiting, the threadbot will switch to the next task, whatever it may be in the entire restaurant.
It sounds incredible at face value, only one threadbot doing the work of all the others, but you’re
confident that your calculations are correct. And besides, Monday is a very quiet day; so even if
something goes wrong, the impact will be small. For this new project, you call the bot “loopbot”
because it will loop over all the jobs in the restaurant.

The programming was more difficult than usual. It isn’t just that you had to program one
threadbot with all the different tasks; you also had to program some of the logic of when to
switch between tasks. But by this stage, you’ve had a lot of experience with programming these
threadbots so you manage to get it done.

Monday arrives, and you watch your loopbot like a hawk. It moves between stations in fractions
of a second, checking whether there is work to be done. Not long after opening, the first guest
arrives at the front desk. The loopbot shows up almost immediately, and asks whether the guest
would like a table near the window or near the bar. And then, as the loopbot begins to wait, its
programming tells it to switch to the next task, and it whizzes off! This seems like a dreadful
error, but then you see that as the guest begins to say “window please,” the loopbot is back! It
receives the answer and directs the guest to table 42. And off it goes again, checking for drinks
orders, food orders, table cleanup, and arriving guests, over and over again.

Late Monday evening, you congratulate yourself on a remarkable success! You check the data
collection module on the loopbot, and it confirms that even with a single threadbot doing the
work of seven, the idle time was still around 97%! This result gives you the confidence to
continue the experiment all through the rest of the week.

As the busy Friday service approaches, you reflect on the great success of your experiment. For
service during a normal working week, you can easily manage the workload with a single
loopbot. And there is another thing you’ve noticed: you don’t see any more collisions. It makes
sense: since there is only one loopbot, it cannot get confused with itself. No more duplicate
orders going to the kitchen, and no more confusion about when to grab a plate or drink.

Friday evening service begins, and as you had hoped, the single threadbot keeps up with all the
customers and tasks, and service is proceeding even better than before. You imagine that you
can take on even more customers now, and you don’t have to worry about having to bring on
more threadbots. You think of all the money you’re going to save.

Unfortunately, something goes wrong: one of the meals, an intricate souffle, has flopped! This
has never happened before in your restaurant. You begin to study the loopbot more closely. It
turns out that at one of your tables, there is a very chatty guest. This guest has come to your
restaurant alone, and keeps trying to make conversation with your loopbot, even sometimes
holding your loopbot by the hand. When this happens, your loopbot is unable to dash off and
attend to the ever­growing list of tasks elsewhere in your restaurant. This is why the kitchen
produced its first flopped souffle. Your loopbot was unable to make it back to the kitchen to
remove a souffle from the oven, because it was held up by a guest.

Friday service finishes, and you head home to reflect on what you have learned. It’s true that the
loopbot could still do all the work that was required on a busy Friday service; but on the other
hand, your kitchen produced its very first spoiled meal, something that has never happened
before. Chatty guests used to keep waitbots busy all the time, but that never affected the kitchen
service at all.

All things considered, you ponder, it is still better to continue using a single loopbot. Those
worrying collisions no longer occur, and there is much more space in your restaurant, space that
you can use for more customers. But you realize something profound about the loopbot: it can
only be effective if every task is short; or at least can be performed in a very short period of
time. If any activity keeps the loopbot busy for too long, other tasks will begin to suffer neglect.

It is difficult to know in advance which tasks may take too much time. What if a guest orders a
cocktail that requires very intricate preparation, much more than usual? What if a guest wants to
complain about a meal at the front­desk, refuses to pay, and grabs the loopbot by the arm,
preventing it from task­switching? You decide that instead of figuring out all of these issues up
front, it is better to continue with the loopbot, record as much information as possible, and deal
with any problems later as they arise.

More time passes.

Gradually, other restaurant owners notice your operation, and eventually they figure out that
they too can get by, and even thrive, with only a single threadbot. Word spreads. Soon every
single restaurant operates in this way, and it becomes difficult to remember that robotic
restaurants ever operated with multiple threadbots at all.

Epilogue
In our story, each of the robot workers in the restaurant is a single thread. The key observation
in the story is that the nature of the work in our restaurant involves a great deal of waiting, just
as requests.get() is waiting for a response from a server.

In a restaurant, the worker time spent waiting isn’t huge when slow humans are doing manual
work, but when super­efficient and quick robots are doing the work, then nearly all their time is
spent waiting. With computer programming, the same is true when network programming is
involved. CPUs do “work” and “wait” on network I/O. CPUs in modern computers are
extremely fast, hundreds of thousands of times faster than network traffic. Thus, CPUs running
networking programs spend a great deal of time waiting.

The insight in the story is that programs can be written to explicitly direct the CPU to move
between work tasks as necessary. While there is an improvement in economy (using fewer
CPUs for the same work), the real advantage, compared to a threading (multi­CPU) approach is
the elimination of race conditions.

It’s not all roses, however: as we found in the story, there are benefits and drawbacks to most
technology solutions. The introduction of the LoopBot solved a certain class of problems, but
also introduced new problems—not least of which is that the restaurant owner had to learn a
slightly different way of programming.

What Problem Is Asyncio Trying to Solve?


For I/O­bound workloads, there are exactly two reasons (only!) to use async­based concurrency
over thread­based concurrency:

Asyncio offers a safer alternative to preemptive multitasking (i.e., using threads), thereby
avoiding the bugs, race conditions, and other non­deterministic dangers that frequently
occur in non­trivial threaded applications.

Asyncio offers a simple way to support many thousands of simultaneous socket


connections, including being able to handle many long­lived connections for newer
technologies like websockets, or MQTT for internet­of­things applications.

That’s it.

Threading—as a programming model—is best suited to certain kinds of computational tasks


that are best executed with multiple CPUs and shared memory for efficient communication
between the threads. In such tasks, the use of multicore processing with shared memory is a
necessary evil because the problem domain requires it.

Network programming is not one of those domains. The key insight is that network
programming involves a great deal of “waiting for things to happen,” and because of this, we
don’t need the operating system to efficiently distribute our tasks over multiple CPUs.
Furthermore, we don’t need the risks that preemptive multitasking brings, such as race
conditions when working with shared memory.

However, there is a great deal of misinformation about other supposed benefits of event­based
programming models that just ain’t so. Here are a few:

Asyncio will make my code blazing fast

Unfortunately, no. In fact, most benchmarks seem to show that threading solutions are
slightly faster than their comparable Asyncio solutions. If the extent of concurrency itself is
considered a performance metric, Asyncio does make it a bit easier to create very large
numbers of concurrent socket connections though. Operating systems often have limits on
how many threads can be created, and this number is significantly lower than the number of
socket connections that can be made. The OS limits can be changed, but it is certainly easier
to do with Asyncio. And while we expect that having many thousands of threads should
incur extra context­switching costs that coroutines avoid, it turns out to be difficult to
benchmark this in practice.1 No, speed is not the benefit of Asyncio in Python; if that’s
what you’re after, try Cython instead!

Asyncio makes threading redundant

Definitely not! The true value of threading lies in being able to write multi­CPU programs,
in which different computational tasks can share memory. The numerical library numpy, for
instance, already makes use of this by speeding up certain matrix calculations through the
use of multiple CPUs, even though all the memory is shared. For sheer performance, there is
no competitor to this programming model for CPU­bound computation.

Asyncio removes the problems with the GIL

Again, no. It is true that Asyncio is not affected by the GIL,2 but this is only because the
GIL affects multithreaded programs. The “problems” with the GIL that people refer to are
that it prevents true multicore concurrency when using threads. Since Asyncio is single­
threaded (almost by definition), it is unaffected by the GIL, but it also cannot benefit from
multiple CPU cores either.3 It is also worth pointing out that in multithreaded code, the
Python GIL can cause additional performance problems beyond what has already been
mentioned in other points: Dave Beazley presented a talk, “Understanding the Python GIL,”
at PyCon 2010, and much of what is discussed in that talk remains true today.
Asyncio prevents all race conditions

False. The possibility of race conditions is always present with any concurrent
programming, regardless of whether threading or event­based programming is used. It is
true that Asyncio can virtually eliminate a certain class of race conditions common in
multithreaded programs, such as intra­process shared memory access. However, it doesn’t
eliminate the possibility of other kinds of race conditions, for example inter­process races
with shared resources common in distributed microservices architectures. You must still pay
attention to how shared resources are being used. The main advantage of Asyncio over
threaded code is that the points at which control of execution is transferred between
coroutines are visible (due to the presence of await keywords), and thus it is much easier
to reason about how shared resources are being accessed.

Asyncio makes concurrent programming easy

Ahem, where do I even begin?

The last myth is the most dangerous one. Dealing with concurrency is always complex,
regardless of whether you’re using threading or Asyncio. When experts say, “Asyncio makes
concurrency easier,” what they really mean is that Asyncio makes it a little easier to avoid
certain kinds of truly nightmarish race­condition bugs; the kind that keep you up at night, and
about which you tell other programmers in hushed tones over campfires, wolves howling in the
distance.

Even with Asyncio, there is still a great deal of complexity to deal with. How will your
application support health checks? How will you communicate with a database which may
allow only a few connections, much fewer than your 5,000 socket connections to clients? How
will your program terminate connections gracefully when you receive a signal to shut down?
How will you handle (blocking!) disk access and logging? These are just a few of the many
complex design decisions that you will have to answer.

Application design will still be difficult, but the hope is that you will have an easier time
reasoning about your application logic when you have only one thread to deal with.

1 Research in this area seems hard to find, but the numbers seem to be around 50
microseconds per threaded context­switch on Linux on modern hardware. To give a (very)
rough idea: a thousand threads implies 50 ms total cost just for the context switching. It
does add up, but it isn’t going to wreck your application either.

2 The global interpreter lock (GIL) makes the Python interpreter code (not your code!)
thread­safe by locking the processing of each opcode; it has the unfortunate side effect of
effectively pinning the execution of the interpreter to a single CPU, and thus preventing
multicore parallelism.

3 This is similar to how JavaScript lacks a GIL “problem”: there is only one thread.
Chapter 2. The Truth About Threads
History

Topics
Let’s be frank for a moment—you really don’t want to use Curio. All things equal, you should
probably be programming with threads. Yes, threads. THOSE threads. Seriously. I’m not
Tutorials
kidding.1
—Dave Beazley, Developing with Curio
Offers & Deals
This is the part where I tell you, “Threads are terrible and you should never use them,” right?
Unfortunately,
Highlights the situation is not so simple. We need to weigh the benefits and risks with using
threads, just like any technology choice.
Settings
This book is not supposed to be about threads at all. But the problem is that (a) Asyncio is
offered
Support as an alternative to threading, so it’s hard to understand the value proposition without
some comparison; and (b), even when using Asyncio you will still likely have to deal with
Sign Out
threads and processes, so you still need to know something about threading regardless.

IMPORTANT

The context of this discussion is exclusively concurrency in network programming


applications. Pre­emptive multithreading is also used in other domains, in which
case the trade­offs are entirely different.

Benefits of Threading
These are the main benefits of threading:

Ease of reading code

Your code can run concurrently but still be set out in a very simple, top­down linear
sequence of commands to the point where—and this is key—you can pretend within the
body of your functions, that no concurrency is happening.
Parallelism with shared memory

Your code can exploit multiple CPUs while still having threads share memory. This is
important in many workloads where it would be too costly to move large amounts of data
between the separate memory spaces of different processes, for example.

Know­how and existing code

There is a large body of knowledge and best practice for writing threaded applications.
There is also a huge amount of existing “blocking” code that depends on multithreading for
concurrent operation.

Now, with Python, the point about parallelism is questionable because the Python interpreter
uses a global lock, called the Global Interpreter Lock, to protect the internal state of the
interpreter itself—protection from the potential catastrophic effects of race conditions between
multiple threads. A side effect of the lock is that it ends up pinning all threads in your program
to a single CPU. As you might imagine, this negates any parallelism performance benefits
(unless you use tools like Cython or Numba to maneuver around the limitation).

The first point regarding perceived simplicity, however, is significant: threading in Python feels
exceptionally simple, and if you haven’t been burned before by impossibly hard race­condition
bugs, threading offers a very attractive concurrency model. And if you have been burned in the
past, threading remains a compelling option because you will likely have learned (the hard way)
how to keep the code both simple and safe.

I don’t have space to get into safer threaded programming here, but generally speaking, the best
practice for using threads is to use the ThreadPoolExecutor() class from the
concurrent.future module, passing all required data in through the submit() method.
Here is a basic example:

Example 2­1. Best practice for threading

from concurrent.future import ThreadPoolExecutor as Executor

def worker(data):
<process the data>

with Executor(max_workers=10) as exe:


future = exe.submit(worker, data)

The ThreadPoolExecutor offers an extremely simple interface for running functions in a


thread—and the best part is that, if needed, you can convert the pool of threads into a pool of
subprocesses simply by using ProcessPoolExecutor instead. It has the same API as
ThreadPoolExecutor which means that your code will be little­affected by the change.
The executor API is also used in asyncio and is described in a later section.

In general you would prefer that your tasks are somewhat short­lived, so that when your
program needs to shut down, you can simply call Executor.shutdown(wait=True) and
wait a second or two to allow the executor to complete.

Most important: if at all possible, try to prevent your threaded code (in the example above, the
worker() function) from accessing or writing to any global variables!

Several great guidelines for safer threaded code were presented by Raymond Hettinger at PyCon
Russia 2016 and again at PyBay 2017 and I strongly urge you to add these videos to your watch
list.

Drawbacks of Threading
…non­trivial multithreaded programs are incomprehensible to humans. It is true that the
programming model can be improved through the use of design patterns, better granularity of
atomicity (e.g. transactions), improved languages, and formal methods. However, these
techniques merely chip away at the unnecessarily enormous non­determinism of the threading
model. The model remains intrinsically intractable.2
—Edward A Lee, The Problem with Threads ­ Technical Report No.
UCB/EECS­2006­1 Electrical Engineering and Computer Sciences ­
University of California at Berkeley

These are not new, and have been mentioned in several other places already, but for
completeness let’s collect them here anyway:

Threading bugs and race conditions in threaded programs are the hardest kinds of bugs to
fix. With experience, it is possible to design new software that is less prone to these
problems, but in non­trivial, naively designed software they can be nearly impossible to
fix, even by experts! Really!

Threads are resource­intensive and require extra operating­system resources to create,


such as pre­allocated, per­thread stack space which consumes process virtual memory up
front. This is a big problem with 32­bit operating systems, because the address space per
process is limited to 3 GB.3 Nowadays, with the widespread availability of 64­bit
operating systems, virtual memory isn’t as precious as it used to be (addressable space for
virtual memory is typically 48 bits, i.e., 256 TiB), and on modern desktop operating
systems, the physical memory required for stack space for each thread isn’t even allocated
by the OS until it is required, including stack space per thread. For example, on a modern,
64­bit Fedora 27 Linux with 8 GB memory, creating 10,000 do­nothing threads with this
short snippet…

# threadmem.py
import os
from time import sleep
from threading import Thread
threads = [
Thread(target=lambda: sleep(60)) for i in range(10000)
]
[t.start() for t in threads]
print(f'PID = {os.getpid()}')
[t.join() for t in threads]

…leads to the following information in top:

MiB Mem : 7858.199 total, 1063.844 free, 4900.477 used


MiB Swap: 7935.996 total, 4780.934 free, 3155.062 used

PID USER PR NI VIRT RES SHR COMMAND


15166 caleb 20 0 80.291g 131.1m 4.8m python3

Pre­allocated virtual memory is a staggering ~80 GB (due to 8 MB stack space per


thread!), but resident memory is only ~130 MB. On a 32­bit Linux, I would be unable to
create this many threads due to the 3 GB userspace address­space limit, regardless of
actual consumption of physical memory. To get around this problem (on 32­bit), it is
sometimes necessary to decrease the pre­configured stack size, which you can still do in
Python today, with threading.stack_size([size]). Obviously, decreasing stack
size has implications for runtime safety with respect to the degree to which function calls
may be nested, including recursion. Single­threaded coroutines have none of these
problems and are a far superior alternative for concurrent I/O.

At very high concurrency levels (say, >5,000 threads), there can also be an impact on
throughput due to context­switching costs,4 ,5 assuming you can figure out how to
configure your operating system to even allow you to create that many threads! It has
become so tedious now on recent macOS versions, for example, to test my 10,000 do­
nothing­threads example above, that I gave up trying to raise the limits at all.

Threading is inflexible: the operating system will continually share CPU time with all
threads regardless of whether a thread is ready to do work or not. For instance, a thread
may be waiting for data on a socket, but the OS scheduler may still switch to and from that
thread thousands of times before any actual work needs to be done. (In the async world,
the select() system call is used to check whether a socket­awaiting coroutine needs a
turn, and if not, that coroutine isn’t even woken up, avoiding any switching costs
completely.)

None of this information is new, and the problems with threading as a programming model are
not platform­specific either. For example, this is what the Windows MSDN programming­
guidelines documentation says about threading:

The central concurrency mechanism in the Windows API is the thread. You typically use the
CreateThread function to create threads. Although threads are relatively easy to create and
use, the operating system allocates a significant amount of time and other resources to manage
them. Additionally, although each thread is guaranteed to receive the same execution time as
any other thread at the same priority level, the associated overhead requires that you create
sufficiently large tasks. For smaller or more fine­grained tasks, the overhead that is associated
with concurrency can outweigh the benefit of running the tasks in parallel.6
—MSDN Programming Guidelines, Comparing the Concurrency Runtime to
Other Concurrency Models

But—I hear you protest—this is Windows, right? Surely a UNIX system doesn’t have these
problems? Here follows a similar recommendation from the Mac Developer Library:

Threading has a real cost to your program (and the system) in terms of memory use and
performance. Each thread requires the allocation of memory in both the kernel memory space
and your program’s memory space. The core structures needed to manage your thread and
coordinate its scheduling are stored in the kernel using wired memory. Your thread’s stack
space and per­thread data is stored in your program’s memory space. Most of these structures
are created and initialized when you first create the thread—a process that can be relatively
expensive because of the required interactions with the kernel.7
—Mac Developer Library, Threading Programming Guide

They go even further in the Concurrency Programming Guide (emphasis mine):


In the past, introducing concurrency to an application required the creation of one or more
additional threads. Unfortunately, writing threaded code is challenging. Threads are a low­
level tool that must be managed manually. Given that the optimal number of threads for an
application can change dynamically based on the current system load and the underlying
hardware, implementing a correct threading solution becomes extremely difficult, if not
impossible to achieve. In addition, the synchronization mechanisms typically used with threads
add complexity and risk to software designs without any guarantees of improved performance.
8

—Mac Developer Library, Concurrency Programming Guide

These themes repeat throughout:

threading makes code hard to reason about

threading is an inefficient model for large­scale concurrency (thousands of concurrent


tasks)

Next we look at a case study involving threads in which I intend to highlight the first and most
important point.

Case Study: Robots and Cutlery


Second, and more important, we did not (and still do not) believe in the standard
multithreading model, which is preemptive concurrency with shared memory: we still think that
no one can write correct programs in a language where “a = a + 1” is not deterministic.9
—Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar
Celes, The Evolution of Lua

At the start of this book we heard the story of a restaurant in which humanoid robots—
ThreadBots—did all the work. In that analogy, each worker was a thread. In this case study
we’re going to look at why threading is considered unsafe.

Example 2­2. ThreadBot programming for table service

import threading
from queue import Queue

class ThreadBot(threading.Thread):
def __init__(self):
super().__init__(target=self.manage_table)
self.cutlery = Cutlery(knives=0, forks=0)
self.tasks = Queue()

def manage_table(self):
while True:
task = self.tasks.get()
if task == 'prepare table':
kitchen.give(to=self.cutlery, knives=4, forks=4)
elif task == 'clear table':
self.cutlery.give(to=kitchen, knives=4, forks=4)
elif task == 'shutdown':
return

A ThreadBot is a subclass of a thread.

The target function of the thread is the manage_table() method, defined further below.

This bot is going to be waiting tables, and will need to be responsible for some cutlery. Each
bot keeps track of the cutlery that it took from the kitchen here. (The Cutlery class will be
defined later.)

The bot will also be assigned tasks. They will be added to this task queue, and the bot will
perform them during its main processing loop, next.

The primary routine of this bot is this infinite loop. If you need to shut a bot down, you have
to give them the "shutdown" task.

There are only three tasks defined for this bot. This one, "prepare table" is what the
bot must do to get a new table ready for service. For our test, the only requirement is to get
sets of cutlery from the kitchen and place them on the table. "clear table" is for when
a table is to be cleared: the bot must return the used cutlery back to the kitchen.
"shutdown" just shuts the bot down.

And now, the definition of the cutlery object:

Example 2­3. The definition of the Cutlery

from attr import attrs, attrib

@attrs
class Cutlery:
knives = attrib(default=0)
forks = attrib(default=0)

def give(self, to: 'Cutlery', knives=0, forks=0):


self.change(­knives, ­forks)
to.change(knives, forks)
def change(self, knives, forks):
self.knives += knives
self.forks += forks

kitchen = Cutlery(knives=100, forks=100)


bots = [ThreadBot() for i in range(10)]

import sys
for bot in bots:
for i in range(int(sys.argv[1])):
bot.tasks.put('prepare table')
bot.tasks.put('clear table')
bot.tasks.put('shutdown')

print('Kitchen inventory before service:', kitchen)


for bot in bots:
bot.start()

for bot in bots:


bot.join()
print('Kitchen inventory after service:', kitchen)

attrs, which is an open source Python library that has nothing to do with threads or
asyncio, is a really wonderful library for making class creation easy. Here, the @attrs
decorator will ensure that this Cutlery class will get all the usual boilerplate code (like
__init__()) automatically set up.

The attrib function provides an easy way to create attributes, including defaults, which
you might normally have handled as keyword arguments in the __init__() method.

This method is used to transfer knives and forks from one Cutlery object to another.
Typically it will be used by bots to obtain cutlery from the kitchen for new tables, and to
return the cutlery back to the kitchen after the table is cleared.

This is a very simple utility function for altering the inventory data in the object instance.

We’ve defined kitchen as the identifier for the kitchen inventory of cutlery. Typically,
each of the bots will obtain cutlery from this location. It is also required that they return
cutlery to this store when a table is cleared.

This script is executed when testing. For our test we’ll be using 10 threadbots.

We get the number of tables as a command­line parameter, and then give each bot that
number of tasks for preparing and clearing tables in the restaurant.

The “shutdown” will make the bots stop (so that bot.join() a bit further down will
return). The rest of the script prints diagnostic messages and starts up the bots.

Your strategy for testing the code basically involves running a group of threadbots over a
sequence of table service. Each threadbot must:

prepare a “table for four,” which means obtaining four sets of knives and forks from the
kitchen;

clear a table, which means returning the set of four knives and forks from a table back to
the kitchen.

If you run a bunch of threadbots over a bunch of tables a specific number of times, you expect
that after all the work is done, all of the knives and forks should be back in the kitchen and
accounted for.

Wisely, you decide to test that, and with 100 tables to be prepared and cleared for each
threadbot, and all of them operating at the same time, because you want to ensure that they can
work together and nothing goes wrong. This is the output of that test:

$ python cutlery_test.py 100


Kitchen inventory before service: Cutlery(knives=100, forks=100)
Kitchen inventory after service: Cutlery(knives=100, forks=100)

All the knives and forks end up back in the kitchen! So you congratulate yourself on writing
good code and deploy the bots. Unfortunately, in practice, every now and then you find that you
do not end up with all cutlery accounted for when the restaurant closes. You noticed the
problem gets worse when you add more bots and/or the place gets busier. Frustrated, you run
your tests again, changing nothing except the size of the test (10,000 tables!):

$ python cutlery_test.py 10000


Kitchen inventory before service: Cutlery(knives=100, forks=100)
Kitchen inventory after service: Cutlery(knives=96, forks=108)

Oops. Now you see that there is indeed a problem. With 10,000 tables served, you end up with
the wrong number of knives and forks left in the kitchen. For reproducibility, you check that the
error is consistent:
$ python cutlery_test.py 10000
Kitchen inventory before service: Cutlery(knives=100, forks=100)
Kitchen inventory after service: Cutlery(knives=112, forks=96)

There are still errors, but by different amounts compared to the previous run. That’s just
ridiculous! Remember, these bots are exceptionally well­constructed and they don’t make
mistakes. What could be going wrong?

Discussion

Let’s summarize the situation:

Your ThreadBot code is very simple and easy to read. The logic is fine.

You even have a working test (with 100 tables) that reproducibly passes.

You have a longer test (with 10,000 tables) that reproducibly fails.

The longer test fails in different, non­reproducible ways.

These are a few typical signs of a race­condition bug. Experienced readers will already have
seen the cause, so let’s investigate that now. It all comes down to this method inside our
Cutlery class:

def change(self, knives, forks):


self.knives += knives
self.forks += forks

The inline summation, +=, is implemented internally (inside the C code for the Python
interpreter itself) as a few separate steps:

1. Read the current value, self.knives, into a temporary location.

2. Add the new value, knives to the value in that temporary location.

3. Copy the new total from the temporary location back into the original location.

The problem with preemptive multitasking is that any thread busy with the steps above can be
interrupted at any time, and a different thread can be given the opportunity to work through the
same steps.

In this case, ThreadBot A might do step 1, then the OS scheduler pauses A and switches to
ThreadBot B, and B also reads the current value of self.knives, then execution goes back
to A, and it increments and writes back its new total—but then B continues from where it got
paused (after step 1), and then increments and writes back its new total, thereby erasing the
change made by A!

WARNING

If describing this race condition sounds complex, please keep in mind that this
example (of a race condition) is just about the simplest possible case. We were able
to check all the code, and we even have tests that can reproduce the problem on
demand. In the real world, in large projects, try to imagine how much more difficult
it can become.

This problem can be fixed by placing a lock around the modification of the shared state
(imagine we added a threading.Lock to the Cutlery class):

def change(self, knives, forks):


with self.lock:
self.knives += knives
self.forks += forks

But this requires you to know all the places where state will be shared between multiple threads.
This approach is viable when you control all the source code, but it becomes very difficult when
many third­party libraries are used—which is likely in Python thanks to the wonderful open
source ecosystem.

Note that it was not possible to see the race condition by looking at the source code alone. This
is because the source code provides no hints about where execution is going to switch between
threads. That wouldn’t be useful anyway, because the OS can switch between threads just about
anywhere.

Another, much better, solution—and the point of async programming—is that we modify our
code so that we use only one ThreadBot and configure it to move between all the tables as
necessary. For our case study, this means that the knives and forks in the kitchen will only ever
get modified by a single thread.

And even better: in our async programs we’ll be able to see exactly where context will switch
between multiple concurrent coroutines, because the await keyword indicates such places
explicitly. I’ve decided against showing an async version of this case study here, because the
next chapter is going to explain how to use asyncio in depth. But if your curiosity is
insatiable, there is an annotated example in the appendix; it’ll probably only make sense after
you read the next chapter!

1 https://curio.readthedocs.io/en/latest/devel.html#please­don­t­use­curio

2 http://bit.ly/2CFOv8a

3 The theoretical address space for a 32­bit process is 4 GB, but the operating system
typically reserves some of that. Often, only 3 GB is left to the process as addressable virtual
memory, but on some operating systems it can be as low as 2 GB. Please take the numbers
mentioned in this section as generalizations and not absolutes. There are far too many
platform­specific (and historically sensitive) details to get into here.

4 http://blog.tsunanet.net/2010/11/how­long­does­it­take­to­make­context.html

5 https://en.wikipedia.org/wiki/Context_switch#Cost

6 http://bit.ly/2Fr3eXK

7 https://apple.co/2BMeM83

8 https://apple.co/2owWwHM

9 http://bit.ly/2Fq9M8P
Playlists

Chapter 3. Asyncio Walk-Through


History

Topics
Asyncio provides another tool for concurrent programming in Python, that is more lightweight
than threads or multiprocessing. In a very simple sense it does this by having an event loop
Tutorials
execute a collection of tasks, with a key difference being that each task chooses when to yield
control back to the event loop.1
Offers & Deals
—Philip Jones, Medium

The Asyncio
Highlights API in Python is complex because it aims to solve different problems for different
groups of people. Unfortunately, there is very little guidance available to help you figure out
Settings
which parts of asyncio are important for the group you’re in.

Support
My goal is to help you figure that out. There are two main target audiences for the async
features in Python:
Sign Out

End­user developers

These want to make applications using asyncio. I am going to assume that you’re in this
group.

Framework developers

These want to make frameworks and libraries that end­user developers can use in their
applications.

Much of the confusion around asyncio in the community today is due to confusion between
these two goals. For instance, the documentation for asyncio in the official Python
documentation is more appropriate for framework developers, not end users. This means that
end­user developers reading those docs quickly become shell­shocked by the apparent
complexity. You’re somewhat forced to take it all in before being able to do anything with it.

It is my hope that this book can help to separate, in your mind, the features of asyncio that are
important for end­user developers and those important for framework developers.
TIP

If you’re interested in the lower­level details around how concurrency frameworks


like asyncio are built internally, I highly recommend a wonderful talk by Dave
Beazley, Python Concurrency From the Ground Up: LIVE!, in which he
demonstrates putting together a simpler version of an async framework like
asyncio.

My goal is to give you only the most basic understanding of the building blocks of Asyncio;
enough that you should be able to write simple programs with it, and certainly enough that you
will be able to dive into more complete references.2

First up, we have a “quickstart” section that aims to provide the most important building blocks
for asyncio applications.

Quickstart
You only need to know about seven functions to use asyncio [for everyday use].
—Yury Selivanov, author of PEP 492

It’s pretty scary to open the official documentation for Asyncio. There are many sections with
new, enigmatic words, and concepts that will be unfamiliar to even experienced Python
programmers, as asyncio is a very new thing. We’re going to break all that down and explain
how to approach the asyncio documentation later, but for now you need to know that the
actual surface area you have to worry about with the asyncio library is much smaller than it
seems.

Yury Selivanov, the author of PEP 492 and all­round major contributor to async Python,
explained in his talk async/await in Python 3.5 And Why It Is Awesome, presented at PyCon
2016 that many of the APIs in the asyncio module are really intended for framework
designers, not end­user developers. In that talk, he emphasized the main features that end users
should care about, and these are a small subset of the whole asyncio API.

In this section we’re going to look at those core features, and see how to hit the ground looping
with event­based programming in Python.

You’ll need a basic knowledge of coroutines (presented in the section after this one), but except
for that, if you want to be able to make use of the asyncio library as an end­user developer
(and not a framework designer), these are the things you need to know, with a tiny example:
Example 3­1. The “Hello World” of Asyncio

# quickstart.py
import time
import asyncio

async def main():


print(f'{time.ctime()} Hello!')
await asyncio.sleep(1.0)
print(f'{time.ctime()} Goodbye!')
loop.stop()

loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_forever()
pending = asyncio.Task.all_tasks(loop=loop)
group = asyncio.gather(*pending, return_exceptions=True)
loop.run_until_complete(group)
loop.close()

Output:

$ python quickstart.py
Sun Sep 17 14:17:37 2017 Hello!
Sun Sep 17 14:17:38 2017 Goodbye!

loop = asyncio.get_event_loop()
You need a loop instance before you can run any coroutines, and this is how you get one. In
fact, anywhere you call it, get_event_loop() will give you the same loop instance
each time, as long as you’re using only a single thread.3

task = loop.create_task(coro)
In the code above, the specific invocation is loop.create_task(main()). Your
coroutine function will not be executed until you do this. We say that create_task()
schedules your coroutine to be run on the loop. The returned task object can be used to
monitor the status of the task, for example whether it is still running or has completed, and
can also be used to obtain a result value from your completed coroutine. You can also cancel
the task with task.cancel().4

loop.run_until_complete(coro) and loop.run_forever()


These are the two ways to get the loop running. Both of these will block the current thread,
which will usually be the main thread. Note that run_until_complete() will keep the
loop running until the given coro completes—but all other tasks scheduled on the loop will
also run while the loop is running.

group = asyncio.gather(task1, task2, task3)


The typical idiom for most programs will be to start off with loop.run_forever() for
the “main” part of the program, and then when a process signal is received, stop the loop,
gather the still­pending tasks, and then use loop.run_until_complete() until those
tasks are done. This is the method for doing the gathering. More generally, it can also be
used to gather multiple coroutines together and wait (using await!) for all of the gathered
tasks to finish.

loop.stop() and loop.close()


As described further above, these are used to gradually bring a program to a standstill.
stop() is usually called as a consequence of some kind of shutdown signal being received,
and close() is usually the final action: it must be called on a stopped loop, and it will
clear all queues and shut down the Executor. A “stopped” loop can be restarted; a “closed”
loop is gone for good.

CAUTION

The preceding example is too simplistic to be useful in a practical setting. More


information around correct shutdown handling is required. The goal of the example
was merely to introduce the most important functions and methods in asyncio.
More practical information for shutdown handling is presented later in the book.

asyncio in Python exposes a great deal of the underlying machinery around the event loop—
and requires you to be aware of things like the event loop and its lifetime management. This is
different from Node.js for example, which also contains an event loop, but keeps it somewhat
hidden away. However, once you’ve worked with asyncio for bit, you’ll begin to notice that
the pattern for starting up and shutting down the event loop doesn’t stray terribly far from the
code above. And in the remainder of this book we will examine some of the nuances around
loop lifetime in more detail too.

I left something out in the example above. The last item of basic functionality you’ll need to
know is how to run blocking functions. The thing about cooperative multitasking is that you
need all I/O­bound functions to…well, cooperate, and that means allowing a context switch
back to the loop using the keyword await. Most of the Python code available in the wild today
does not do this, and instead relies on you to run such functions in threads. Until there is more
widespread support for async def functions, you’re going to find that using such blocking
libraries is unavoidable.

For this, asyncio provides an API that is very similar to the API in the
concurrent.futures package. This package provides a ThreadPoolExecutor and a
ProcessPoolExecutor. The default is thread­based, but is easily replaced with a process­
based one. I omitted this from the previous example because it would have obscured the
description of how the fundamental parts fit together. Now that those are covered, we can look
at the executor directly.

There are a couple of quirks to be aware of. Let’s have a look at a code sample:

Example 3­2. The basic executor interface

# quickstart_exe.py
import time
import asyncio

async def main():


print(f'{time.ctime()} Hello!')
await asyncio.sleep(1.0)
print(f'{time.ctime()} Goodbye!')
loop.stop()

def blocking():
time.sleep(0.5)
print(f"{time.ctime()} Hello from a thread!")

loop = asyncio.get_event_loop()

loop.create_task(main())
loop.run_in_executor(None, blocking)

loop.run_forever()

pending = asyncio.Task.all_tasks(loop=loop)
group = asyncio.gather(*pending)
loop.run_until_complete(group)
loop.close()

Output:

$ python quickstart_exe.py
Sun Sep 17 14:17:38 2017 Hello!
Sun Sep 17 14:17:38 2017 Hello from a thread!
Sun Sep 17 14:17:39 2017 Goodbye!
Exploring the Variety of Random
Documents with Different Content
The Project Gutenberg eBook of Naisten
kasvatuksesta
This ebook is for the use of anyone anywhere in the United States
and most other parts of the world at no cost and with almost no
restrictions whatsoever. You may copy it, give it away or re-use it
under the terms of the Project Gutenberg License included with this
ebook or online at www.gutenberg.org. If you are not located in the
United States, you will have to check the laws of the country where
you are located before using this eBook.

Title: Naisten kasvatuksesta


Havaintoja ja mietteitä

Author: Lucina Hagman

Release date: April 30, 2024 [eBook #73501]

Language: Finnish

Original publication: Porvoo: Werner Söderström, 1888

Credits: Tuula Temonen

*** START OF THE PROJECT GUTENBERG EBOOK NAISTEN


KASVATUKSESTA ***
NAISTEN KASVATUKSESTA

Havaintoja ja mietteitä

Kirj.

LUCINA HAGMAN

Suomen Naisyhdistyksen palkitsema

Porvoossa, Werner Söderström, 1888.

Kysymys naisen kasvatuksesta liittyy likeisesti laveampaan


aineesen hänen kohtalostaan ja asemastaan ihmiskunnassa
yleensä. Sen vuoksi ei ole mahdollista edellistä käytellä, jollei
samalla myöskin ota puheeksi jälkimäistä. Sillä kasvatus, jonka
alaisena nainen on ollut, ei suinkaan ole pidettävä vaan
satunnaisuuden tuotteena, vaan perustuu kieltämättä yleisiin
käsityksiin ja tapoihin. Sen mukaan kuin nämä käsitykset ja tavat
yleisen kehityksen lakia noudattaen muuttuvat, muuttuu ikään
kasvatus ja taipuu syntyvien uusien vaatimuksien alle.
Meidän aikamme pyrkii tasoittamaan niin paljo kuin mahdollista.
Se tahtoo tasoittaa ja sovittaa näennäisesti ristiriitaisia aatteita, eri
kansanluokkien väliä ja varallisuutta, kaavoitettujen uskonoppien ja
omantunnon vapauden väliä ja välijuopaa miehen ja naisen
asemassa. Ei ole siis ihmeteltävä että kysymys naisten
kasvatuksesta tunkee tutkisteluun ja vaatii kansalaisten huomiota. Ja
kuta enemmän vanhat käsitykset tyttärien kohtalosta elämässä
alkavat horjua, sitä levottomammasti vanhemmat ja kasvattajat
rupeavat tarkastamaan sen toiminnan laatua, jolla he tyttäriänsä
elämälle valmistavat. Kun seuraavilla riveillä otamme puheeksi
naisen kasvatuksen ja sen puutteet, vaatii siis asia että samassa
myös kosketamme niiden periaatteiden luontoa, joihin kasvatuksen
tähänastinen suunta ja laatu on perustunut.
I.

Mikä nainen on, mimmoinen on hänen luontonsa, mikä on oleva


hänen osansa? Siinä kysymys, jonka tutkistelemista on kestänyt
historian alusta. Oppineet ovat asiasta laatineet laveita järjestelmiä.
He ovat rakentaneet muodon niille käsityksille, joita aika on
synnyttänyt. Ja samalla kun oppineet ja tiedemiehet neronsa
voimalla ovat tehneet omaa tehtäväänsä, ovat runoilijat puolestaan
varovasti asettaneet naisen seisomaan mielikuvastimensa eteen ja
siinä häntä tarkastaneet. Ja mimmoiselta on hän näyttänyt heidän
peilissään? Muutamain peilissä enkeliltä, toisten hirviöltä, toisten
kukkaselta, harvoin ihmiseltä ainoastaan. Mutta missä muodossa
hän onkin ilmestynyt, mitä ominaisuuksia hänessä liekin havaittu —
hyviä tai pahoja, — niin aina on rakentelija tai katselija ne niin
perkaellut ja sommitellut, että kokonaisuus on vastannut hänen
mielitekoonsa ja sopinut hänen järjestelmäänsä. Jos hän on
tavannut jonkun ominaisuuden, joka ei ole häntä miellyttänyt, on hän
sanonut: "Sitä ominaisuutta naisessa ei ole." Jos taas on ominaisuus
puuttunut, joka hänestä oli tarpeellinen, on hän tuon ominaisuuden
naiselle pannut.

Tällä tavalla on syntynyt yleiseen käsitykseen personaton käsite.


Ja me erehtyisimme suuresti jos luulisimme tämän luoman olleen
ilman merkitystä naissuvun kohtalon kehityksessä. Tämä yleisten
katsantotapain synnyttämä käsite, jolle tiedemiehet ja runoilijat ovat
laatineet muodon, tämä kirjoitettujen psykologiain naisihanne on se
nainen, jonka kanssa etusijassa miehet kaikkina aikoina ovat
huvitelleet. Tämä nainen se on, jolle he ovat kumartaneet, jota he
ovat imarrelleet ja jolle he ovat polvilleen langenneet. Tämä nainen
se on, jolle he juhlatiloissa puheita pitävät, jota he ylistävät.

Ovatko naiset todellisuudessa sellaisia, jommoisiksi heitä


oletetaan, siihen harvoin haetaan vastausta. Tahdon kertoa
tapauksesta, joka siihen nähden saattaa olla valaiseva, vaikka se ei
olekkaan sääntönä käsitettävä. Hegelin filosofiaa tunnustava
maisteri piti eräässä maaseutukaupungissamme luennoita
psykologiassa. Hän oli joutunut myöskin siihen oppikohtaan, joka
sanoo että nainen — jonkun sielullisen ominaisuuden puutteessa —
ei kykene ystävyyteen. Kerrotaan jonkun kysyneen häneltä
jälestäpäin josko hän todella uskoi esiintuomaa väitettänsä. Hänen
sanotaan vastanneen; "Niin, ainakin se niin on minun systeemini
mukaan." Ja systeemi kuuluu näin: Päinvastaisuuden lain mukaan
naisella ei voi olla reflektionia eli ajattelemisen ominaisuutta, (koska
se on miehellä). Ystävyys perustuu reflektioniin, nainen ei kykene
reflektioniin; päätös: nainen ei kykene ystävyyteen.

Sillä lailla on nais-käsitteen täytynyt palvella systeemiä. Ja


olkoonpa naiselle myönnetty ominaisuuksia, jotka ovat hänen
asettaneet miestä ylemmäksi, taikka semmoisia annettu, jotka ovat
hänen alentaneet miestä alemmaksi, niin on varomalla varottu ettei
häntä miehen vertaiseksi selvitettäisi.

Sanoimme ettei tämä sepitetty nais-ihanne suinkaan ole ollut


merkitystä vailla naisten kohtalon kehityksessä. Tiedemiehet kun
ovat naisen olentoa määritelleet ovat pysyneet kiinni mielivaltaisiin
edellytyksiin perustuvassa järjestelmässään. Tämä on vuoronsa taas
vaikuttanut ja niin on yleinen käsitys naisesta joutunut yksipuoliseen
suuntaan. Häntä on arvosteltu jonkinmoisena esineenä jota tutkijain
on sopinut pidellä miten tahansa, pääasia vaan että hän on sopinut
"systeemiin." Semmoisena on nainen miellyttävin, kuuluu lausunto,
sen vuoksi hän semmoinen 27 % ja sen vuoksi tulee hänen
semmoisena pysyä. Siinä tapaamme syyn ja samassa sen maalin,
johon tutkimus on tähdännyt. Ja siinä on vihdoin se perus, jolle sitten
lainsäädäntö naisesta on rakennettu.

Kuta mielivaltaisempi ja abstraktisempi jonkun aikakauden käsitys


naisesta on ollut, sitä hullunkurisempina tapaamme naistyypit saman
ajan kirjallisuudessa. Uudempi aika on tuonut muassaan uusia
periaatteita kirjallisuuden viljelykseen. Kirjailijan velvollisuutta
tarkastaa olevaa elämää ja tehdä havaintonsa uskollisesti
todellisuudesta, ei ennen paljo kysytty. Ei myöskään annettu lupaa
jokapäiväisen elämän synnyttämiin ihmisluonteihin. Auktoriteetti-
usko ulottui kaunokirjallisuuden alallekin; niinkuin kirjailijat kertoivat
ja runoilijat lauloivat, niin se oli. Sillä lailla istutettiin yhä kaavoitellut
opit ja tyypit suuren yleisön tiedollisuuteen ja näyttäytyivät uudestaan
eri muodoissa, yleisissä mielipiteissä ja tavoissa. Ja niin edespäin
nuorison kasvatuksessa, jonka kautta sitte käsitykset polveutuivat
seuraavaan sukuun.

Syntymähetkestä asti on nainen saanut: kärsiä syrjäytetyn


kohtaloa. Kun lapsi tuli maailmaan, kun ensimmäinen äännähdys
kuului vastasyntyneen huulilta, kysyttiin huolellisesti mikä se oli.
"Tyttö" — ja tuo sana on: usein vaikuttanut isässä
välinpitämättöimyyttä, äidissä surua ja huolta. Tyttären syntymistä on
pidetty miltei häpeällisenä. — Juutalaisissa oli sääntönä että äiti, jos
hän oli synnyttänyt pojan, oli suljettu Herran huoneesta
neljäkymmentä päivää, mutta kun syntynyt oli tyttö;
kahdeksankymmentä päivää. Kun enimmissä kansoissa pojan
syntymä otettiin vastaan juhlallisuuksilla ja uhreilla, antoi tytön
syntymä syytä alakuloisuuteen ja mielipahaan. Hindulaisten
uskonnollisten käsitysten mukaan ei ihminen yksin kyennyt
voittamaan itselleen onnellista elämää tuolla puolen hautaa. Tämä
riippui jälkeentulevaisten teoista ja elämästä, ja heidän
käsityksessään oli tämmöinen vanhempainsa autuuttamisen voima
ainoastaan poijilla. Pojan syntyessä kiirehti isä tuomaan hunajaa ja
kultaa, luonnon parhaita antimia. Hän voiteli vastasyntyneensä
huulet hunajalla ja antoi sille hellimpiä nimiä, ja äitikin sai siinä
tapauksessa ottaa vastaan kunnianosoituksia.

Jos lapsi oli tyttö, ei kuultu laulaa eikä vietetty juhlallisuutta; heidän
uskontonsa ei puhunut mitään millä: hän oli elämään vastaan
otettava. Tytöllä ei ollut mitään merkitystä suvun taikka kansan
kehityksessä. Kunnioituksen sijaan sai äiti kokea kylmyyttä ja
ylenkatsetta mieheltään. Jos hän synnytti ainoastaan tyttöjä oli
miehellä oikeus hyljätä hänet yhdentoista vuoden jälkeen. —
Athenassa oli isän tapana poikalapsen synnyttyä koristaa porttinsa
öljylehden seppeleillä, sillä tavoin kaikille ohikulkijoille
osoittaaksensa sitä onnea, joka häntä oli kohdannut. Jos lapsi oli
tyttö käski hän palvelijansa ripustamaan kehrävarsi porttiinsa.
Spartalaiset pitivät tytön sukua miltei viallisuutena. Kymmenestä
lapsesta, jotka heitettiin kuolemaan oli tavallisesti seitsemän tyttöjä.
Roomassa oli tapana asettaa vastasyntynyt lapsi isän jalkain
juureen. Varjeluksen merkiksi nosti isä lapsen ylös; mutta jätti
paikoilleen ja meni tiehensä, jos aikoi sen hyljätä. Monista
kuninkaista ja ruhtinoista keskiajalta kertoo historia, miten he kun
kohtalo ei heille suonut poikia, hylkäsivät ja luotaan karkoittivat
tyttölapsensa.

Jos menneistä ajoista käännämme silmämme uudempaan aikaan,


emme enää huomaa eroitusta näin jyrkkänä, mutta kokonaan
tasoittunut ei suhde kuitenkaan ole. Vanha tosiasia on vielä jälellä.
Tytön syntymä usein ei herätä vanhemmissa samaa iloa kuin jos
lapsi on poika.

Miksi tyttären syntymistä on pidetty ja pidetään monasti vielä


vähemmän onnellisena kuin pojan syntyä? Onhan isälle syntynyt se
olento, jolle luonnon sanotaan lahjoittaneen suloisuuden, jota
luonnon ja Luojan sanotaan määränneen miellyttämään ja
valmistamaan muille huvitusta, olemaan "miestä varten" olemassa.
Hän on nyt antanut hengen samankaltaiselle olennolle, joka kentiesi
on suloisuudellaan tarjoava toisille miehille samaa mieltymystä, jota
muut naiset ehkä olivat ennen hänelle valmistaneet. — Ja miksei äiti
iloitse? Onhan hän synnyttänyt maailmaan olennon, jolle luonnon
sanotaan varta vasten antaneen nuo pyöreät muodot, tuon hoikan
vartalon, nuo hienot jäsenet osoittamaan ettei hän ole luotu
raskaampaa työtä varten, niinkuin mies, vaan "suloa antamaan ja
suloa saamaan."

Isän rakkaus ja äidin rakkaus on meille antava vastauksen


edellisiin kysymyksiin. Heidän rakkauteensa on laskettu ominaisuus,
jota muut hellemmät tunteet usein puuttuvat. Se on tuo huolellinen
katse tulevaisuuteen, joka levottomasti kysyy, mitä lapsesta tulee,
mimmoiseksi sen elämän onni muodostuu. Tämä tunne se on, joka
tytön syntyessä herättää vanhemmissa huolta ja murhetta. Isä, kun
hän ensikerran nostaa syntyneen tyttölapsen käsivarrelleen, ja äiti,
kun hän sen painaa rintaansa vasten, tekee mielessään tuon
hiljaisen kysymyksen: Mitä lapsestani tulee? Vanhemmat tietävät
mitä todellinen elämä tytölle tarjoo. He tietävät mitä nuo monet
määritykset naisesta maksavat. He tuntevat kuinka huonosti kaikki
nuo kauniit puheet sopivat olevaan elämään, siihen, joka ei
laisinkaan välitä runoilijain ja filosoofien ohjelmista, vaan menettelee
omapäisesti, armotta. Elämä on heille toista opettanut. Nyt kun he
isän ja äidin tunteella arvostelevat, astuu elämän kokemus
vaikuttavana heidän eteensä. He tietävät kuinka suuria vaaroja ja
vaikeuksia vastaan tyttö astuu. Jos he ovat varattomia eivätkä voi
lapselleen riittävää perintöä jättää, mikä on silloin hänen kohtalonsa
oleva? kysyvät he. Kuinka voipi hän tulla toimeen? Millä voi hän
itsellensä kunniallisen leivän ansaita? Mihin hän joutuu
yhteiskunnassa, jossa monet ja parhaimmat elatuksen tiet ovat
häneltä kielletyt, jossa nekin työt joihin häntä päästetään maksetaan
riittämättömällä palkalla? Jollei hänelle voi koota kylläksi perintöä,
taikka jollei hän tule kauniiksi, taikka jos hänessä on joku ruumiillinen
puute, niin hän kenties ei tule naimisiin. Hän saattaa myös sortua
maailman asettamain kiusausten alle ja langeta. Ja jos hän kerran
lankee, niin ei totisinkaan katumus ja parannus voi hänelle hänen
hyvää mainettansa takaisin antaa yhteiskunnassa, joka
armottomimmalla tavalla tuomitsee naisessa siveellistä virhettä.

Vanhemmat tietävät tämän. Monet miehet eivät välittäneet siitä,


ennenkuin itse saivat tyttären. Mutta silloin moni ajatteli: pitäneekö
minunkin tyttäreni joutua sellaisen kohtalon alaiseksi? Eikä rikkaus,
kauneus, hengen lahjatkaan aina voi vanhempia, levollisiksi saattaa.
Heidän tyttärensä kohtalo tulee kuitenkin toisen vallan alaiseksi
tavalla taikka toisella. Ei hän saa tietänsä valita, ei hän saa itse
itseänsä hallita. Ennemmin tai myöhemmin on hänen onnensa ja
omaisuutensa joutuva toisen mielivaltaan.
Pojan suhteen on kaikki toisin. Entisiin aikoihin pojan syntymä
vasta oli perillisen saaminen, silloin kun eivät lait tyttölapselle
antaneet ollenkaan perintöä. Mutta vieläkin, vaikka paljon on
muuttunut, otetaan usein pojan syntymä vastaan toisilla tunteilla kuin
tytön. Kun poika on tullut maailmaan ajattelee isä heti; mille elämän
alalle tuo nuorukaisena ja miehenä kääntynee, minkä ammatin, viran
tai työn hän valinnee. Hän toivoo pojassaan toistettavan jotakin siitä
mitä hän itsekin on, mikä häntä itseänsäkin on innostuttanut, mitä
hän itse on maailmassa toimittanut. Poikahan on täyttävä hänen
suurimmat toiveensa. Ammattilainen, liikkeen harjoittaja, kauppias
ajattelee että hänen poikansa ottaa vastaan ammatin, kun hän itse
vanhaksi tulee tai kuolee. Jos hän on tiedemies ja tutkija, toivoo hän
pojastansa saavan työnsä jatkajan, joka keksii ja ilmisaattaa sen
mikä häneltä on jäänyt jälelle. Ja pojallensa hän jättää nimensä,
johon sukunsa kunnia ja maine liittyy. Jollei hänellä ole poikia, kuolee
suku, sanotaan.

Isän ei tarvitse alakuloisena huolehtia poikansa kohtaloa.


Yhteiskunta on kaikki niin hyvin häntä varten järjestänyt ettei hänen
tarvitse muuta kuin valmistautua ja valita. Kaikki ammatit ja elämän
polut ovat hänelle avattuina. Työ, ihmisen suurin siunaus tarjoutuu
hänelle kaikilla aloilla, mitä säätyä hän lieneekin, kun naista
sitävastoin, varsinkin säätyluokissa, ainoastaan vastahakoisesti on
siihen laskettu. Samaten varattomimmissa kansan luokissa on
naisen pääsö tuottavaan työhön supistettu osaksi asetusten kautta,
mutta vielä varmemmasti tarpeellisen valmistuksen puutteessa. —
Ja siveelliseen elämään nähden on nuorukaisen helpompi takaisin
voittaa ihmisten kunnioitus, jos hän on langennut.

Tälle ala-arvoiselle asemalle, jolle olemme nähneet tytön asetetun


jo syntymähetkestä, on hän usein jäänyt elinajakseen. Eihän siis
ihme jos vanhempain mieli painuu alakuloiseksi tyttären syntyessä.

Nainen on ollut toisten omaisuutena, mutta itsellänsä ei hänellä


ole ollut mitään, jota hän olisi voinut omaksensa sanoa. Holhoukselle
on annettu suojeluksen ja turvan nimi, mutta todellisuudessa on se
ollut kovaa orjuuttamista. Jos maailmassa olisi ainoastaan hyviä ja
helläsydämisiä ihmisiä, taikka toisin sanoen, jos ihmisessä olisi
ainoastaan hyviä ja kiitettäviä ominaisuuksia, niin ehkä vaimon
holhonalaisuus ei olisi kääntynyt raskaaksi ikeeksi ja vapauden
sortamiseksi. Jos isät eivät milloinkaan olisi olleet tyrannillisia ja
puolueellisia; jos aviomiehet eivät milloinkaan olisi olleet itsekkäitä,
raakoja ja irstaita, ja toiselta puolen, jos tyttäret ja aviovaimot eivät
olisi olleet tavallisia puuttuvaisia ihmisiä niinkuin miehetkin, vaan
olisivat todellisesti olleet tuollaisia ihanne-naisia jommoisiksi heitä
yleiset käsitteet ovat julistaneet, niin silloin ehkä olisi nainen tullut
onnelliseksi alistetulla tilallansa. Silloin hän kentiesi ei olisi ruvennut
vapauttamista kaipaamaan.

Naisen historia maailmassa on kertomus ruumiin ja sielun


kärsimyksistä, siveellisestä sorrosta, jonka käsissä hänen on
täytynyt sydämensä luonnollisimmat tunteet tukehduttaa. Isän vallan
tai holhomiehen pakosta on hänen täytynyt antautua aviomiehelle,
minkä nämä toiset olivat valinneet. Pahan pitelemisen esineenä on
hän usein menettänyt ruumiillisen terveytensä, sielunsa terävyyden
ja siveellisen arvostelukykynsä. Alinomaisen tukehduttamisen
vaikutuksesta on tahdon voima hänessä lopulta heikontunut
välinpitämättömäksi tahdottomuudeksi. Naisen historia on kertomus
taloudellisesta ja yhteiskunnallisesta ahdistuksesta, jonka alaisena
hänen on täytynyt luopua omaisuutensa hallitsemisesta samaten
kuin toimeentulonsa hankkimisesta taipumuksen ja vapaan valinnan
mukaan. Hän on ollut lain voimalla suljettu kaikesta laillisesta
osanotosta yhteiskunnallisen elämän järjestämiseen ja julkiseen
elämään yleensä. Perheellisen elämän hallinnossakaan ei hänellä
ole ollut laillista sanan vuoroa, ei omain synnyttämäin ja
kasvattamain lastensakaan suhteen.
II.

Läpi koko historian vuosisadasta vuosisataan on kuitenkin


huomattava selvä nouseminen naisen tilassa kaikilla aloilla
perheessä, yhteiskunnassa, valtiossa, lakkaamatta etenevä työ
vapauttaa naista holhouksen tilasta ja nostaa häntä miehen
vertaiseksi. Tämä on toki niin silmiinpistävä tosiasia, ettei ole tarvis
siitä kuin muistuttaa. — Kuinka on ymmärrettävä että nainen on
voinut vapautua yhteiskunnassa, jossa sielutieteen oppineet ovat
olleet sitä mieltä, että naisen vapauttaminen sotii hänen luontoansa
vastaan ja kääntää ylösalasin kaiken luonnonmukaisen järjestyksen;
jossa uskon oppineilta on kuultu että kaikellainen vapautuksen liike
naisen suhteen on Jumalan asettamain rajojen rikkomista ja niin
muodoin kapina Jumalaa vastaan; jossa valtiomiehet ja julkisen
elämän johtajat ovat selvittäneet että naisen täytyy olla holhouksen
alainen, että itsenäisyys on hänelle haitaksi? Nainen on luotu
miellyttämään, olemme kuulleet opetettavan ja tämä "luonnon"
panema ominaisuus häviää hänessä, jos hän lasketaan itsenäiseksi,
päästetään virkoihin, ammatteihin, yhteiskunnan toimiin ja julkisen
elämän kilpailuun miehen kanssa. Kuinka on ymmärrettävä että
kaiken tämän sivulla vapautus, tosin hitaasti mutta järkähtämättä, on
voinut käydä päinsä? Kuinka on se ymmärrettävä ja ollut mahdollista
vaikka on huudettu ja vielä huudetaan että perhe häviää, että kaikki
sulo elämästä katoaa, että luonto mullistuu, jos nainen annetaan
alttiiksi elämän jokapäiväisyydelle ja lasketaan maailman myrskyyn?
Kuinka voidaan käsittää että vaikka etevimpien johtavien miestemme
joukossa löytyy niitä, jotka uhaten julki varoittavat naisia tahtomasta
väkeviksi tulla ja peloittavat heitä surkealla tappiolla "taistelussa,
jossa he puuttuvat kaikkia aseita;" kuinka on käsitettävä että vastoin
näiden mahtavien ääniä — hiljainen ja vakaa liike on käynyt ja yhä
käypi, joka tahtoo tehdä naisen väkevämmäksi ja antaa hänellekin
aseita käsiin elämään ja etsimään onneansa?

Syy lienee se että tuon personattoman naisen, tuon ihanne-


vaimon rinnalla, jolle sielutieteilijät ja runoilijat ovat muodon
rakentaneet, on ollut ihmiskunnassa toinen nainen, lihasta ja verestä
luotu ihmiseksi niinkuin mieskin. Tämä on se nainen, joka tuntee ja
kärsii, joka tahtoo elää ja työtä tekee, joka tarvitsee ruokaa ja
juomaa, vaatetta ja lämmintä, joka ikävöitsee vapautta ja onnea, joka
kaipaa kehitystä ruumiilleen ja sielulleen. Hän ei ole enkeli eikä hirviö
eikä kukkanen, hän on ihminen, ei muuta. Tämä on se olento, joka
on saanut toimeen naisen vapautustyön ja kumoamistaan kumonnut
vanhoja valtoja. Ei ole auttanut että ihmisille yhä uudestaan on
näytetty tuota hallitsevain käsitysten ja teoriain ihanne-naista ja
koreilla sanoilla selitetty sitä todelliseksi naiseksi. Väkistenkin on yhä
ilmaantunut toisellaisia naisia, jotka heittävät oppineiden systeemit
ylösalasin. Pelkän olemassa olonsa kautta ovat he niinmuodoin
tekeytyneet vanhojen käsitystapojen pahimmiksi vihollisiksi.

Systeemien laatijoissa tavataan nimittäin eräs suuri puute, jonka


he itse ovat unohtaneet. He ovat verrattain helposti, vaikka tosin
kauan kestäneen työn kautta, rakentaneet kehälle tuon mitä he
naiseksi kutsuvat. He ovat tehneet enemmänkin: he ovat
pukeuttaneet sen inhimilliseen haamuun, jopa vielä puhaltaneet sen
sieramiin elävää henkeä. Mutta he eivät ole osanneet valmistaa sitä
ilmaa, jossa tuo konstrueerattu nainen olisi viihtynyt, maailmaa,
jossa se olisi tullut toimeen. Ottaa tämä yhteinen maailma, jonka
Jumala loi ihmisten tarpeeksi, ja siihen asettaa sepitetty ihanne-
olento, se ei ole voinut onnistua. Aina on näkynyt ja näkyy yhäti
selvemmästi että tämä ilma, joka täällä meidän proosallisella
maanpallollamme on, ei ole sopinut tuolle naiselle. Todellinen elämä
ei ole taitanut korjata häntä suojaansa.

Mistä siis ristiriita on syntynyt? Siitä että täällä on olemassa naisia;


jotka eivät ole vastanneet tuota ihannetta ja joiden tarpeet ja
vaatimukset siis myöskään eivät ole löytäneet tyydytystä laeissa ja
säännöissä, laaditut systeemin mukaista olentoa varten. Siitä tämä
etenevä pyrintö vapauttamaan toista sukupuolta luonnottomasta
tilasta ja kohottaa sitä miehen rinnalle lain ja yleisen arvostuksen
edessä. Jos naiset todella luonnosta olisivat sitä, miksi heitä ajan
käsitykset ovat selittäneet; jos ne todella vastaisivat sitä naista, jonka
runoilijain lauluissa ja psykoloogien järjestelmissä tapaamme, niin
silloin kenties ei mitään liikettä sukupuolen aseman muuttamiseksi
nähtäisi eikä olisi olemassa ollut. Jos naiset todella olisivat olennoita
ilman omaa sielun elämää, ilman luotua kykyä ja taipumusta
inhimilliseen kehitykseen ja henkisten lahjain viljelemiseen, niin eivät
he olisi koskaan semmoista havainneet. Jos naiset todella olisivat
luodut miehen tahdon alle annettaviksi, niin ei heissä milloinkaan
olisi herännyt ikävöimistä tämän tahdon alta pois. Jos he olisivat
luodut vaan miehen tähden, eikä laisinkaan itsensä vuoksi, niin ei
heissä voisi ilmaantua mitään tarvetta elää itsensä tähden. Toiselta
puolen ei heillä myöskään silloin olisi mitään vaatimusta miesten
suhteen, että nämäkin puolestaan eläisivät enemmän naisen tähden.
Käsitykset naisesta, hänen tarkoituksestaan ja asemastansa ovat
välttämättömästi painaneet leimansa tytön kasvatukseen. Sen on
täytynyt asettua näiden käsityksien, tapain ja yhteiskunnallisten
lakien palvelukseen. Senvuoksi taidamme juuri kasvatuksessa
paraiten huomata mitä katsantotapoja yleisessä mielipiteessä on
liikkunut. Saamme nähdä miten meidän aikammekin, joka kuitenkin
jo luulee vapautuneensa monissa kohdissa käsityksissään naisesta,
vielä on kytketty vanhoihin perintöihin. Ja nämä perinnöt istuvat
meissä lujemmasti kuin tiedämmekään. Teoriassa — saattavat
monet olla hyvinkin muuttuneita ja vastarinnassa menneiden aikain
oppeja vastaan. Mutta katsele heidän menettelyänsä kasvatuksessa,
niin näet hämmästykseksesi kuinka se nais-ihanne, jota he siinä
kannattavat, on toisellainen kuin se, minkä heidän kielellänsä tapaat.
Ei missään vanhain käsitysten ja totuttujen tapain jäljet niin selvästi
näy kuin kasvatuksessa. Siinä löydämme sen suuren säiliön, joka
kauemmin kuin mikään muu kätkee ja elossa pitää katsantotapoja ja
määrääviä sääntöjä, joita vastaan jo on ryntäys alkanut. Jos
mielitään perusteellisia parannuksia naisen kohtalossa ja siten koko
yhteiskunnallisessa elämässä, täytyy siis varsinkin kasvatuksen
joutua keskustelun ja tarkastuksen alaiseksi.
III

Tyttöjen ruumiillisen kasvatuksen tuntomerkit pistävät silmään jo


heidän vaatetuksessaan. Kysymyksen, mikä on vaatteiden
luonnollinen tarkoitus, pitää jokainen ajatteleva ihminen aivan
tarpeettomana, koska sen oppimattominkin hyvin tietää. Kysynet
keneltä hyvänsä, niin saat vastaukseksi: vaatetuksen pitää olla
semmoisen että se säilyttää ruumiille tarpeellista lämpöä, siis
sovitetun ilmanalan ja vuodenaikain vaatimuksien mukaan. Toiseksi
kuulee että vaatteiden tulee olla mukavat ja käytännölliset, niin
etteivät estä ruumiin vapaata kasvamista ja kehittymistä ja että
tottelevat kaikkien jäsenten käskyjä. Eivät myöskään saa vaatteet
estää ahkeraa, voimia kysyvää työntekoa.

Vaatetuksen tulee niinmuodoin ensisijassa palvella tarpeellista ja


hyödyllistä. Tämän myöntää jokainen ihminen. Mutta vastaako
naisten vaatteus ylipäänsä tarpeen ja hyödyn vaatimuksia, jos sitä
vertaamme miesten vaatetukseen?

Arvelematta uskallamme kasvatusta vastaan tässä suhteessa


tehdä sen muistutuksen, että se siinä paljon laiminlyöpi. Mimmoiset
ovat tyttöjen puvut? Mitä periaatteita niitä tehdessä noudatetaan?
Niin kauan kun lapset makaavat kapalossaan, kääritään ne vielä
Welcome to our website – the ideal destination for book lovers and
knowledge seekers. With a mission to inspire endlessly, we offer a
vast collection of books, ranging from classic literary works to
specialized publications, self-development books, and children's
literature. Each book is a new journey of discovery, expanding
knowledge and enriching the soul of the reade

Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.

Let us accompany you on the journey of exploring knowledge and


personal growth!

textbookfull.com

You might also like