📌 PyQGIS: Shapefile-ის შექმნა და Garbage Collection¶
QGIS 3.22 – 3.44+ | თანამედროვე API | Georgian Technical Guide
შენი კლასიკური QgsVectorFileWriter + del writer მიდგომა ჯერ კიდევ მუშაობს, მაგრამ თანამედროვე QGIS-ში უფრო უსაფრთხო და ელეგანტური გზები არსებობს.
📋 შინაარსი¶
- მაგალითი კოდი — ძველი სანდო სტილი
- რა არის Garbage Collector?
- QgsVectorFileWriter-ის ცხოვრების ციკლი
- del writer-ის გარეშე — რა ხდება?
- თანამედროვე მიდგომა (QGIS 3.22+)
- try / finally — საუკეთესო პრაქტიკა
- Context Manager —
withსტილი - სრული მაგალითი — Feature-ების ჩაწერა
- TL;DR — სწრაფი გადაწყვეტილების ცხრილი
🧾 მაგალითი კოდი¶
ძველი (მაგრამ სანდო) სტილი¶
from qgis.core import (
QgsVectorFileWriter,
QgsFields,
QgsField,
QgsWkbTypes,
QgsCoordinateReferenceSystem
)
from PyQt5.QtCore import QVariant
shapefile_path = r'C:\Users\Public\Documents\GIS\shapefile\saxli.shp'
# ველების განსაზღვრა
fields = QgsFields()
fields.append(QgsField('ID', QVariant.Int))
fields.append(QgsField('Field_1', QVariant.String))
fields.append(QgsField('Field_2', QVariant.Double))
# Writer-ის შექმნა
writer = QgsVectorFileWriter(
shapefile_path,
'UTF-8',
fields,
QgsWkbTypes.Point,
QgsCoordinateReferenceSystem('EPSG:32638'),
'ESRI Shapefile'
)
# ... აქ ჩაწერ feature-ებს ...
del writer # ⚠️ ეს ხაზი სავალდებულოა!
⚠️ გამაფრთხილებელი შენიშვნა:
del writerგარეშე ფაილი შეიძლება არასრული ან locked დარჩეს!
♻️ რა არის Garbage Collector?¶
Python ავტომატურად მართავს მეხსიერებას — როდესაც ობიექტზე reference აღარ არსებობს, GC მას შლის.
პრობლემა PyQGIS-ში — ორ-ფენიანი არქიტექტურა:
┌─────────────────────────────┐
│ Python (SIP wrapper) │ ← GC-ს ეს ფენა ხედავს
├─────────────────────────────┤
│ C++ (ფაილი, ბუფერი, lock) │ ← GC-ს ეს ფენა ყოველთვის არ ხედავს
└─────────────────────────────┘
👉 შედეგი: Python-ის GC შეიძლება "გაახსენდეს" ობიექტის წაშლა, მაგრამ C++ დესტრუქტორი არ გაიძახოს — ფაილი კი დახურულ-გაურეცხავი დარჩეს.
🔄 ცხოვრების ციკლი¶
writer = QgsVectorFileWriter(...) → ✅ ფაილი იხსნება + lock-დება
↓
addFeature(feat) → 📦 მონაცემები ბუფერში
↓
del writer (ან GC) → 🔧 C++ დესტრუქტორი იძახება
↓
ფაილი იხურება + ბოლო მონაცემები ჩაიწერება → ✅ დასრულებულია
❌ del writer-ის გარეშე — რა ხდება?¶
| პრობლემა | შედეგი | სად ხდება ხშირად |
|---|---|---|
| 🔒 ფაილი რჩება locked | "File in use" — წაშლა/გადატანა შეუძლებელია |
Windows-ზე ყოველთვის |
| 💾 ბოლო feature-ები არ ჩაიწერება | .shp ფაილი არასრულია, ზოგჯერ 0 KB |
ნებისმიერ OS-ზე |
| 🧠 Memory leak loop-ში | Out of Memory / QGIS-ი იკეტება |
დიდი ციკლების დროს |
🚀 თანამედროვე მიდგომა¶
create() + SaveVectorOptions (QGIS 3.22+)¶
from qgis.core import (
QgsVectorFileWriter,
QgsCoordinateTransformContext,
QgsProject
)
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = "ESRI Shapefile"
options.fileEncoding = "UTF-8"
# options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile
transform_context = QgsProject.instance().transformContext()
writer = QgsVectorFileWriter.create(
shapefile_path,
fields,
QgsWkbTypes.Point,
QgsCoordinateReferenceSystem('EPSG:32638'),
transform_context,
options
)
if writer.hasError() != QgsVectorFileWriter.NoError:
print("❌ შეცდომა:", writer.errorMessage())
else:
# ... addFeature(feat) ...
del writer
print("✅ ფაილი წარმატებით შეიქმნა")
Layer-ის პირდაპირ ექსპორტი¶
# თუ გაქვს მზა QgsVectorLayer
error, new_file, _, _ = QgsVectorFileWriter.writeAsVectorFormatV3(
layer,
shapefile_path,
transform_context,
options
)
if error == QgsVectorFileWriter.NoError:
print("✅ ექსპორტი დასრულდა — del საჭირო არ არის!")
✅
writeAsVectorFormatV3— ამ შემთხვევაშიdelარ გჭირდება, რადგან writer შიდა scope-შია.
🛡️ try / finally — საუკეთესო პრაქტიკა¶
writer = QgsVectorFileWriter.create(
shapefile_path,
fields,
QgsWkbTypes.Point,
QgsCoordinateReferenceSystem('EPSG:32638'),
QgsProject.instance().transformContext(),
options
)
try:
for feature in features:
writer.addFeature(feature)
print(f"✅ {len(features)} feature ჩაიწერა")
except Exception as e:
print(f"❌ შეცდომა: {e}")
finally:
del writer # გარანტირებული cleanup — შეცდომის შემთხვევაშიც!
print("🔒 ფაილი უსაფრთხოდ დახურულია")
💡
finallyბლოკი ყოველთვის სრულდება — გამონაკლისის შემთხვევაშიც, რაც cleanup-ს გარანტიას იძლევა.
🧩 Context Manager — with სტილი¶
პრობლემა: QgsVectorFileWriter-ს with ჩაშენებული არ აქვს¶
QgsVectorFileWriter არ არის native context manager — მას არ გააჩნია __enter__ / __exit__ მეთოდები. ამიტომ პირდაპირ ასე ვერ გამოიყენებ:
გამოსავალი 1 — contextlib.contextmanager (მარტივი)¶
from contextlib import contextmanager
from qgis.core import QgsVectorFileWriter, QgsProject
@contextmanager
def vector_writer(path, fields, geom_type, crs, options):
"""
QgsVectorFileWriter-ის context manager wrapper.
with ბლოკის დასასრულს ავტომატურად ახდენს cleanup-ს.
"""
writer = QgsVectorFileWriter.create(
path, fields, geom_type, crs,
QgsProject.instance().transformContext(),
options
)
if writer.hasError() != QgsVectorFileWriter.NoError:
raise RuntimeError(f"Writer შეცდომა: {writer.errorMessage()}")
try:
yield writer # ← ეს "writer" მოდის as-ში
finally:
del writer # ← with ბლოკის დასასრულს ყოველთვის სრულდება
print("✅ ფაილი დახურულია")
# გამოყენება:
with vector_writer(shapefile_path, fields, QgsWkbTypes.Point, crs, options) as writer:
for feat in features:
writer.addFeature(feat)
# ← აქ del ავტომატურად უკვე მოხდა
✅ უპირატესობა:
del-ზე ფიქრი აღარ გჭირდება —finallyბლოკი ამას ყოველთვის გაარიგებს.
გამოსავალი 2 — Wrapper კლასი __enter__ / __exit__-ით (ობიექტ-ორიენტირებული)¶
from qgis.core import (
QgsVectorFileWriter, QgsFields, QgsWkbTypes,
QgsCoordinateReferenceSystem, QgsProject
)
class ShapefileWriter:
"""
QgsVectorFileWriter-ის OOP wrapper with context manager მხარდაჭერით.
გამოყენება:
with ShapefileWriter(path, fields, geom_type, crs) as writer:
writer.addFeature(feat)
"""
def __init__(self, path, fields, geom_type, crs, options=None):
self.path = path
self.fields = fields
self.geom_type = geom_type
self.crs = crs
self.options = options or self._default_options()
self._writer = None
@staticmethod
def _default_options():
opts = QgsVectorFileWriter.SaveVectorOptions()
opts.driverName = "ESRI Shapefile"
opts.fileEncoding = "UTF-8"
return opts
def __enter__(self):
self._writer = QgsVectorFileWriter.create(
self.path, self.fields, self.geom_type, self.crs,
QgsProject.instance().transformContext(),
self.options
)
if self._writer.hasError() != QgsVectorFileWriter.NoError:
raise RuntimeError(self._writer.errorMessage())
return self._writer # ← ეს "writer" მოდის as writer-ში
def __exit__(self, exc_type, exc_val, exc_tb):
if self._writer is not None:
del self._writer # ← გარანტირებული cleanup
self._writer = None
if exc_type:
print(f"❌ გამონაკლისი: {exc_val}")
else:
print(f"✅ '{self.path}' — წარმატებით შეიქმნა")
return False # ← False = გამონაკლისი კვლავ გარეთ გადაეცემა
# გამოყენება:
with ShapefileWriter(shapefile_path, fields, QgsWkbTypes.Point, crs) as writer:
for feat in features:
writer.addFeature(feat)
💡 რატომ
return False? —__exit__ბლოკშიFalseნიშნავს, რომ გამონაკლისი არ ჩახშობილა — ის კვლავ გარეთ "ამოვა".Trueკი ჩახშობდა, რაც ჩვეულებრივ არასასურველია.
მეთოდების შედარება¶
┌──────────────────┬───────────────────┬──────────────────┬──────────────────┐
│ მიდგომა │ auto cleanup │ კოდის სიმარტივე │ რეკომენდაცია │
├──────────────────┼───────────────────┼──────────────────┼──────────────────┤
│ del writer │ ❌ ხელით │ ✅ მარტივი │ ძველი კოდი │
│ try / finally │ ✅ გარანტირებული │ 🔶 საშუალო │ ✅ კარგი │
│ contextmanager │ ✅ გარანტირებული │ ✅ მარტივი │ ✅ კარგი │
│ Wrapper კლასი │ ✅ გარანტირებული │ ✅ ელეგანტური │ ⭐ საუკეთესო │
└──────────────────┴───────────────────┴──────────────────┴──────────────────┘
📝 სრული მაგალითი — Feature-ების ჩაწერა¶
ქვემოთ მოცემულია end-to-end მაგალითი: ველების განსაზღვრიდან დაწყებული, feature-ების შექმნამდე და Shapefile-ში ჩაწერამდე. კოორდინატები — EPSG:32638 (UTM Zone 38N, საქართველო).
from qgis.core import (
QgsVectorFileWriter,
QgsFields,
QgsField,
QgsFeature,
QgsGeometry,
QgsPointXY,
QgsWkbTypes,
QgsCoordinateReferenceSystem,
QgsProject
)
from PyQt5.QtCore import QVariant
from contextlib import contextmanager
# ──────────────────────────────────────────────
# 1. კონფიგურაცია
# ──────────────────────────────────────────────
OUTPUT_PATH = r'C:\Users\Public\Documents\GIS\shapefile\saxlebi.shp'
CRS = QgsCoordinateReferenceSystem('EPSG:32638') # UTM 38N — საქართველო
# ──────────────────────────────────────────────
# 2. ველების (სვეტების) განსაზღვრა
# ──────────────────────────────────────────────
fields = QgsFields()
fields.append(QgsField('id', QVariant.Int))
fields.append(QgsField('dasaxeleba', QVariant.String, len=100))
fields.append(QgsField('fartobi', QVariant.Double, prec=2))
fields.append(QgsField('aghuricxva', QVariant.Int))
# ──────────────────────────────────────────────
# 3. საცდელი მონაცემები (სახლების სია)
# ──────────────────────────────────────────────
saxlebi_data = [
# (id, dasaxeleba, fartobi, aghuricxva, x_utm, y_utm )
(1, 'სახლი N1 — თბილისი', 120.5, 4, 490230.0, 4741850.0),
(2, 'სახლი N2 — რუსთავი', 85.0, 2, 501100.0, 4732400.0),
(3, 'სახლი N3 — გორი', 210.0, 6, 432700.0, 4740600.0),
(4, 'სახლი N4 — ქუთაისი', 95.5, 3, 340850.0, 4738200.0),
(5, 'სახლი N5 — ბათუმი', 175.0, 5, 241950.0, 4616300.0),
]
# ──────────────────────────────────────────────
# 4. Context Manager (contextlib სტილი)
# ──────────────────────────────────────────────
@contextmanager
def vector_writer(path, fields, geom_type, crs):
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = "ESRI Shapefile"
options.fileEncoding = "UTF-8"
options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile
writer = QgsVectorFileWriter.create(
path, fields, geom_type, crs,
QgsProject.instance().transformContext(),
options
)
if writer.hasError() != QgsVectorFileWriter.NoError:
raise RuntimeError(f"❌ Writer ვერ შეიქმნა: {writer.errorMessage()}")
try:
yield writer
finally:
del writer # C++ destructor → ფაილი იხურება + flush
# ──────────────────────────────────────────────
# 5. Feature-ების შექმნა და ჩაწერა
# ──────────────────────────────────────────────
written_count = 0
with vector_writer(OUTPUT_PATH, fields, QgsWkbTypes.Point, CRS) as writer:
for row in saxlebi_data:
feat_id, dasaxeleba, fartobi, aghuricxva, x, y = row
# 5a. Feature ობიექტის შექმნა
feat = QgsFeature(fields)
# 5b. გეომეტრია — Point
feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
# 5c. ატრიბუტების შევსება
feat['id'] = feat_id
feat['dasaxeleba'] = dasaxeleba
feat['fartobi'] = fartobi
feat['aghuricxva'] = aghuricxva
# 5d. Feature-ის ჩაწერა
if writer.addFeature(feat):
written_count += 1
print(f" ✅ [{feat_id}] {dasaxeleba}")
else:
print(f" ❌ [{feat_id}] ჩაწერა ვერ მოხდა!")
# ── with ბლოკი დასრულდა → del ავტომატურად მოხდა ──
print(f"\n📊 სულ ჩაიწერა: {written_count} / {len(saxlebi_data)} feature")
print(f"📁 ფაილი: {OUTPUT_PATH}")
მოსალოდნელი გამოტანა (output)¶
✅ [1] სახლი N1 — თბილისი
✅ [2] სახლი N2 — რუსთავი
✅ [3] სახლი N3 — გორი
✅ [4] სახლი N4 — ქუთაისი
✅ [5] სახლი N5 — ბათუმი
📊 სულ ჩაიწერა: 5 / 5 feature
📁 ფაილი: C:\Users\Public\Documents\GIS\shapefile\saxlebi.shp
✅ ფაილი დახურულია
🗺️ Feature-ების ჩაწერის სქემა¶
saxlebi_data (list of tuples)
│
▼
for row in saxlebi_data:
│
├── QgsFeature(fields) ← ჩარჩო/template
│
├── setGeometry(QgsPointXY(x,y)) ← კოორდინატი
│
├── feat['სახელი'] = მნიშვნელობა ← ატრიბუტები
│
└── writer.addFeature(feat) ← ბუფერში ჩაიწერება
│
del writer (with __exit__)
│
C++ flush → disk ✅
💡 შენიშვნა ველის სიგრძეზე:
QgsField('dasaxeleba', QVariant.String, len=100)— ESRI Shapefile-ში სტრიქონის მაქსიმალური სიგრძეა 254 სიმბოლო. Georgian Unicode სიმბოლოებისთვის კარგი ზღვარია 100–150.
⭐ TL;DR¶
სწრაფი გადაწყვეტილების ცხრილი (2025–2026)¶
| სიტუაცია | რეკომენდაცია | del აუცილებელია? |
|---|---|---|
| მარტივი ერთჯერადი writer | create() + del writer |
✅ ხელით |
| Layer-ის ექსპორტი | writeAsVectorFormatV3() |
❌ არა |
| ბევრი ფაილის წერა ციკლში | try / finally + del writer |
✅ ხელით |
| სუფთა, მოდულური კოდი | contextmanager wrapper |
✅ ავტომატური |
| OOP / გამოყენებადი კლასი | ShapefileWriter (__exit__) |
✅ ავტომატური |
| მაქსიმალური სტაბილურობა | ნებისმიერი with მიდგომა |
✅ გარანტირებული |
💎 მთავარი გზავნილი¶
del writer— ყოველთვის გამოიყენე ხელით შექმნილ writer-ებთანtry / finally— გარანტირებული cleanup ნებისმიერ სცენარშიcontextmanager— სუფთაwithსტილი მინიმალური კოდითShapefileWriterკლასი — OOP მიდგომა, გამოყენებადი მთელ პროექტშიwriteAsVectorFormatV3— ყველაზე სუფთა გზა layer-ების ექსპორტისთვის
გამოყენებული API: QGIS 3.22–3.44+ | PyQGIS | SIP wrapper | C++ destructor pattern
C++ დესტრუქტორი PyQGIS-ის კონტექსტში¶
C++ | Python | SIP Wrapper | PyQGIS | მეხსიერების მართვა
დესტრუქტორი — C++ კლასის სპეციალური მეთოდი, რომელიც ავტომატურად გამოიძახება ობიექტის განადგურებისას. ის ათავისუფლებს რესურსებს — ხურავს ფაილებს, ათავისუფლებს მეხსიერებას, წყვეტს კავშირებს.
📋 შინაარსი¶
- რა არის დესტრუქტორი?
- C++ vs Python — სიცოცხლის ციკლი
- SIP Wrapper — ორ-ფენიანი არქიტექტურა
- პრობლემა PyQGIS-ში
del— C++ დესტრუქტორის ხელით გაშვება- QgsVectorFileWriter — სრული სიცოცხლის ციკლი
- Cleanup პატერნები
- სხვა PyQGIS კლასები რომლებიც cleanup საჭიროებს
🔧 რა არის დესტრუქტორი?¶
C++-ში ყოველ კლასს შეიძლება ჰქონდეს კონსტრუქტორი და დესტრუქტორი:
class QgsVectorFileWriter {
public:
// კონსტრუქტორი — ობიექტი იქმნება
QgsVectorFileWriter(const QString& path, ...) {
// ✅ ფაილი იხსნება
// ✅ ბუფერი გამოიყოფა
// ✅ Lock-ი იდება
}
// დესტრუქტორი — ობიექტი ნადგურდება
~QgsVectorFileWriter() {
// ✅ ბუფერი ჩაიწერება დისკზე (flush)
// ✅ ფაილი დაიხურება
// ✅ Lock-ი მოიხსნება
// ✅ მეხსიერება გათავისუფლდება
}
};
დესტრუქტორის თვისებები¶
✅ ავტომატურია — C++ თვითონ იძახებს როდესაც ობიექტი scope-ს ტოვებს
✅ ერთია — კლასს მხოლოდ ერთი დესტრუქტორი შეიძლება ჰქონდეს
✅ პარამეტრი არ — ~ClassName() — არგუმენტები არ მიიღება
✅ გარანტირებულ — C++-ში stack ობიექტებისთვის ყოველთვის გამოიძახება
C++-ში ავტომატური გამოძახება¶
void someFunction() {
QgsVectorFileWriter writer("output.shp", ...); // კონსტრუქტორი
writer.addFeature(feat1);
writer.addFeature(feat2);
} // ← ფუნქცია მთავრდება → ~QgsVectorFileWriter() ავტომატურად!
// ფაილი დაიხურება, ბუფერი ჩაიწერება
⚖️ C++ vs Python — სიცოცხლის ციკლი¶
C++ და Python სრულიად განსხვავებულად მართავს ობიექტების სიცოცხლეს:
C++ — Deterministic Destruction¶
// C++ — ზუსტად ვიცით, სად ნადგურდება ობიექტი
{
Writer w("file.shp"); // კონსტრუქტორი — ზუსტად აქ
w.addFeature(f);
} // დესტრუქტორი — ზუსტად აქ, scope-ის ბოლოს
// "deterministic" — წინასწარ განჭვრეტადი
Python — Non-Deterministic GC¶
# Python — ვერ ვიცით ზუსტად, სად გაიწმინდება
writer = SomePythonObject()
writer.do_something()
writer = None # reference წაიშალა... მაგრამ GC სად გაასუფთავებს?
# შეიძლება ახლავე, შეიძლება მოგვიანებით — "non-deterministic"
ძირითადი განსხვავება¶
┌──────────────────────┬──────────────────────────────────────────────┐
│ C++ │ Python │
├──────────────────────┼──────────────────────────────────────────────┤
│ Destructor ყოველთვის │ __del__ გამოიძახება... სადღაც, სადმე │
│ ზუსტ მომენტში │ │
├──────────────────────┼──────────────────────────────────────────────┤
│ Stack object → │ Reference count = 0 → │
│ scope exit = destroy │ GC გადაწყვეტს როდის გაასუფთაოს │
├──────────────────────┼──────────────────────────────────────────────┤
│ "I control memory" │ "GC controls memory" │
└──────────────────────┴──────────────────────────────────────────────┘
🔗 SIP Wrapper — ორ-ფენიანი არქიტექტურა¶
QGIS C++-ში დაწერილია. PyQGIS გვაძლევს Python-იდან წვდომას SIP-ის (SIP Interface for Python) გამოყენებით:
┌─────────────────────────────────────────────────┐
│ Python სამყარო │
│ ┌───────────────────────────────────────────┐ │
│ │ writer = QgsVectorFileWriter(...) │ │
│ │ (Python ობიექტი — SIP wrapper) │ │
│ └──────────────────────┬────────────────────┘ │
│ │ SIP Bridge │
├─────────────────────────▼────────────────────────┤
│ C++ სამყარო │
│ ┌───────────────────────────────────────────┐ │
│ │ QgsVectorFileWriter* ptr │ │
│ │ (C++ ობიექტი — რეალური რესურსები) │ │
│ │ → ფაილი გახსნილია │ │
│ │ → ბუფერი მეხსიერებაში │ │
│ │ → OS Lock │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
SIP wrapper-ის ქცევა¶
writer = QgsVectorFileWriter(...)
# ↑ ეს ქმნის:
# 1. Python wrapper ობიექტს (მცირე, Python-ის heap-ში)
# 2. C++ QgsVectorFileWriter ობიექტს (ფაილის რესურსებით)
# Python wrapper "ფლობს" C++ ობიექტს — pointer-ს ინახავს
del writer
# ↑ ეს:
# 1. Python wrapper-ს წყვეტს
# 2. SIP ხვდება: Python მხარე გაქრა → C++ destructor გამოძახება!
# 3. ~QgsVectorFileWriter() → flush + close + unlock
⚠️ პრობლემა PyQGIS-ში¶
Python-ის GC არ იძახებს C++ დესტრუქტორს ზუსტ მომენტში. ეს იწვევს:
პრობლემა 1 — ფაილი დარჩება Locked¶
writer = QgsVectorFileWriter("output.shp", ...)
writer.addFeature(feat)
# writer = None ← reference წაიშალა, მაგრამ GC ჯერ არ გაიმეორა
# მომდევნო კოდი ამ ფაილს ვეღარ შეეხება:
os.remove("output.shp") # ❌ PermissionError: file is in use!
shutil.move("output.shp", "backup/") # ❌ WinError 32
პრობლემა 2 — მონაცემები არ ჩაიწერება¶
writer = QgsVectorFileWriter("output.shp", ...)
for feat in features:
writer.addFeature(feat)
# ↑ feature-ები ბუფერშია, დისკზე ჯერ არ არის!
# GC-მ ჯერ არ გაასუფთავა → ~QgsVectorFileWriter() ჯერ არ გამოიძახა
# → flush() ჯერ არ მოხდა → ბოლო feature-ები დაიკარგა!
layer = QgsVectorLayer("output.shp", "Layer", "ogr")
# .shp ფაილი შეიძლება:
# → ცარიელია (0 features)
# → არასრულია (ბოლო batch-ი დაიკარგა)
# → დაზიანებულია (header ≠ content)
პრობლემა 3 — Memory Leak ციკლში¶
for i in range(10000):
writer = QgsVectorFileWriter(f"layer_{i}.shp", ...)
writer.addFeature(feat)
# del writer ← ეს სტრიქონი არ არის!
# GC-ი ვერ ასწრებს → C++ ობიექტები გროვდება → RAM სავსეა
# → QGIS-ი იყინება / ნელდება / ილახება
🗑️ del — C++ დესტრუქტორის ხელით გაშვება¶
Python-ის del reference-ს წყვეტს. როდესაც ბოლო reference წაიშლება — Python ახლავე ათავისუფლებს ობიექტს → SIP დესტრუქტორს გამოიძახებს:
writer = QgsVectorFileWriter("output.shp", ...)
writer.addFeature(feat1)
writer.addFeature(feat2)
del writer
# ↑ სწორედ ამ სტრიქონზე:
# Python: reference_count(writer) → 0
# SIP: "Python-ს აღარ სჭირდება" → C++ destructor!
# C++: ~QgsVectorFileWriter()
# → ბუფერი → disk (flush)
# → file.close()
# → OS lock-ის მოხსნა
# → C++ ობიექტის მეხსიერება → free()
del vs = None¶
# ✅ ორივე მუშაობს — reference count 0-ზე ეცემა
del writer # ← ყველაზე გამოხატული, კოდში ნათელია
writer = None # ← იგივე ეფექტი, ნაკლებ კითხვადი
# ⚠️ ეს არ მუშაობს — reference კვლავ არსებობს!
backup = writer # სხვა ცვლადი ინახავს reference-ს
del writer # ← Python: reference_count = 1 (backup-ი ჯერ კიდევ!)
# → C++ destructor ჯერ არ გამოიძახება!
Reference Count-ის ვიზუალიზაცია¶
writer = QgsVectorFileWriter(...)
→ Python object: ref_count = 1
backup = writer
→ Python object: ref_count = 2
del writer
→ Python object: ref_count = 1 ← ჯერ კიდევ ცოცხალია!
del backup
→ Python object: ref_count = 0 ← ახლა: C++ destructor!
🔄 QgsVectorFileWriter — სრული სიცოცხლის ციკლი¶
QgsVectorFileWriter(path, encoding, fields, geomType, crs, driver)
│
│ კონსტრუქტორი (~QgsVectorFileWriter())
▼
┌──────────────────────────────┐
│ C++ ობიექტი "ცოცხალია" │
│ ✅ output.shp გახსნილია │
│ ✅ OS Lock დადებულია │
│ ✅ Buffer მზადაა │
└──────────────────────────────┘
│
│ addFeature(feat) — ბუფერში
▼
┌──────────────────────────────┐
│ feature-ები ბუფერშია │
│ ⚠️ დისკზე ჯერ არ არის │
│ ⚠️ ფაილი ჯერ არ არის სრული │
└──────────────────────────────┘
│
│ del writer ←── ეს სტრიქონი!
▼
┌──────────────────────────────┐
│ ~QgsVectorFileWriter() │ C++ Destructor
│ ✅ flush() — ბუფერი → disk │
│ ✅ file.close() │
│ ✅ OS Lock მოიხსნა │
│ ✅ მეხსიერება გათავისუფლდა │
└──────────────────────────────┘
│
▼
output.shp — სრული და კითხვადი ✅
🛡️ Cleanup პატერნები¶
პატერნი 1 — del (მინიმალური)¶
writer = QgsVectorFileWriter(
shapefile_path, 'UTF-8', fields,
QgsWkbTypes.Point, crs, 'ESRI Shapefile'
)
writer.addFeature(feat)
del writer # C++ destructor → flush + close
⚠️ სუსტი მხარე: გამონაკლისის შემთხვევაში
del-ამდე კოდი შეჩერდება დაdelარ შესრულდება.
პატერნი 2 — try / finally (გარანტირებული)¶
writer = QgsVectorFileWriter.create(
shapefile_path, fields, QgsWkbTypes.Point, crs,
QgsProject.instance().transformContext(), options
)
try:
for feat in features:
writer.addFeature(feat)
print(f"✅ {len(features)} feature ჩაიწერა")
except Exception as e:
print(f"❌ შეცდომა: {e}")
finally:
del writer # ← ყოველთვის სრულდება — გამონაკლისის შემთხვევაშიც!
print("🔒 C++ destructor გამოძახდა — ფაილი დახურულია")
✅
finally— Python-ის გარანტია: ყოველთვის სრულდება, გამონაკლისის მიუხედავად.
პატერნი 3 — contextmanager (with სტილი)¶
from contextlib import contextmanager
from qgis.core import QgsVectorFileWriter, QgsProject
@contextmanager
def vector_writer(path, fields, geom_type, crs):
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = "ESRI Shapefile"
options.fileEncoding = "UTF-8"
writer = QgsVectorFileWriter.create(
path, fields, geom_type, crs,
QgsProject.instance().transformContext(), options
)
if writer.hasError() != QgsVectorFileWriter.NoError:
raise RuntimeError(writer.errorMessage())
try:
yield writer # Python-ს writer-ს გადასცემს
finally:
del writer # C++ destructor — გარანტირებული!
# გამოყენება:
with vector_writer(path, fields, QgsWkbTypes.Point, crs) as writer:
for feat in features:
writer.addFeature(feat)
# ← with ბლოკი დასრულდა → del ავტომატურად
პატერნი 4 — writeAsVectorFormatV3 (del საჭირო არ არის)¶
# ამ შემთხვევაში writer C++ ფუნქციის შიდა scope-შია
# → C++-ის ავტომატური destructor მუშაობს → del არ გვჭირდება
error, _, _, _ = QgsVectorFileWriter.writeAsVectorFormatV3(
layer, path,
QgsProject.instance().transformContext(),
options
)
if error == QgsVectorFileWriter.NoError:
print("✅ ექსპორტი — del საჭირო არ იყო")
პატერნების შედარება¶
┌──────────────────┬───────────────┬──────────────┬──────────────────┐
│ პატერნი │ GC გარანტია │ კოდის სიწმინ │ გამოძახება │
├──────────────────┼───────────────┼──────────────┼──────────────────┤
│ del writer │ ❌ try გარეშე │ ✅ მარტივი │ ხელით │
│ try / finally │ ✅ გარანტია │ 🔶 საშუალო │ ხელით finally-ში │
│ contextmanager │ ✅ გარანტია │ ✅ სუფთა │ ავტომატური │
│ writeAsVectorV3 │ ✅ C++ auto │ ✅ სუფთა │ C++ ავტომატური │
└──────────────────┴───────────────┴──────────────┴──────────────────┘
🔍 სხვა PyQGIS კლასები რომლებიც Cleanup საჭიროებს¶
QgsVectorFileWriter ერთადერთი კლასი არ არის, სადაც C++ რესურსების ხელით გათავისუფლება მნიშვნელოვანია:
QgsDataProvider — მონაცემთა წყარო¶
# ✅ QgsVectorLayer-ის წაშლა provider-საც ხურავს
layer = QgsVectorLayer(path, "Layer", "ogr")
QgsProject.instance().removeMapLayer(layer.id())
# ← C++ QgsOgrProvider destructor → OGR file handle დახურვა
QgsFeatureIterator — iterator-ის დახურვა¶
# ⚠️ დიდ შრეებში iterator-ი C++ კავშირს ინახავს
it = layer.getFeatures()
for feat in it:
if feat['ID'] == target_id:
break # ← Iterator გაჩერდა, მაგრამ C++ კავშირი ღიაა!
# ✅ ხელით დახურვა
it.close() # ← C++ destructor → DB cursor / file handle დახურვა
QgsRasterLayer — რასტრული ფაილი¶
raster = QgsRasterLayer(tiff_path, "DEM", "gdal")
# GDAL dataset-ი C++ დონეზე ღიაა
# QgsProject-იდან წაშლა → C++ GDAL destructor
QgsProject.instance().removeMapLayer(raster.id())
QgsTransaction — ბაზის ტრანზაქცია¶
from qgis.core import QgsTransaction
transaction = QgsTransaction.create(layer)
transaction.begin()
try:
layer.startEditing()
# ... ცვლილებები ...
transaction.commit()
except:
transaction.rollback() # ← C++ კავშირის გათავისუფლება
finally:
del transaction # ← C++ destructor → DB connection cleanup
📌 შეჯამება¶
C++ Destructor
│
├── ავტომატურია C++-ში — scope-ის ბოლოს
│
└── PyQGIS-ში — SIP wrapper-ის გამო ავტომატური არ არის
│
├── del writer → Python ref=0 → SIP → C++ destructor
├── try/finally → გარანტია გამონაკლისის შემთხვევაშიც
├── contextmanager → with ბლოკი → ავტომატური del
└── writeAsVectorFormatV3 → C++ scope → ავტომატური
| Python ბრძანება | C++ ეფექტი |
|---|---|
writer = QgsVectorFileWriter(...) |
QgsVectorFileWriter() კონსტრუქტორი — ფაილი იხსნება |
writer.addFeature(feat) |
ბუფერში ჩაწერა — დისკზე ჯერ არ არის |
del writer |
~QgsVectorFileWriter() — flush + close + unlock |
- C++ დესტრუქტორი = კლასის cleanup მეთოდი,
~ClassName()სინტაქსით - SIP Wrapper = Python-ის "გარსი" C++ ობიექტის გარშემო — ორ-ფენიანი სამყარო
del= Python reference-ს წყვეტს → SIP → C++ destructor გამოძახებაtry/finally= გარანტირებული cleanup — შეცდომის შემთხვევაშიც- Python GC ≠ C++ Destructor — GC non-deterministic, destructor deterministic
👉 წესი: QgsVectorFileWriter ან სხვა C++ რესურსი-მქონე ობიექტი — ყოველთვის del, try/finally, ან with პატერნით დაასრულე.