Learning Advanced Python by Studying Open Source Projects 1st Edition Li - The Latest Updated Ebook Version Is Ready For Download
Learning Advanced Python by Studying Open Source Projects 1st Edition Li - The Latest Updated Ebook Version Is Ready For Download
com
https://textbookfull.com/product/learning-advanced-python-
by-studying-open-source-projects-1st-edition-li/
OR CLICK HERE
DOWLOAD EBOOK
https://textbookfull.com/product/firm-sponsored-developers-in-open-
source-software-projects-a-social-capital-perspective-dirk-homscheid/
textbookfull.com
https://textbookfull.com/product/deep-learning-for-remote-sensing-
images-with-open-source-software-1st-edition-remi-cresson/
textbookfull.com
https://textbookfull.com/product/how-open-source-ate-software-
understand-the-open-source-movement-and-so-much-more-gordon-haff/
textbookfull.com
https://textbookfull.com/product/how-open-source-ate-software-
understand-the-open-source-movement-and-so-much-more-gordon-haff-2/
textbookfull.com
Penetration Tester's Open Source Toolkit Faircloth
https://textbookfull.com/product/penetration-testers-open-source-
toolkit-faircloth/
textbookfull.com
https://textbookfull.com/product/reinforcement-learning-with-open-ai-
tensorflow-and-keras-using-python-1st-edition-abhishek-nandy/
textbookfull.com
https://textbookfull.com/product/postgresql-up-and-running-a-
practical-guide-to-the-advanced-open-source-database-regina-o-obe/
textbookfull.com
https://textbookfull.com/product/studying-babies-and-toddlers-
relationships-in-cultural-contexts-1st-edition-liang-li/
textbookfull.com
Learning Advanced Python by
Studying Open Source Projects
This book is one of its own kind. It is not an encyclopedia or a hands-on tutorial that traps
readers in the tutorial hell. It is a distillation of just one common Python user’s learn-
ing experience. The experience is packaged with exceptional teaching techniques, careful
dependence unraveling and, most importantly, passion.
Learning Advanced Python by Studying Open Source Projects helps readers overcome the
difficulty in their day-to-day tasks and seek insights from solutions in famous open source
projects. Different from a technical manual, this book mixes the technical knowledge, real-
world applications and more theoretical content, providing readers with a practical and
engaging approach to learning Python.
Throughout this book, readers will learn how to write Python code that is efficient,
readable and maintainable, covering key topics such as data structures, algorithms, object-
oriented programming and more. The author’s passion for Python shines through in
this book, making it an enjoyable and inspiring read for both beginners and experienced
programmers.
Rongpeng Li (Ron) is a YouTube educator and animator. He has a consistent passion for
education. He has published two books on statistics and scientific simulation.
Chapman & Hall/CRC
The Python Series
Rongpeng Li
Cover image: Rongpeng Li
© 2024 Rongpeng Li
Reasonable efforts have been made to publish reliable data and information, but the author and publisher cannot
assume responsibility for the validity of all materials or the consequences of their use. The authors and publishers
have attempted to trace the copyright holders of all material reproduced in this publication and apologize to
copyright holders if permission to publish in this form has not been obtained. If any copyright material has not been
acknowledged please write and let us know so we may rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be reprinted, reproduced, transmitted, or
utilized in any form by any electronic, mechanical, or other means, now known or hereafter invented, including
photocopying, microfilming, and recording, or in any information storage or retrieval system, without written
permission from the publishers.
For permission to photocopy or use material electronically from this work, access www.copyright.com or contact the
Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA 01923, 978-750-8400. For works that are
not available on CCC please contact [email protected]
Trademark notice: Product or corporate names may be trademarks or registered trademarks and are used only for
identification and explanation without intent to infringe.
DOI: 10.1201/9781003316909
Typeset in Minion
by codeMantra
To Yan, the brightest light in my life.
To my family, whose decisions made my life today possible.
To Holly and Prosper, the endless source of joy in the room.
Contents
Preface, x
Acknowledgments, xi
IntroductIon 1
PURPOSE AND SCOPE OF THIS BOOK 1
OVERVIEW OF THE APPROACH TAKEN 3
NOTE 4
C
A GENTLE INTRODUCTION TO PYTHON’S DATA MODEL 5
CUSTOMIZED COMPARISON 7
A MANAGED ITERATION BEHAVIOR 12
ATTRIBUTES, FUNCTION OR DICTIONARY? 16
SUMMARY 20
NOTES 20
C
INTRODUCTION 21
DESCRIPTORS AND ATTRIBUTE LOOKUP ORDER 21
Descriptor Demystified 21
Lazy Evaluation in Matplotlib 25
METACLASS AND ITS USAGE IN ELASTICSEARCH DSL 30
Understanding Metaclass Using Meta-Recipe 30
Use Metaclass to Model Documents in Elasticsearch DSL 35
SUMMARY 40
APPENDIX 40
NOTES 41
viii ◾ Contents
C
CONCURRENCY FROM A TOP-DOWN PERSPECTIVE 42
Operating System and Concurrency 45
Introducing Global Interpreter Lock (GIL) 47
MULTIPROCESSING FOR CPU BOUND TASKS 48
Parallel Pandas Apply in pandarallel 52
MULTITHREADING FOR I/O BOUND TASKS 58
SUMMARY 63
NOTES 63
C
A SHIFT OF PARADIGM 64
EVENT-DRIVEN SIMULATION 65
ASYNC AS A PATTERN 71
SUMMARY 75
APPENDIX 75
NOTES 76
C
INTRODUCTION 77
THE DECORATOR FOR RETRYING A FUNCTION 77
CONTEXT MANAGER IN A NUTSHELL 81
DIVE INTO THE AIOSQLITE EXAMPLE 84
Connection as an Executor and a Scheduler 85
Connection and Cursor as Async Context Managers 88
SUMMARY 93
NOTES 93
C
UNDERSTAND YOUR BUSINESS 94
A Quick Overview of the Business 95
MODEL THE BUSINESS ENTITIES WITH OOP 97
Design the Core Entities 97
Establish the Relationship between Classes 100
The Benefits of Universal interface 104
Sometimes No OOP is the Best Design 106
Contents ◾ ix
SUMMARY 108
NOTES 108
C
INTRODUCTION 109
FIXTURE AND PARAMETERIZATION 109
Parameterization 111
Resources and Fixture 113
MONKEY PATCH 115
Modify the Built-in Print 115
More Powerful Monkey Patching 117
PROPERTY-BASED TEST 118
SUMMARY 122
NOTES 122
INDEX, 123
Preface
x
Acknowledgments
I would like to thank the creators, maintainers and all contributors to the open source
projects I used as examples in this book.
It is unimaginable if those amazing resources are not freely accessible to me and to
everyone around the globe. The open source movement has astronomically impacted not
only the software engineering but also the advance of technology universally.
Thank you, from the bottom of my heart.
xi
Introduction
1. The Python data model and Python classes are fundamentally important as they are
the basics to learn everything else. They are basic but they are not obviously easy to
master. Many Python users, or most Python users didn’t graduate with a computer
science master’s degree. They started using Python by copying and pasting so long as
the code worked. At some point in their learning or career path, the lack of founda-
tion will bite. I want to solidify your foundations.
2. The other four topics are deemed as a gap between so-so engineers and more pro-
fessional engineers. I picked these four because they are the ones that I find easy to
break the cyclic dependencies of the advanced topics. For example, it is impossible
to talk about concepts like fixture without talking about decorators first. It is hard
DOI: 10.1201/9781003316909-1 1
2 ◾ Learning Advanced Python by Studying Open Source Projects
to imagine context manager without talking about the idea of coroutine first: well
technically you can. I try to chop the open source projects that utilize multiple such
concepts into digestible pieces. The example of aiosqlite is probably the most typical
example. Please do read Chapter 5 and let me know whether I did an acceptable job.
The following topics are not considered important enough for most readers.
Well, those topics are very important if you are working on fine-tuning Python perfor-
mances: not because of bad code but because of Python itself. For example, some high-
performance computing work requires such skills, but most Python users don’t ever write
a line of C in their whole career. Similarly, most people don’t publish Python packages as
well.
There are great resources on these two topics. I would recommend the official doc for the
former and the book Publishing Python Packages1 by Dane Hillard for the latter. Honestly
speaking, the fact that the second topic is enough for an independent book also makes it
impractical to be included in this book. The first topic is also big enough for an indepen-
dent book.
The following topics are indeed important, but they are not really Python specific.
The first draft of this book included documentation as an independent chapter. Popular
Python open source libraries often have fabulous documentation. However, the more I
wrote about it, the more I feel that they are not advanced Python, they are skills related to
advanced Programming. Everyone who wrote code needs to know them. It is not suitable to
include them in a book about Python.
Similar ideas apply to project management. Since most open source projects are devel-
oped for the public, you can read the comments, the interactions and sometimes spicy
exchanges between developers and users. I personally find this topic super interesting while
preparing the materials. However, they are, again, not Python specific. I have to drop it.
Let’s discuss the purpose of this book. This book’s purpose is to help moderately good
Python users to become professional Python users.
What is a Python user? What is moderately good? What makes a Python user
professional?
A Python user is a superset of a Python developer. When I was a PhD student, I used
Python a lot. I was not a Python developer. Namely, I wrote Python as a tool to do some-
thing else, rather than for the sake of writing Python. On the contrary, a Python devel-
oper’s job is to write high-quality Python applications.
Introduction ◾ 3
However, I find Python users’ work often requires very high technical skills. For exam-
ple, a Jupyter Notebook is far from enough for a researcher. The researchers sometimes
hack their way through the development and write terrible code. This book aims at the
aspirational and ambitious Python users so they can use Python in the most efficient way.
Of course, developers are welcome.
So, what is exactly being moderately good that qualifies you to read this book?
If you
2. are familiar with basic object-oriented programming concepts like class, instance
and inheritance.
3. understand basic operating system concepts like process, thread and memory.
4. are familiar with tools/platforms like Git, GitHub and VS Code.
I hope this gives a good idea of what topics this book covers, and what kind of prerequisites
I am expecting the readers to have.
NOTE
Chapter 1
cars = list()
cars.append(‘bmw’)
cars.extend([‘audi’, ‘toyota’])
assert cars[0] == ‘bmw’
last_car = cars.pop()
assert last_car == ‘toyota’
assert len(cars) == 2
For a dictionary, you can perform a different set of operations as shown in code snippet
1.2. You can get a member of the dictionary. Note that both list and dict support the
len() method.
fruit_prices = dict()
fruit_prices[‘apple’] = 0.5
fruit_prices[‘orange’] = 0.25
assert len(fruit_prices) == 2
assert fruit_prices.get(‘pear’) == None
What makes lists and dictionaries behave in different ways? What if we want to modify
their behaviors and create a hybrid data structure to suit our needs? This will be the main
topic of this chapter: the data model2 for built-in Python data types.
DOI: 10.1201/9781003316909-2 5
6 ◾ Learning Advanced Python by Studying Open Source Projects
In Python, everything is an instance of the object class. You can think of object as the
ancestor of all things in Python, including the built-in types like int, str, list,
dict and all user-defined classes. Let’s try some examples with code snippet 1.3.
isinstance(“California”,object)
# True
isinstance(int,object)
# True
isinstance(list(),object)
# True
class Car:
pass
isinstance(Car(),object)
# True
isinstance(Car, object)
# True
A Car instance is an object instance. Also, the Car class itself is an object instance. The
question is that if all stuffs are instances of the object class, then what makes a list dif-
ferent from a dict?
Let’s check the supported methods of a list and a dict.
The dir() function returns a list of attributes and methods supported by an object. We
sort the list in alphabetical order to make it easier to compare the results. Code snippet 1.4
shows the result for a list instance.
sorted(dir(list()))
CODE 1.4 The attributes and methods supported by a Python list object.
Let’s check what methods are supported by a list instance but not supported by a dict
instance and vice versa. To do that, we need to build two sets and take a difference as
shown in code snippet 1.5.
set(dir(list())) - set(dir(dict()))
The Data Model of Python ◾ 7
set(dir(dict())) - set(dir(list()))
# {‘__ior__’, ‘__or__’, ‘__ror__’, ‘fromkeys’, ‘get’,
‘items’, ‘keys’, ‘popitem’, ‘setdefault’, ‘update’, ‘values’}
CODE 1.5 Compare the differences of supported methods for a list and a set.
You may notice that both list and set support the __gt__ methods. It means that you
can compare two lists or two dictionaries. It seems that they follow the same protocol here
that defines what they can do: they can be compared against their own kinds.
However, a list supports the __add__ method while a dict instance does not. This
hints that they follow different protocols: lists can be added directly, while dictionaries
cannot.
In the following sections, you will learn how to create your own data structures that fol-
low different protocols and control exactly how they behave.
CUSTOMIZED COMPARISON
Let’s begin with a scenario. You are hired by a major car dealer to create an application that
will help them to keep track of their customers’ information. One key feature of the applica-
tion is to compare the in-house creditability of customers. For example, if two customers
bid for the same car, the application needs to tell your boss which customer is more credible.
We need a Customer class. Code snippet 1.6 creates it.
class Customer:
def __init__(self, first_name: str, last_name: str, credit_
score: int, credit_limit: int, in_debt: bool, monthly_income: int):
self.first_name = first_name
self.last_name = last_name
self.credit_score = credit_score
self.credit_limit = credit_limit
self.in_debt = in_debt
self.monthly_income = monthly_income
def __repr__(self):
debt_status = “not in debt” if self.in_debt == False else
“in debt”
return f”Customer {self.first_name} {self.last_name},
{debt_status},” \
f” with a credit score of {self.credit_score},”\
f” credit limit of {self.credit_limit}” \
f” and a monthly income of {self.monthly_income}.”
8 ◾ Learning Advanced Python by Studying Open Source Projects
The __repr__ method is used to print the object. If you don’t define it, Python will print
the object’s memory address like < __main__.Customer object at0x1083c4ee0>.
We can create a few customers and play around with them as shown in code snippet 1.7.
print(melissa_miller)
# Customer Melissa Miller, not in debt, with a credit score of
700, credit limit of 250000 and a monthly income of 9000.
To compare the financial credibility of the customers, the car dealer has created a set of
rules based on their historical experiences. The rules are organized in order such that the
rules are checked in order. These rules are:
However, if the one who gets paid higher is in debt while another who gets paid lower is
not, the difference of monthly income must be larger than 4000. Otherwise, the one who
is not in debt is more credible.
Ideally, we want to compare the john _ smith and richard _ dawkins objects in
code snippet 1.8. However, we can’t do it because Customers don’t support such operations.
We have two solutions. One is to write a helper function that compares two customers.
Every time we need to compare customers, we can pass the function to something like the
key parameter of the sort() method. Another solution is to enable native support for
Customer comparison.
Which solution is better? The logic of comparison needs to go somewhere in the code. The
question is where. The canonical way is in the Customer class. There are two reasons. First,
by enabling syntax like john _ smith < richard _ dawkins, we aligned our syntax with
the Pythonic way of doing comparison. It is much easier for your coworkers to integrate the
code into their projects. Second, the built-in comparison logic is more robust and error-free
than a standalone function. When you ship your code to someone else, you have a better
control over the code unless the code users deliberately overwrite the comparison logic in
the Customer class, which is much less likely than them writing another standalone com-
parison function.
Let’s pause our concerns for a while to learn from the pros and see how this is done in
the SymPy library. SymPy is an open source Python library for symbolic computation.
It can be used to perform algebraic computations and symbolic differentiation, etc. Code
snippet 1.9 shows a differentiation example.
Back to SymPy, instead of studying its implementation, let’s investigate a bug to pierce
into its core. The developers found a bug that alternating two objects’ order in comparison
10 ◾ Learning Advanced Python by Studying Open Source Projects
gives different results in early 2021. As stated in issue 20796,3 the two comparisons in code
snippet 1.10 should both return False. Based on the types of them, one represents numeri-
cal value and another logical. Objects with different data types should always be different.
S(0.0) == S.false
# True
S.false == S(0.0)
# False
S here stands for Singleton. S.false means that there is only one such thing as a math-
ematical false in the whole mathematical universe. There can only be one false and there
can only be one 0.
Now, let’s get to the implementation of the __eq__ method of the S class. Pull request
208014 fixed the issue but I already copied the pre-PR implementation to snippet 1.11.
In snippet 1.11, the self represents a float number.
CODE 1.11 Equality comparison of a float number with a bool, before pull request
20801.
The __eq__ method takes two arguments, the first one is the object itself and the second
one is the object to be compared with. When you write comparison like A == B, The __eq__
method of the object on the left side of the comparison is called: in the example, A.
In the line denoted with comment 1, we first check the Boolean representation of the
object itself. If it is False, it means the object is a 0, probably with arbitrary precision. In that
case, we check the other object. If it is also equivalent to 0, then they are equal.
In the line denoted with comment 2, we check whether the other object is a Boolean
object or not. If it is, then we always return False. A number is never the same with a
Boolean, mathematically.
A quick note if you are not catching up. How did we know that the float number’s __eq__
method is wrong? Since S.false == S(0.0) gives the correct answer, which is False, we
then know the S.false. __eq __(S(0.0)) returns the right answer. This is how we know
that the issue is in the implementation of __eq__ for the float number Singleton object.
The devil lies in the order of the two lines I commented. When a float number is equiva-
lent to 0, not self in the first if statement is evaluated to be True, therefore if the other object
is a False object, a True value is returned. These two if statements need to be switched. If
the types are different, then they are never equivalent. This is exactly what the pull request
20801 fixes.
Notice that the practice is very similar to our proposed implementation of __lt__
or __gt__ methods for the Customer class. The order of criteria matters.
Go back on our original question, code snippet 1.12 is implements the __lt__ method
for the Customer class. It is probably the most elegantly written code. In production,
make sure you write comprehensive tests, too.
if self.credit_limit != other.credit_limit:
return self.credit_limit < other.credit_limit
income_diff = abs(self.income - other.income)
CODE 1.12 Implementation of the less than logic for Customer class.
This dunder method allows users to sort customers natively as shown in code snippet 1.13.
So far everything looks great. Let’s do something even fancier in the next section.
Please be aware of the difference between the abstract base class and the abstract base class
for containers. The former is a superset of the latter. The abstract base classes in Python also
include those from the numbers module and the io module, etc.
Each abstract class is required to have a specific set of abstract methods. A realization of
one of those abstract classes must implement the corresponding abstract methods.
The abstract classes also have inheritance relationships. For example, the Reversible
abstract class is a subclass of the Iterable abstract class. Therefore, besides the iter()
method, the Reversible class also defines the reversed() method. The Sequence
abstract class is a subclass of both the Reversible and the Collection abstract classes.
Therefore, the Sequence class also needs to define the len() method and the contains()
method to support the in operator, as you often see in the for loop statements.
By the courtesy of Sangmoon Oh, the inheritance relationship between abstract base
classes for containers can be visualized as a hierarchy as shown in Figure 1.1. You can find
his blog on Medium.6
The reason we are interested in the hierarchy of abstract classes is that sometimes we
need to create our own hybrid or Frankenstein classes. Here is the continuation of the car
dealership example.
Your employer, the car dealer, is pleased with your work. Now, the managements want
you to create an application to store and retrieve customer data according to their VIP
status. After some analysis, you identify that you need a data structure to support the
FIGURE 1.1 The class hierarchy of abstract base classes for containers.
14 ◾ Learning Advanced Python by Studying Open Source Projects
If a new car is available, the car dealer can iterate through VIP customers first to give them
privileges to choose first. The CustomerStruct should largely behave like a set. Among
all the common data structures, only dictionaries and sets support membership tests in
O (1) time. Since we are not looking up for data, a set is enough. The issue with a default
set is that its iteration is not in any particular order after some member manipulation.
However, you should know that since Python 3.7, the default dict data structure is inser-
tion-ordered if you iterate over a dictionary. I will quote from the Python documentation:
A set is an unordered collection with no duplicate elements. Basic uses include membership
testing and eliminating duplicate entries. Set objects also support mathematical operations
like union, intersection, difference and symmetric difference.
We don’t want to implement an ordered set completely because we don’t prefer one VIP
customer over another or one non-VIP customer over another. If you are interested in a
fully functional ordered set, you can check the ordered sorted container Python library.7
The problem then boils down to the implementation of the __iter__ method which
dictates the behavior of iteration. Interestingly, the SymPy developers faced a similar con-
cern like ours. They want to implement the rational number set and allow callers to iterate
over the set in a specific way. Why? Recall that a rational number is any number that can
1 3
be represented as a fraction. For example, , are both rational numbers while π , the ratio
2 5
of a circle’s circumference to its diameter, is not. The rational number set is also infinitely
large. Iterating over such an infinite set exhaustively and reasonably is their concern.
The source code is in the sympy.sets.fancysets.Rationals class as shown in
code snippet 1.14.
def __iter__(self):
from sympy.core.numbers import igcd, Rational # comment 1
yield S.Zero
yield S.One
yield S.NegativeOne
d = 2
while True:
for n in range(d):
The Data Model of Python ◾ 15
if igcd(n, d) == 1: # comment 2
yield Rational(n, d)
yield Rational(d, n)
yield Rational(-n, d)
yield Rational(-d, n)
d += 1
In the line denoted with the first comment, they used a function called igcd() to deter-
mine whether two numbers are relatively prime. The Rational class is used to build a
rational number. Two rational numbers are essentially the same if one can be reduced to
1 23
another. For example, is essentially equivalent to . Several Singleton objects are being
2 46
yielded in the following lines first. There is only one 0, one positive 1 and one negative 1, etc.
Then the iteration is dictated by increasing a value d, while finding all the possible com-
binations of the pair (n, d ), where n is smaller than d and relatively prime to it.
Notice that the __iter __ method can yield rational numbers indefinitely, which gives you
an idea why protocols are important to write efficient code. The code doesn’t need to be hon-
est if it does what it is supposed to do faithfully, according to the protocol. For example, you
can pass a handler to users to iterate over a large amount of data records from a database. You
don’t want to query so much data beforehand because it is going to be a disaster memory-
wise and time-wise. What you can do is to return a small amount of data for the user to use,
maintain a cursor and defer additional queries until needed. The amortized cost will be low.
Let’s give it a try to check the pattern. I am examining the first 20 results in code snippet
1.15.
# 3/2
# -2/3
# -3/2
# 1/4
# 4
# -1/4
# -4
# 3/4
CODE 1.15 Use the Rationals class to generate first 20 rational numbers.
Time to go back to our CustomerStruct case. We also need to slightly modify the
Customer class to initiate them with an is _ vip argument (snippet 1.16).
class Customer:
def __init__(self, ..., is_vip: bool)
self.is_vip = is_vip
class CustomerStruct:
def __init__(self, customer_set: set):
self.customer_set = customer_set
def __iter__(self):
for customer in self.customer_set:
if customer.is_vip:
yield customer
for customer in self.customer_set:
if not customer.is_vip:
yield customer
The drawback of this approach is that we will have to loop the customer _ set twice.
Well, there is no free lunch. Better solutions so exist but you get the point.
class Customer:
def __init__(self, is_vip: bool):
self.is_vip = is_vip
print(Customer(True).__dict__)
Discovering Diverse Content Through
Random Scribd Documents
közben, társai közül eltünik, de gondoskodva van róla, hogy magát
semmi bajba ne keverje: az erdő, a hol vadászni szokott, hét
mérföldnyi kerületben, katonai őrlánczczal van körülvéve, melyen
sem ki, sem be nem lehet jutni Annius tudta nélkül.
És mégis megtörténik, hogy e körülfogott erdőségben néha úgy
el tud tünni a fejedelemnő, hogy kisérői hetekig sem találják újra föl.
Pedig minden rejtekhelye a bércznek és völgynek fel van már
kutatva, az erdőlakók megvesztegetve, hogy lessenek utána; sőt azt
a fáradságos munkát is megtették már, hogy az elvágtató paripa
nyomán utána mentek; a vezérlő patkónyomok azonban majd egy
sziklaúton, majd egy vízmederben elvesztek s azontul nem lehetett
többé folytatásukra akadni, mintha ég vagy föld rejtette volna el a
lovagnőt paripástól együtt.
Mikor aztán társaihoz visszatért, vagy azok ráakadtak, annyi
bölény szakállát és szarvait látták nyergére fölaggatva, a mennyit ők
valamennyien sem bírtak elejteni. Ez volt tanubizonysága annak,
hogy egész rejtélyes távolléte alatt nem tett mást, mint a vadakat
üldözte s egyedül küzdött velük a berkekben.
Kilencz év alatt Annius megvetett szerelme a boszúálló gyűlöletté
változott át; a nőnek, ki hajolni nem akart, törnie kellett.
Egy napon egy sarmata decurio beszélé előtte a tábortüz mellett
mesélgetve, hogyan szokták az ő nemzetbeliek a bölényt
megkeríteni; felöltöztetik a jó futó paripát hím bölény bőrébe, maga
a lovag szinte a bőr alatt van, úgy hogy a feje, a mint a ló hátán
hasmánt fekszik, a bölény púpját képezi, s ez által tökéletesen azt az
alakot adja a lónak, a mi az idomtalan, vastagderekú bölényé. A
púpon elől nyilás van, melyen a lovag kinéz, s kezeivel a bölény
sörénye alatt igazgatja a ló fékét. Igy a gyanutlanul legelésző
bölénycsorda közepébe juthat s akkor egyszerre hátravetve a rejtő
sörényes bivalyfőt, nyilakkal lelövöldözi áldozatait.
– Hallod-e Balbus, így szólt hozzá a vezér, én te rád egy nagy
feladatot bízok. Eredj abba az erdőbe, az általad leirt álcza alatt, a
hol az angresi asszony vadászni szokott, menj hozzá közel s légy
rajta, hogy figyelmét magadra vond. Én parancsot fogok adni, hogy
mikor ő üldözésedre indul, társai elmaradjanak s engedjék őt
egyedül utánad menni. Te csalogasd őt magad után, s ha elmarad
tőled, fordulj te ismét rá vissza s kövesd nyomról-nyomra, míg
megtudod a titkot, hová szokott ő eltünni e vadászatok alkalmával.
Balbus ügyesen hajtá végre a rábizott alakjátékot. Egy vadászat
alkalmával esti holdvilágnál megjelent a bölény álarcza Eponine
vadásztanyája közelében s a fejedelemnő rögtön paripájára ült s
üldözőbe vette azt.
Az álfenevad hurczolta őt maga után órahosszat erdőkön,
völgyeken keresztül; egyszerre Balbus vevé észre, hogy háta mögött
a csörtetés elmarad s a mint visszafordult, látta, hogy a fejedelemnő
elcsapott nyomáról s lovát kantáron vezetve, gyalog halad egy hegyi
folyam sziklás partján fölfelé, egészen eltérve az iránytól, melyet a
bölény vett.
Most ő is visszafordult s utána kezde menni. Nem szokatlan
tünemény ez, hogy az üldözött bölény vadásza ellen visszacsap s
szerepet cserél vele.
A fejedelemnő nem sokára észrevette a fenevad közeledését,
azonban a helyett, hogy sietett volna lovára kapni, közelre várta azt
s akkor hirtelen előkapva tegzét, egy nyilat küldött eléje.
Balbus érzé, hogy épen arcza mellett vágott be a nyíl. Ha
Eponina a bölény púpjára czéloz, épen Balbus fejébe talál.
A megsebesült mén egyszerre ijedten fordult vissza s vágtatni
kezde tüskön, bokron keresztül, a mit észrevéve Balbus, hirtelen
kicsúszott a bölénybőr alól a ló farán keresztül s azt futni hagyva,
maga a földön kúszva követte a fejedelemnő jártát, mint valami
lappangó hiúz.
A völgy fenekére érve, a hegyi folyam forrása tünt szem elé; fenn
a meredek hegyoldalban eredt az, s négy öles magasról zuhatagot
képezve, omlott egyenesen alá, még csak egy kő sem volt útjában,
melyen megakadjon.
A zuhatagon keresztül valami figyelmesen vizsgáló szem
fölfedezhetett egy embernagyságnyi hasadást a sziklában, s
gyanítható volt, hogy azon belül tán egy mélyebb üreg létezik,
azonban a lecsapó vízoszlop lehetetlenné tette az ahozférhetést,
minthogy épen fedezte a nyílást s a bejutni kivánót kétségtelenül
lezúzta volna.
A mint Eponine e forráshoz ért, levette kürtjét oldaláról s a
barlang nyílásához közel háromszor belefujt; a hangnak igen erősnek
kellett lenni, hogy a zuhatag robaján keresztül meg lehessen hallani.
Erre a kürtszóra úgy tetszék Balbusnak, mintha a vízomlás
zuhogása kevesebb viszhangot költene a völgyben, mintha a
lerohanó vízoszlop vékonyodnék; és valóban lassankint mindig
csendesebb lett a zaj, pihentebb a vízomlás; utóbb csak egy vékony
csergedező sugár maradt fenn belőle, hanem a helyett a
sziklahasadék aljából kezdett el egy új forrás előzuhogni,
kétségtelenül ugyanaz, mely amott a magasban megszünt omolni.
A sarmata kém megborzadt e látványon. A fejedelemnő
bizonyosan egy bűbájos amphisbaenra, ki a földalatti szellemekkel
parancsol, hogy kénye-kedve szerint változtassák a vizek folyását; s
a földön fekve maradt, nem mert tovább közelíteni hozzá, a
bűvésznő szava bizonyosan veszett farkassá változtatná a vakmerőt.
Annyit látott csupán, hogy a mint a zuhatag megállt, Eponine
lovastul együtt leszállt a vízmederbe s lovát maga után vonva, a szűk
barlangnyíláson át a szikla mélyében eltünt.
És azután nem sokára ismét elkezdett csorogni a vízsugár onnan
a magasból; mindig erősebb, mindig hangosabb lett; néhány pillanat
múlva épen oly erőszakos rohammal fedte a barlang nyílását, mint
elébb.
Balbus, ha elég merész lett volna is hozzá, nem követhette a
fejedelemnőt tovább.
A helyett sebten visszafordult s gyalog kivergődve az erdőből,
visszakerült Anniushoz, tudósítva a római vezért azon csodáról, mely
szemei előtt végbement, s esküdött rá, hogy a mely nő szavára a víz
megáll s a szikla meghasad, a víz újra megered s a szikla újra
bezárul, annak nem üdvös halandó fejjel üldözésére elindulni.
Annius pedig többet tudott már az elégnél, s mosolygott kéme
ijedelmén. Az azon korbeli római sok istennek áldozott ugyan, de
egytől sem félt.
***
Annius azonnal kiválasztott húsz férfit a legmerészebb triariusok
közül s a sarmata kém által elvezetteté magát az aranyhegyi
rengetegbe, a zuhatagig.
Négy izmos férfi, egy öblös paizst domború felével fölfelé tartva,
felfogá az omló víz rohamát: a nyílás járható lett.
Annius paizsát előre tartva, jobbjában kivont kardját szorítva,
merészen lépett be az ismeretlen sötétségbe, melyet az aggódók
képzelete varázshatalmú lények csodáival népesíte meg. A
csataedzett triariusok szívdobogva követték vezérüket.
Alig haladtak húsz lépésnyire a szűk torkolaton keresztül, midőn
világosság kezdett szemeik előtt derengni. Ott megállítá harczosait
Annius és csendet inte.
Egy szál szurokfenyőhasáb lobogott fáklyaképen a barlang
mélyén, fényes világot vetve egy boltozatos oldalüregre, mely mint
egy fejedelmi trón padmalya, zászlókkal és fegyverekkel volt
feldiszítve. A boltozat alatt medvebőrökből vetett nyugágy volt
elkészítve; azon a nyugágyon hevert egy férfi, hosszú szőkefürtű
fejét egy tündérszépségű hölgy ölében nyugtatva, lábaiknál két
fiucska játszott, egyik hét éves lehetett, a másik még kisebb; apjuk
kézíját próbálgatták; az elpattant húr meg-megüté a kisebbik gyönge
ujját, de azért nem sírt, csak nevetett rajta; a nagyobbik már jól
megtudta azt vonni.
Annius megismeré Eponinét.
A nő gyöngéden simogatá meg a férfi omló hajfürteit, s oly
szerelmes tekintettel, mint egy nőoroszlán, függött annak deli
vonásain.
– Látod, Sabinus, hogy a föld alatt is van boldogság, suttogá, a
játszó gyermekekre pillantva.
– Még mindig a zsarnoké a föld felszíne?
– Övé az égtől a pokolig minden: csak a sírban lakók mentek
hatalmától.
– S én a sírban lakom.
– Már kilencz év óta.
– Már kilencz év óta nem láttam, hogy mi az a nap? Csak éjjel
járhatok ki a vadakra lesni, a mik a patakhoz jönnek. Úgy óhajtottam
sokszor meglesni a hajnalt, de te megtiltottad azt nekem, s én szót
fogadok. Csak az omló zuhatagon keresztül látom néha a megtört
napfényt szivárványalakban; s mikor te jösz, akkor a te szemeidben.
– Még ez a két gyermek sem látta a napvilágot soha.
– Születésük óta csak csillagfényt és fáklyavilágot ismernek.
– Talán el sem fogják tűrni szemeik a szabad napsugárt.
– Oh a szabad napsugár! sóhajta fel az eltemetett hős, ki ne
éledne fel attól? Melyik halottnak ártana az meg, ha föltámadhatna
újra?
– Csitt, mintha csörrenést hallanék.
– A zsilip fel van húzva; a földalatti víz zuhog a csatornában, a
vízesésen át nem jöhet ide senki.
– Tanítod-e gyűlölni gyermekeidet?
– Oh ez mindennapi imájuk.
– Tudják-e, hogy ők egy király fiai? Mondtad-e nekik, hogy fejük
felett egy haza van és egy korona, mely az övék?
– Hallgasd csak, mit tudnak énekelni?
A két gyermek rögtön felugrott fektéből s kis kardjaikat kapva,
elkezdék a fegyvertánczot, melyre apjuk tanította őket, s éneklék
mellé a harczi dalt gyenge, csengő gyermekhangon:
«Fel, fel a sírból!
Törd le a lánczot!
Kardot a kézbe!
Elleneidnek
Döfd kebelébe!
Vágd a bitorlót!
Hulljon a vére!
Gallia földe
Zöld lesz utána!»
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.
textbookfull.com