2010-10-05 10 views
5

Uważam, że czytelność i zasada KISS są najważniejszymi czynnikami w programowaniu. Dlatego używam Python :)
A oto dokładny sytuacja, którą spotykamy się bardzo często:Przejście z małych skryptów do większych aplikacji nie jest łatwe

powiedzieć, że mają ładny i czysty skrypt, który jest opakowaniem do obsługi bazy danych:

import database_schema as schema 
loader = schema.Loader("sqlite:///var/database.db") 
session = loader.session 

def addUser(name, full_name, password): 
    user = schema.User(name, full_name, password) 
    session.add(user) 
    session.commit() 

def listUsers(): 
    all_users = session.query(schema.User).all() 
    return all_users 

który jest używany tak:

import database 
database.addUser("mike", "Mike Driscoll", "password") 
database.listUsers() 

w pewnym momencie chcę przepisać ten moduł, tak aby mógł on pracować z bazami danych na inną ścieżkę (dla testów jednostkowych, na przykład).

Jakie są więc moje opcje?

  1. Najbardziej intuicyjną rzeczą jest dodanie zmiennej database_path == "", a następnie ... co? Ustawienie go za pomocą funkcji setPath(new_path), a następnie dodanie wyjątku (if database_path == "": raise SomeException) do każdej funkcji, jest po prostu brzydkie i nie powinno być wykonywane przez nikogo.

  2. W pełni funkcjonalna klasa z ustawianiem self._database_path w czasie inicjalizacji.

który jest następnie używany w ten sposób:

from database import Database 
database = Database("sqlite:///var/database.db") 
database.addUser("mike", "Mike Driscoll", "password") 
database.listUsers() 

To już kolejne linie kodu niż w pierwszym przykładzie, i dodanie problemu nazewnictwa: posiadanie klasy o nazwie Database w module database jest głupi, nie?

przepraszam za długi czytać, tutaj moje ostatnie pytania:

  1. Dlaczego nie ma czegoś takiego jak __init__ funkcje modułów, w Pythonie?
  2. Czy brakuje mi czegoś (statyczne zmienne klasy itp.), Które może zrobić to, co chcę (stały zbiór w czasie importu) w łatwy i czysty sposób, który wciąż nie jest daleko od modułu z wieloma prostymi funkcje, które były na początku?

P.S. przepraszam za mój angielski.

Edit:

Tak, aby to jasne, jak ten kod może wyglądać w mojej wyobraźni wszechświata Python:

import database_schema as schema 

def __init__(database_path): 
    loader = schema.Loader(database_path) 
    global session 
    session = loader.session 

def addUser(name, full_name, password): 
    user = schema.User(name, full_name, password) 
    session.add(user) 
    session.commit() 

def listUsers(): 
    all_users = session.query(schema.User).all() 
    return all_users 

i używane tak:

import database("sqlite:///var/database.db") 

database.addUser("mike", "Mike Driscoll", "password") 
database.listUsers() 

Odpowiedz

2

Moduł to obiekt w języku Python z arbitralnie nazwanymi atrybutami, które można powiązać i odwoływać się. Kod Pythona dla modułu o nazwie mod zwykle znajduje się w pliku o nazwie mod.py. Podczas próby zaimportowania tworzony jest nowy obszar nazw zawierający wszystkie atrybuty tego modułu.

Mimo wszystko, co powiedziane, nie jest to to samo, co klasa i tworzenie instancji obiektów tej klasy. Są to różne abstrakcje i powinny być używane jako takie.

zamiast testów na

if database_path == "": 
    .... 

zrobić pythonic sposób

if database_path: 
    .... 

I zamiast podniesienia wyjątek, można użyć dochodzić

assert database_path != "", 'database path empty' 

Moduł robi nie istnieją w smakach takich jak instancje obiektów klasy. Importowanie modułu spowoduje utworzenie przestrzeni nazw o tym samym zestawie atrybutów za każdym razem, gdy zostanie zaimportowany. W takiej sytuacji init może nie mieć większego sensu.

Nie ma nic złego w drugiej formie kodu, który podałeś. a jeśli nie chce tego robić im niektóre z powyższych idiomów może złagodzić ból :)

+0

Dzięki za porady, za mało reputacji, aby się uporać. Nadal nie do końca rozumiem, dlaczego niemożliwe jest posiadanie funkcji uruchamianej zaraz po zaimportowaniu modułu (patrz aktualizacja oryginalnego wpisu). I nadal uważam, że źle jest pamiętać, aby robić to ręcznie (wywołanie metody setPath), a także umieścić coś tak powtarzalnego, jak "asercja ścieżka_bazy_danych", ścieżka_do bazy danych pusta "" w każdą funkcję używającą tej zmiennej. – kolobos

+0

@kolobos Nie mam w tej chwili tłumacza, ale czy nie jest to kod w module _wykonywany_ kiedy moduł jest importowany? Oznacza to, że twoje wewnętrzne instrukcje "__init__" będą działać globalnie. – mlvljr

+0

@mlvljr Masz rację, wszystko na "poziomie globalnym" jest wykonywane po zaimportowaniu modułu. Pytanie tutaj brzmi: jak przekazać ścieżkę do bazy danych do modułu i jak to zrobić w najbardziej "prawidłowy" i elegancki sposób. Jak widać w mojej odpowiedzi, wydaje się, że przejście na zajęcia jest tylko drogą. – kolobos

0

Jedynym rozwiązaniem, które znalazłem to, aby załadować konfigurację z pliku:

import ConfigParser 
config = ConfigParser.RawConfigParser() 
config.read('config.ini') 

def getSession(): 
    loader = schema.Loader(config.get('Global', 'database')) 
    return(loader.session) 
... 

Która jest not perfect: nazwa samego pliku jest zakodowana na stałe, a jeśli chcesz wykonać testy jednostkowe na innej (na przykład pustej) bazie danych, musisz zmienić przypisanie konfiguracji, a następnie przywrócić ją z powrotem, gdy skończysz, niezbyt elegancko.

1

W tego typu sytuacjach używam opcjonalnego parametru.

def addUser(name, full_name, password, _loader=None): 
    user = schema.User(name, full_name, password) 
    if (_loader is None): 
     # Use global values. 
     session.add(user) 
     session.commit() 
    else: 
     _session = _loader.session 
     # ... 

Ja sam, z dala od inicjowania takich rzeczy podczas ładowania modułu. Wyobraź sobie, że chcesz utworzyć dokumentację za pomocą narzędzi takich jak epydoc. W tym kontekście nie ma sensu tworzenie połączenia tylko dlatego, że ładuje moduł. Na pewno pójdę z podejściem klasowym.

+0

+1 za wspomnienie o problemach związanych z dokumentacją. I tak, klasa wydaje się być jedynym właściwym sposobem robienia rzeczy, tylko zastanawiałem się, dlaczego nie ma nic pomiędzy "modułem z grupą funkcji" i "w pełni funkcjonalną klasą". I właśnie znalazłem się zmuszony do załadowania silnika bazy danych w czasie importu, kiedy używam rozszerzenia SQLAlchemy w deklaracji, ale to kolejne pytanie. – kolobos

Powiązane problemy