بر ویژگی های ترکیبی SQLAlchemy مسلط شوید تا صفات محاسبه شده را برای مدل های داده ای رسا تر و قابل نگهداری تر ایجاد کنید. با مثال های عملی بیاموزید.
ویژگی های ترکیبی پایتون SQLAlchemy: صفات محاسبه شده برای مدل سازی قدرتمند داده ها
SQLAlchemy، یک جعبه ابزار SQL پایتون قدرتمند و انعطاف پذیر و Object-Relational Mapper (ORM)، مجموعه غنی از ویژگی ها را برای تعامل با پایگاه های داده ارائه می دهد. در میان این ها، ویژگی های ترکیبی به عنوان یک ابزار به ویژه مفید برای ایجاد صفات محاسبه شده در مدل های داده شما برجسته می شوند. این مقاله یک راهنمای جامع برای درک و استفاده از ویژگی های ترکیبی SQLAlchemy ارائه می دهد و به شما امکان می دهد برنامه های رسا تر، قابل نگهداری تر و کارآمدتر بسازید.
ویژگی های ترکیبی SQLAlchemy چیست؟
یک ویژگی ترکیبی، همانطور که از نامش پیداست، نوع خاصی از ویژگی در SQLAlchemy است که می تواند بسته به زمینه ای که در آن دسترسی می شود، متفاوت عمل کند. این به شما امکان می دهد تا یک ویژگی را تعریف کنید که می تواند مستقیماً در یک نمونه از کلاس شما (مانند یک ویژگی معمولی) قابل دسترسی باشد یا در عبارات SQL (مانند یک ستون) استفاده شود. این با تعریف توابع جداگانه برای دسترسی در سطح نمونه و سطح کلاس به دست می آید.
در اصل، ویژگی های ترکیبی راهی برای تعریف صفات محاسبه شده ارائه می دهند که از سایر ویژگی های مدل شما مشتق شده اند. این صفات محاسبه شده می توانند در پرس و جوها استفاده شوند و همچنین می توانند مستقیماً در نمونه های مدل شما قابل دسترسی باشند و یک رابط کاربری سازگار و شهودی ارائه دهند.
چرا از ویژگی های ترکیبی استفاده کنیم؟
استفاده از ویژگی های ترکیبی چندین مزیت دارد:
- رسایی: آنها به شما امکان می دهند روابط و محاسبات پیچیده را مستقیماً در مدل خود بیان کنید، و کد شما را خواناتر و درک آن را آسان تر می کند.
- قابلیت نگهداری: با کپسوله کردن منطق پیچیده در ویژگی های ترکیبی، تکرار کد را کاهش می دهید و قابلیت نگهداری برنامه خود را بهبود می بخشید.
- کارایی: ویژگی های ترکیبی به شما امکان می دهند محاسبات را مستقیماً در پایگاه داده انجام دهید، و میزان داده ای را که باید بین برنامه شما و سرور پایگاه داده منتقل شود، کاهش می دهید.
- سازگاری: آنها یک رابط کاربری سازگار برای دسترسی به صفات محاسبه شده ارائه می دهند، صرف نظر از اینکه با نمونه های مدل خود کار می کنید یا پرس و جوهای SQL می نویسید.
مثال اساسی: نام کامل
بیایید با یک مثال ساده شروع کنیم: محاسبه نام کامل یک شخص از نام و نام خانوادگی او.
تعریف مدل
ابتدا، یک مدل `Person` ساده با ستون های `first_name` و `last_name` تعریف می کنیم.
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.hybrid import hybrid_property
Base = declarative_base()
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
def __repr__(self):
return f""
engine = create_engine('sqlite:///:memory:') # پایگاه داده درون حافظه برای مثال
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
ایجاد ویژگی ترکیبی
اکنون، یک ویژگی ترکیبی `full_name` اضافه می کنیم که نام و نام خانوادگی را به هم متصل می کند.
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def full_name(self):
return f"{self.first_name} {self.last_name}"
def __repr__(self):
return f""
در این مثال، دکوراتور `@hybrid_property` متد `full_name` را به یک ویژگی ترکیبی تبدیل می کند. وقتی به `person.full_name` دسترسی پیدا می کنید، کد داخل این متد اجرا می شود.
دسترسی به ویژگی ترکیبی
بیایید مقداری داده ایجاد کنیم و ببینیم چگونه به ویژگی `full_name` دسترسی پیدا کنیم.
person1 = Person(first_name='Alice', last_name='Smith')
person2 = Person(first_name='Bob', last_name='Johnson')
session.add_all([person1, person2])
session.commit()
print(person1.full_name) # خروجی: Alice Smith
print(person2.full_name) # خروجی: Bob Johnson
استفاده از ویژگی ترکیبی در پرس و جوها
قدرت واقعی ویژگی های ترکیبی زمانی آشکار می شود که از آنها در پرس و جوها استفاده می کنید. ما می توانیم بر اساس `full_name` فیلتر کنیم، انگار که یک ستون معمولی است.
people_with_smith = session.query(Person).filter(Person.full_name == 'Alice Smith').all()
print(people_with_smith) # خروجی: []
با این حال، مثال بالا فقط برای بررسی های برابری ساده کار می کند. برای عملیات پیچیده تر در پرس و جوها (مانند `LIKE`)، باید یک تابع عبارت تعریف کنیم.
تعریف توابع عبارت
برای استفاده از ویژگی های ترکیبی در عبارات SQL پیچیده تر، باید یک تابع عبارت تعریف کنید. این تابع به SQLAlchemy می گوید که چگونه ویژگی ترکیبی را به یک عبارت SQL ترجمه کند.
بیایید مثال قبلی را تغییر دهیم تا از پرس و جوهای `LIKE` در ویژگی `full_name` پشتیبانی کنیم.
from sqlalchemy import func
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def full_name(self):
return f"{self.first_name} {self.last_name}"
@full_name.expression
def full_name(cls):
return func.concat(cls.first_name, ' ', cls.last_name)
def __repr__(self):
return f""
در اینجا، دکوراتور `@full_name.expression` را اضافه کردیم. این یک تابع را تعریف می کند که کلاس (`cls`) را به عنوان یک آرگومان می گیرد و یک عبارت SQL را برمی گرداند که نام و نام خانوادگی را با استفاده از تابع `func.concat` به هم متصل می کند. `func.concat` یک تابع SQLAlchemy است که تابع الحاق پایگاه داده را نشان می دهد (به عنوان مثال، `||` در SQLite، `CONCAT` در MySQL و PostgreSQL).
اکنون می توانیم از پرس و جوهای `LIKE` استفاده کنیم:
people_with_smith = session.query(Person).filter(Person.full_name.like('%Smith%')).all()
print(people_with_smith) # خروجی: []
تنظیم مقادیر: تنظیم کننده
ویژگی های ترکیبی همچنین می توانند تنظیم کننده داشته باشند، که به شما امکان می دهد ویژگی های اساسی را بر اساس یک مقدار جدید به روز کنید. این کار با استفاده از دکوراتور `@full_name.setter` انجام می شود.
بیایید یک تنظیم کننده به ویژگی `full_name` خود اضافه کنیم که نام کامل را به نام و نام خانوادگی تقسیم می کند.
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def full_name(self):
return f"{self.first_name} {self.last_name}"
@full_name.expression
def full_name(cls):
return func.concat(cls.first_name, ' ', cls.last_name)
@full_name.setter
def full_name(self, full_name):
parts = full_name.split()
self.first_name = parts[0]
self.last_name = ' '.join(parts[1:]) if len(parts) > 1 else ''
def __repr__(self):
return f""
اکنون می توانید ویژگی `full_name` را تنظیم کنید و ویژگی های `first_name` و `last_name` را به روز می کند.
person = Person(first_name='Alice', last_name='Smith')
session.add(person)
session.commit()
person.full_name = 'Charlie Brown'
print(person.first_name) # خروجی: Charlie
print(person.last_name) # خروجی: Brown
session.commit()
حذف کننده ها
مشابه تنظیم کننده ها، می توانید یک حذف کننده برای یک ویژگی ترکیبی با استفاده از دکوراتور `@full_name.deleter` تعریف کنید. این به شما امکان می دهد تعریف کنید که وقتی سعی می کنید `del person.full_name` چه اتفاقی می افتد.
برای مثال ما، بیایید حذف نام کامل را برای پاک کردن نام و نام خانوادگی روشن کنیم.
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def full_name(self):
return f"{self.first_name} {self.last_name}"
@full_name.expression
def full_name(cls):
return func.concat(cls.first_name, ' ', cls.last_name)
@full_name.setter
def full_name(self, full_name):
parts = full_name.split()
self.first_name = parts[0]
self.last_name = ' '.join(parts[1:]) if len(parts) > 1 else ''
@full_name.deleter
def full_name(self):
self.first_name = None
self.last_name = None
def __repr__(self):
return f""
person = Person(first_name='Charlie', last_name='Brown')
session.add(person)
session.commit()
del person.full_name
print(person.first_name) # خروجی: None
print(person.last_name) # خروجی: None
session.commit()
مثال پیشرفته: محاسبه سن از تاریخ تولد
بیایید یک مثال پیچیده تر را در نظر بگیریم: محاسبه سن یک شخص از تاریخ تولد او. این قدرت ویژگی های ترکیبی را در رسیدگی به تاریخ ها و انجام محاسبات نشان می دهد.
اضافه کردن ستون تاریخ تولد
ابتدا، یک ستون `date_of_birth` به مدل `Person` خود اضافه می کنیم.
from sqlalchemy import Date
import datetime
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
date_of_birth = Column(Date)
# ... (کد قبلی)
محاسبه سن با یک ویژگی ترکیبی
اکنون ویژگی ترکیبی `age` را ایجاد می کنیم. این ویژگی سن را بر اساس ستون `date_of_birth` محاسبه می کند. ما باید موردی را که `date_of_birth` `None` است، مدیریت کنیم.
from sqlalchemy import Date
import datetime
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
date_of_birth = Column(Date)
@hybrid_property
def age(self):
if self.date_of_birth:
today = datetime.date.today()
age = today.year - self.date_of_birth.year - ((today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day))
return age
return None # یا یک مقدار پیش فرض دیگر
@age.expression
def age(cls):
today = datetime.date.today()
return func.cast(func.strftime('%Y', 'now') - func.strftime('%Y', cls.date_of_birth) - (func.strftime('%m-%d', 'now') < func.strftime('%m-%d', cls.date_of_birth)), Integer)
# ... (کد قبلی)
ملاحظات مهم:
- توابع تاریخ مخصوص پایگاه داده: تابع عبارت از `func.strftime` برای محاسبات تاریخ استفاده می کند. این تابع مخصوص SQLite است. برای سایر پایگاه های داده (مانند PostgreSQL یا MySQL)، باید از توابع تاریخ مخصوص پایگاه داده مناسب استفاده کنید (به عنوان مثال، `EXTRACT` در PostgreSQL، `YEAR` و `MAKEDATE` در MySQL).
- تبدیل نوع: ما از `func.cast` برای تبدیل نتیجه محاسبه تاریخ به یک عدد صحیح استفاده می کنیم. این اطمینان حاصل می کند که ویژگی `age` یک مقدار عدد صحیح را برمی گرداند.
- مناطق زمانی: هنگام کار با تاریخ ها، مراقب مناطق زمانی باشید. اطمینان حاصل کنید که تاریخ های شما در یک منطقه زمانی سازگار ذخیره و مقایسه می شوند.
- رسیدگی به مقادیر `None`: ویژگی باید مواردی را که `date_of_birth` `None` است، برای جلوگیری از خطاها مدیریت کند.
استفاده از ویژگی سن
person1 = Person(first_name='Alice', last_name='Smith', date_of_birth=datetime.date(1990, 1, 1))
person2 = Person(first_name='Bob', last_name='Johnson', date_of_birth=datetime.date(1985, 5, 10))
session.add_all([person1, person2])
session.commit()
print(person1.age) # خروجی: (بر اساس تاریخ فعلی و تاریخ تولد)
print(person2.age) # خروجی: (بر اساس تاریخ فعلی و تاریخ تولد)
people_over_30 = session.query(Person).filter(Person.age > 30).all()
print(people_over_30) # خروجی: (افراد بالای 30 سال بر اساس تاریخ فعلی)
مثال های پیچیده تر و موارد استفاده
محاسبه مجموع سفارشات در یک برنامه تجارت الکترونیک
در یک برنامه تجارت الکترونیک، ممکن است یک مدل `Order` با یک رابطه به مدل های `OrderItem` داشته باشید. می توانید از یک ویژگی ترکیبی برای محاسبه ارزش کل یک سفارش استفاده کنید.
from sqlalchemy import ForeignKey, Float
from sqlalchemy.orm import relationship
class Order(Base):
__tablename__ = 'orders'
id = Column(Integer, primary_key=True)
items = relationship("OrderItem", back_populates="order")
@hybrid_property
def total(self):
return sum(item.price * item.quantity for item in self.items)
@total.expression
def total(cls):
return session.query(func.sum(OrderItem.price * OrderItem.quantity)).\
filter(OrderItem.order_id == cls.id).scalar_subquery()
class OrderItem(Base):
__tablename__ = 'order_items'
id = Column(Integer, primary_key=True)
order_id = Column(Integer, ForeignKey('orders.id'))
order = relationship("Order", back_populates="items")
price = Column(Float)
quantity = Column(Integer)
این مثال یک تابع عبارت پیچیده تر را نشان می دهد که از یک زیر پرس و جو برای محاسبه کل به طور مستقیم در پایگاه داده استفاده می کند.
محاسبات جغرافیایی
اگر با داده های جغرافیایی کار می کنید، می توانید از ویژگی های ترکیبی برای محاسبه فاصله بین نقاط یا تعیین اینکه آیا یک نقطه در یک منطقه خاص قرار دارد یا خیر استفاده کنید. این اغلب شامل استفاده از توابع جغرافیایی خاص پایگاه داده (به عنوان مثال، توابع PostGIS در PostgreSQL) است.
from geoalchemy2 import Geometry
from sqlalchemy import cast
class Location(Base):
__tablename__ = 'locations'
id = Column(Integer, primary_key=True)
name = Column(String)
coordinates = Column(Geometry(geometry_type='POINT', srid=4326))
@hybrid_property
def latitude(self):
if self.coordinates:
return self.coordinates.x
return None
@latitude.expression
def latitude(cls):
return cast(func.ST_X(cls.coordinates), Float)
@hybrid_property
def longitude(self):
if self.coordinates:
return self.coordinates.y
return None
@longitude.expression
def longitude(cls):
return cast(func.ST_Y(cls.coordinates), Float)
این مثال به پسوند `geoalchemy2` نیاز دارد و فرض می کند که از یک پایگاه داده با PostGIS فعال استفاده می کنید.
بهترین شیوه ها برای استفاده از ویژگی های ترکیبی
- ساده نگه دارید: از ویژگی های ترکیبی برای محاسبات نسبتاً ساده استفاده کنید. برای منطق پیچیده تر، استفاده از توابع یا متدهای جداگانه را در نظر بگیرید.
- از انواع داده مناسب استفاده کنید: اطمینان حاصل کنید که انواع داده استفاده شده در ویژگی های ترکیبی شما با پایتون و SQL سازگار هستند.
- عملکرد را در نظر بگیرید: در حالی که ویژگی های ترکیبی می توانند با انجام محاسبات در پایگاه داده عملکرد را بهبود بخشند، نظارت بر عملکرد پرس و جوهای خود و بهینه سازی آنها در صورت نیاز ضروری است.
- به طور کامل آزمایش کنید: ویژگی های ترکیبی خود را به طور کامل آزمایش کنید تا مطمئن شوید که در همه زمینه ها نتایج صحیح را تولید می کنند.
- کد خود را مستند کنید: به وضوح ویژگی های ترکیبی خود را مستند کنید تا توضیح دهید که چه کاری انجام می دهند و چگونه کار می کنند.
اشتباهات رایج و نحوه اجتناب از آنها
- توابع مخصوص پایگاه داده: اطمینان حاصل کنید که توابع عبارت شما از توابع مستقل از پایگاه داده استفاده می کنند یا پیاده سازی های خاص پایگاه داده را برای جلوگیری از مشکلات سازگاری ارائه می دهند.
- توابع عبارت نادرست: دوباره بررسی کنید که توابع عبارت شما به درستی ویژگی ترکیبی شما را به یک عبارت SQL معتبر ترجمه می کنند.
- گلوگاه های عملکرد: از استفاده از ویژگی های ترکیبی برای محاسباتی که بیش از حد پیچیده یا پرهزینه هستند خودداری کنید، زیرا این می تواند منجر به گلوگاه های عملکرد شود.
- نام های متناقض: از استفاده از یک نام برای ویژگی ترکیبی خود و یک ستون در مدل خود خودداری کنید، زیرا این می تواند منجر به سردرگمی و خطا شود.
ملاحظات بین المللی سازی
هنگام کار با ویژگی های ترکیبی در برنامه های بین المللی شده، موارد زیر را در نظر بگیرید:
- فرمت های تاریخ و زمان: از فرمت های تاریخ و زمان مناسب برای مناطق مختلف استفاده کنید.
- فرمت های اعداد: از فرمت های اعداد مناسب برای مناطق مختلف، از جمله جداکننده های اعشاری و جداکننده های هزاران استفاده کنید.
- فرمت های ارز: از فرمت های ارز مناسب برای مناطق مختلف، از جمله نمادهای ارز و اعشار استفاده کنید.
- مقایسه های رشته ای: از توابع مقایسه رشته ای آگاه از محلی استفاده کنید تا اطمینان حاصل شود که رشته ها به درستی در زبان های مختلف مقایسه می شوند.
به عنوان مثال، هنگام محاسبه سن، فرمت های مختلف تاریخ مورد استفاده در سراسر جهان را در نظر بگیرید. در برخی مناطق، تاریخ به صورت `MM/DD/YYYY` نوشته می شود، در حالی که در مناطق دیگر `DD/MM/YYYY` یا `YYYY-MM-DD` است. اطمینان حاصل کنید که کد شما تاریخ ها را به درستی در همه قالب ها تجزیه می کند.
هنگام به هم پیوستن رشته ها (مانند مثال `full_name`)، از تفاوت های فرهنگی در ترتیب نام آگاه باشید. در برخی فرهنگ ها، نام خانوادگی قبل از نام داده شده می آید. ارائه گزینه هایی برای کاربران برای سفارشی کردن فرمت نمایش نام را در نظر بگیرید.
نتیجه گیری
ویژگی های ترکیبی SQLAlchemy یک ابزار قدرتمند برای ایجاد صفات محاسبه شده در مدل های داده شما هستند. آنها به شما امکان می دهند روابط و محاسبات پیچیده را مستقیماً در مدل های خود بیان کنید، و خوانایی، قابلیت نگهداری و کارایی کد را بهبود می بخشد. با درک نحوه تعریف ویژگی های ترکیبی، توابع عبارت، تنظیم کننده ها و حذف کننده ها، می توانید از این ویژگی برای ساخت برنامه های پیچیده تر و قوی تر استفاده کنید.
با پیروی از بهترین شیوه های ذکر شده در این مقاله و اجتناب از اشتباهات رایج، می توانید به طور موثر از ویژگی های ترکیبی برای افزایش مدل های SQLAlchemy خود و ساده سازی منطق دسترسی به داده های خود استفاده کنید. به یاد داشته باشید که جنبه های بین المللی سازی را در نظر بگیرید تا اطمینان حاصل شود که برنامه شما به درستی برای کاربران در سراسر جهان کار می کند. با برنامه ریزی و پیاده سازی دقیق، ویژگی های ترکیبی می توانند به بخشی ارزشمند از جعبه ابزار SQLAlchemy شما تبدیل شوند.