CPython Walkthrough 2 ~ Al
.knuth baba
Yoksun kaldığımız merak içgüdüsünü geri kazanıp, bizi ele geçiren tembellik mührünü kırmadığımızdan ötürü birtakım kişilerin öğretilerini, sırf bu kaybımızdan ötürü sorgusuz sualsiz kabulleniyor - doğru sandığımız yanlışlarımıza sürekli yeni eklemeler yapıyoruz.
Arayışını devam ettirebildiğin müddetçe yaşarsın.
-Programs/python.c
Herhangi bir Python kodu executed edilmeden önce, runtime önce configuration'u oluşturur. Runtime configuration'u , Include/cpython/initconfig.h içinde tanımlanmış PyConfig adlı bir data structure'dur. İçinde debug mod ve optimized mod için runtime flagleri, extended option -X <option>, runtime ayarları için enviroment variable'lar bulundurur. Configuration verileri öncelikle çeşitli özellikleri etkinleştirmek ve devre dışı bırakmak için CPython runtime tarafından kullanılır.
Python ayrıca CLI (Comman Line Interface) ile birlikte gelir. Python'da -v flag'i ile verbose modu etkinleştirebilirsiniz. Verbose modda, Python modülleri yüklendiğinde ekrana print ettirecektir.
Kod:
python.exe -v -c "print('hello valentina')"
Karşınıza yüzlerce satır gelecek, site-packages importları, system environmentte ki importlar.
Bu flag'in nere tanımladığına bakalım;
(L47-50, L279-L289)
Kod:
typedef struct {
int _config_version; /* Internal configuration version,
used for ABI compatibility */
int _config_init; /* _PyConfigInitEnum value */
Kod:
/* If greater than 0, enable the verbose mode: print a message each time a
module is initialized, showing the place (filename or built-in module)
from which it is loaded.
If greater or equal to 2, print a message for each file that is checked
for when searching for a module. Also provides information on module
cleanup at exit.
Incremented by the -v option. Set by the PYTHONVERBOSE environment
variable. If set to -1 (default), inherit Py_VerboseFlag value. */
int verbose;
Kısacası environment variable ve runtime command-line flaglerin'i anlamak için Python/initconfig.c dosyasını okuyabiliriz.
Config_read_env_vars fonksiyonunda, environment variablelar okunur ve configuration ayarlarına değer atamak için kullanılır:
Kod:
static PyStatus
config_read_env_vars(PyConfig *config)
{
PyStatus status;
int use_env = config->use_environment;
/* Get environment variables */
_Py_get_env_flag(use_env, &config->parser_debug, "PYTHONDEBUG");
_Py_get_env_flag(use_env, &config->verbose, "PYTHONVERBOSE");
_Py_get_env_flag(use_env, &config->optimization_level, "PYTHONOPTIMIZE");
_Py_get_env_flag(use_env, &config->inspect, "PYTHONINSPECT");
Verbose ayarları için PYTHONVERBOSE, &config->verbose değerini kullanıyor. Daha sonra tekrar initconfig.c içindeki config_parse_cmdline'da, eğer varsa command-line flag'i ile kullanılır:
(L1845, L1941-L1943, L1973-1980
Kod:
static PyStatus
config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
Py_ssize_t *opt_index)
{
...
switch (c) {
...
case 'v':
config->verbose++;
break;
...
/* This space reserved for other options */
default:
/* unknown argument: parsing failed */
config_usage(1, program);
return _PyStatus_EXIT(2);
}
} while (1);
Bu değer daha sonra _Py_GetGlobalVariablesAsDict fonksiyonunda Py_VerboseFlag adlı değişkene kopyalanıyor. Bir Python session'unda, sys.flags komutunu kullanarak verbose mod, quiet mod gibi runtime flaglerine erişebilirsiniz. -X flaglerinin tümü sys._xoptions adlı dictionary'de bulunur:
Kod:
python.exe -X dev -q
import sys
sys.flags
[COLOR="YellowGreen"]sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0,
no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0,
quiet=1, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=0)[/COLOR]
sys._xoptions
[COLOR="yellowgreen"]{'dev': True}[/COLOR]
initconfig.h içindeki runtime configuration'un yanı sıra, root klasöründeki pyconfig.h içinde bulunan build configuration'u da vardır. Build configuration'u şu şekilde görebilirsiniz:
Kod:
python.exe -m sysconfig
-input
Modules/main.c içindeki pymain_main fonksiyonu tarafından işlenir. En basit olanı CPython'a -c opsiyonu ve tırnak içerisinde bir Python programı belirlemektir.
Kod:
python.exe -c "print('valentina')"
[COLOR="YellowGreen"]valentina[/COLOR]
İlk olarak pymain_run_command() fonksiyonu Modules/main.c içerisinde, -c'ye argüman olarak verilen komut ile işlenir. Wchar_t* genellikle UTF8 karakterlerini depolayabildiğinden, CPython üzerinden Unicode verileri için düşük düzeyli bir depolama türü olarak kullanılır.
Wchar_t* Python string'ine dönüştürülürken, Objects/unicodeobject.c dosyasında str türünde bir PyObject döndüren helper fonksiyonu olan PyUnicode_FromWideChar()'ı barındırır. UTF8'e dönüştürme işlemi PyUnicode_AsUTF8String() ile bittikten sonra str'yi bytes objectine dönüştürür. Bu da tamamlandığında, pymain_run_command() daha sonra Python bytes nesnesini execute etmek üzere PyRun_SimpleStringFlags() objesine iletir, ancak önce byte'ları str'ye tekrar dönüştürür:
Kod:
static int
pymain_run_command(wchar_t *command, PyCompilerFlags *cf)
{
PyObject *unicode, *bytes;
int ret;
unicode = PyUnicode_FromWideChar(command, -1);
if (unicode == NULL) {
goto error;
}
if (PySys_Audit("cpython.run_command", "O", unicode) < 0) {
return pymain_exit_err_print();
}
bytes = PyUnicode_AsUTF8String(unicode);
Py_DECREF(unicode);
if (bytes == NULL) {
goto error;
}
ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), cf);
Py_DECREF(bytes);
return (ret != 0);
error:
PySys_WriteStderr("Unable to decode the command from the command line:\n");
return pymain_exit_err_print();
}
Kabaca üstünden geçmek gerekirse yukarıda ki yapılan wchar_t*'ı Unicode'a, byte'a ve string'e dönüştürme işlemi şu işleme eşittir:
Kod:
unicode = str(command)
bytes_ = bytes(unicode.encode('utf8'))
# call PyRun_SimpleStringFlags with bytes_
PyRun_SimpleStringFlags() işlevi Python/pythonrun.c dosyasının bir parçasıdır. Amacı, bu basit komutu bir Python modülüne dönüştürmek ve ardından çalıştırılmak üzere iletmektir. Bir Python modülünün bağımsız bir modül olarak çalıştırılması için __main__'e ihtiyacı olduğundan, kendisi otomatik olarak oluşturur:
Kod:
int
PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
{
PyObject *m, *d, *v;
[COLOR="YellowGreen"]m = PyImport_AddModule("__main__");[/COLOR] if (m == NULL)
return -1;
[COLOR="yellowgreen"]d = PyModule_GetDict(m); v = PyRun_StringFlags(command, Py_file_input, d, d, flags); [/COLOR] if (v == NULL) {
PyErr_Print();
return -1;
}
Py_DECREF(v);
return 0;
}
PyRun_SimpleStringFlags(), bir modül ve dictionary oluşturduktan sonra sahte bir dosya adı oluşturan PyRun_StringFlags() objesini çağırır ve ardından stringden bir AST oluşturmak ve bir modül return etmek için Python parserini çağırır:
Kod:
PyObject *
PyRun_StringFlags(const char *str, int start, PyObject *globals,
PyObject *locals, PyCompilerFlags *flags)
{
...
mod = PyParser_ASTFromStringObject(str, filename, start, flags, arena);
if (mod != NULL)
ret = run_mod(mod, filename, globals, locals, flags, arena);
PyArena_Free(arena);
return ret;
Python komutlarını execute etmenin başka bir yolu, modül adıyla -m seçeneğini beraber kullanmaktır. Örnek vermek gerekirse, standart library'de unittest modülünü çalıştırmak için python -m unittest komutunu kullanırız.
Modülleri komut dosyası olarak execute etmek, önce PEP 338'de ve daha sonra PEP366'da tanımlanan relative importlar için standart olarak önerilmiştir. -M flag'inin kullanılması, modül paketinde __main__ içindeki her şeyi execute etmek istediğinizi gösterir. Bu arama mekanizması için unittest modülünün dosyalarınız içerisinde nerede olduğunun bir önemi yoktur. Modüller/main.c içinde -m flag'i ile çalıştırıldığında modülün adını modname argümanı olarak alan fonksiyon vardır.
CPython daha sonra standart bir kütüphane modülünü, runpy'ı import eder ve PyObject_Call() fonksiyonunu kullanarak execute eder. İçe aktarma, Python/import.c dosyasında bulunan C API fonksiyonu olan PyImport_ImportModule() kullanılarak yapılır:
Kod:
static int
pymain_run_module(const wchar_t *modname, int set_argv0)
{
PyObject *module, *runpy, *runmodule, *runargs, *result;
runpy = PyImport_ImportModule("runpy");
...
runmodule = PyObject_GetAttrString(runpy, "_run_module_as_main");
...
module = PyUnicode_FromWideChar(modname, wcslen(modname));
...
runargs = Py_BuildValue("(Oi)", module, set_argv0);
...
result = PyObject_Call(runmodule, runargs, NULL);
...
if (result == NULL) {
return pymain_exit_err_print();
}
Py_DECREF(result);
return 0;
}
Bu fonksiyonun içerisinde 2 farklı C API fonksiyonu görüyoruz: PyObject_Call() ve PyObject_GetAttrString(). Çünkü PyImport_ImportModule(), core object türü olan bir PyObject* döndürdüğünden, attiribute'leri almak ve çağırmak için special fonksiyonları çağırmanız gerekir. Python'da bir objectiniz varsa ve bir attribute almak istiyorsanız, getattr() öğesini çağırabilirsiniz. C API'sinde bu işlem, Objects/object.c içinde bulunan PyObject_GetAttrString() methodudur. Bir callable çalıştırmak istiyorsanız, method sonuna parantez ekler veya herhangi bir Python objectinde __call __() özelliğini çalıştırabilirsiniz. __call __() methodu, Objects/object.c içinde uygulanır:
Kod:
valentina = "Russian!"
valentina.upper() == valentina.upper.__call__() # ayni seylerdir.
Runpy modülü pure Python ile yazılmıştır. Lib/runpy.py adresinde bulabilirsiniz. Python -m <module> uygulaması, python -m runpy <module> 'un çalıştırılmasına eşdeğerdir. Runpy modülü, bir işletim sistemindeki modülleri bulma ve yürütme sürecini özetlemek için oluşturulmuştur. Runpy hedef modülü çalıştırmak için aşşağıda ki işlemleri yapar:
● İsmi girilen modül için __import __()'u çağırır.
● __name__ 'i __main__ namespace'ine eşitler.
● Modülü __main__ namespace'inde execute eder.
İlk argüman python valentina.py gibi bir dosya adı ise, CPython Python'da open() komutuna benzer bir komut ile açar ve Python/pythonrun.c içindeki PyRun_SimpleFileExFlags()'e iletir. Eğer dosya uzantısı .pyc ise run_pyc_file() çalıştırılır , dosya uzantısı .py ise PyRun_FileExFlags() çalıştırılır. eğer .stdin ise PyRun_FileExFlags().
Kod:
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags)
{
...
m = PyImport_AddModule("__main__");
...
[COLOR="YellowGreen"]if (maybe_pyc_file(fp, filename, ext, closeit)) { ...
v = run_pyc_file(pyc_fp, filename, d, d, flags);[/COLOR] } else {
/* When running from stdin, leave __main__.__loader__ alone */
if (strcmp(filename, "<stdin>") != 0 &&
set_main_loader(d, filename, "SourceFileLoader") < 0) {
fprintf(stderr, "python: failed to set __main__.__loader__\n");
ret = -1;
goto done;
}
[COLOR="yellowgreen"]v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d, closeit, flags);[/COLOR] }
...
return ret;
}
Stdin ve temel scriptler için, CPython pythonrun.c dosyasında bulunan PyRun_FileExFlags()'e iletir.
PyRun_FileExFlags()'in işlevi, -c inputu için kullanılan PyRun_SimpleStringFlags() yöntemine benzer. -c inputu ile kullanım adımları:
Kod:
PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
PyObject *locals, int closeit, PyCompilerFlags *flags)
{
...
mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
flags, NULL, arena);
...
ret = run_mod(mod, filename, globals, locals, flags, arena);
}
run_eval_code_obj() fonksiyonu, Python/eval.c dosyasında PyEval_EvalCode() fonksiyonunu çağıran basit bir wrapper'dir. PyEval_EvalCode() fonksiyonu, CPython için main evaluation loop'tur, her bytecode statement'ini iterate eder ve local makinemizde execute eder.
PyRun_SimpleFileExFlags() fonksiyonunda, .pyc dosyasına dosya yolu sağlayan kullanıcı için bir clause vardır. Dosya yolu .pyc ile bitiyorsa, dosyayı düz metin dosyası olarak yüklemek ve parselemek yerine, .pyc dosyasının diske yazılan bir kod objecti içerdiğini varsayar.
Marshaling, bir dosyanın içeriğini belleğe kopyalamak ve specific data structure'a dönüştürmek için kullanılan teknik bir terimdir. Diskteki code object data structure'u, CPython Compiler'ının compile edilmiş kodu önbelleğe alma yoludur, böylece her seferinde parseleme işlemi yapması gerekmez:
Kod:
static PyObject *
run_pyc_file(FILE *fp, const char *filename, PyObject *globals,
PyObject *locals, PyCompilerFlags *flags)
{
PyCodeObject *co;
PyObject *v;
...
[COLOR="YellowGreen"]v = PyMarshal_ReadLastObjectFromFile(fp)[/COLOR]; ...
if (v == NULL || !PyCode_Check(v)) {
Py_XDECREF(v);
PyErr_SetString(PyExc_RuntimeError,
"Bad code object in .pyc file");
goto error;
}
fclose(fp);
[COLOR="yellowgreen"]co = (PyCodeObject *)v; v = run_eval_code_obj(co, globals, locals);[/COLOR] if (v && flags)
flags->cf_flags |= (co->co_flags & PyCF_MASK);
Py_DECREF(co);
return v;
}
Kod objecti memory'e marshal edildikten sonra, kodu execute etmek için Python/ceval.c'yi çağıran run_eval_code_obj() fonksiyonuna gönderilir.
İncelenen fonksiyonlar beyaz ile belirtilmiş hangi satırda olduğuna kadar linkleri vardır.
Saygılarımla..


