2010/11/01

如何取得相對於某個 .py 檔的絕對路徑

Java 的 Class 提供有一個 getResource(name) 方法,可以用相對某 .class 檔的位置來解讀 name 所表示的相對路徑。

在 Python 裡也有類似的需求,為了程式的可攜性,需要以相對於 .py 檔的方式來描述某個外部檔的位置。假設下面的目錄結構:

/tmp$ tree a
a/
|-- a.txt <-- 內容為 "content of a.txt"
`-- b/
    |-- c/
    |-- mod.py
    `-- othermod.py

2 directories, 2 files

/tmp/a/b/mod.py 會將 /tmp/a.txt 的內容印容印出。它的內容如下:

import os
from os.path import dirname, abspath, join

# 取得 mod.py 所在的路徑 (相對於 current working directory)
mod_dir = dirname(__file__)
# 不直接寫成 '../a.txt' 是為了不要跟 OS 相依
a_pathname = abspath(join(mod_dir, '..', 'a.txt'))

print 'CWD:', os.getcwd()
print open(a_pathname, 'r').read()

不論從哪個位置執行 mod.py (會導致 current working directory 起變化),都能正常地印出 /tmp/a/a.txt 的內容:

/tmp$ python a/b/mod.py
CWD: /tmp
content of a.txt
/tmp$ cd a/b/c
/tmp/a/b/c$ python ../mod.py
CWD: /tmp/a/b/c
content of a.txt

當然也可以藉由其他 module 來定位:

import os, othermod
from os.path import dirname, abspath, join

mod_dir = dirname(othermod.__file__) # 取得 othermod.py 所在的路徑
a_pathname = abspath(join(mod_dir, '..', 'a.txt'))

print 'CWD:', os.getcwd()
print open(a_pathname, 'r').read()

根據上面的實驗結果,我們可以寫一個 abspath(relpath, mod) 來模擬 Java 中 Class.getResource(name) 的行為,也可以做為 os.path.abspath(path) 的擴充。

import os

def abspath(relpath, mod):
    # 呼叫端不用擔心不同 OS 的分隔字元
    relpath = relpath.replace('/', os.sep).replace('\\', os.sep)
    if mod is None: return os.path.abspath(relpath)

    base_dir = os.path.dirname(mod.__file__)
    pathname = os.path.join(base_dir, relpath)
    return os.path.abspath(pathname)
Tip
可以用 sys.modules[__name__] 來取得 current module.

這裡提到的技巧,也很適合用在執行期動態配置 sys.path

參考資料:

2008/03/22

[HiveMind] How to share the same implementation or instance between different service interfaces

HiveMind is a great DI/IoC framework, why there is no anymore developements?

Anyway, the question is different from The ability to specify multiple interfaces of a service, but it is suitable when customization is required.

Assumed that interface package.ExtendedService extends package.GeneralService, if you want service GeneralService and ExtendedService to share the same implementation (or instance), you can create a service factory called DelegateFactory:

DelegateFactory declaration:

<service-point id="DelegateFactory" interface="org.apache.hivemind.ServiceImplementationFactory">
<parameters-schema>
<element name="delegate">
<attribute name="service-id" required="true" translator="service" />
<rules>
<create-object class="package.hivemind.impl.DelegateParameters" />
<read-attribute attribute="service-id" property="delegate" />
<invoke-parent method="addElement" />
</rules>
</element>
</parameters-schema>
<create-instance class="package.hivemind.impl.DelegateFactory" model="primitive" />
</service-point>


DelegateFactory source:

package package.hivemind.impl;

import org.apache.hivemind.ServiceImplementationFactory;
import org.apache.hivemind.ServiceImplementationFactoryParameters;

public class DelegateFactory implements ServiceImplementationFactory {

@Override
public Object createCoreServiceImplementation(ServiceImplementationFactoryParameters factoryParameters) {
DelegateParameters parameters = (DelegateParameters)factoryParameters.getFirstParameter();
return parameters.getDelegate();
}

}


DelegateParameters source

package package.hivemind.impl;

public class DelegateParameters {

private Object delegate;

public Object getDelegate() {
return delegate;
}

public void setDelegate(Object delegate) {
this.delegate = delegate;
}

}


After that, we can simply use DelegateFactory to delegate all requests for GeneralService service to ExtendedService service, share the same implementation or instance.

Usage:

<service-point id="GeneralService" interface="package.GeneralService">
<invoke-factory service-id="DelegateFactory">
<delegate service-id="ExtendedService" />
</invoke-factory>
</service-point>

<service-point id="ExtendedService" interface="package.ExtendedService">
<invoke-factory>
<!-- where ExtendedServiceImpl extends GeneralServiceImpl -->
<construct class="package.ExtendedServiceImpl" />
</invoke-factory>
</service-point>

2008/01/16

Why month in Java Date/Calendar is zero-based

以前從來沒有懷疑過為什麼 Java API 裡 java.util.Datejava.util.Calendar 的月份會是 zero-based. 以下這些資料給了答案: 因為當初, 所以現在...



這或許不是什麼太大的問題 (雖然這是個不該犯的錯), 反正只要記住月份跟別人不一樣, "規定" 是 zero-based 就好了... 直到自己想要開發一些跟日期時間有關的 library 時, 我遇到了與 JavaSoft 當初由 Date 提出 Calendar 時一樣的掙札 - 我的 API 要採 zero-based 或 one-based?

如果我採用 one-based, 就代表別人不能再用 Calendar 的常數來使用我的 API. 沒辦法, 只好跟著錯下去, 因此最後我還是決定一起淌這個混水, 採用 zero-based

這真是太可怕了, 不得不警惕自己在規劃 API 時一定要很謹慎, 因為 API 這種東西一旦對 caller 承諾了, 就難以收回... 真是一言既出、駟馬難追!!

2008/01/14

別小看圖檔格式!!

之前使用者上傳了一支由 Adobe Photoshop CS 所製作的 JPEG 檔, 透過 Servlet 輸出時, 結果無法正常地顯示在瀏覽器裡 (在 IE 裡會得到一個叉叉, 在 Firefox 下則會明確丟出 "... cannot be displayed, because it contains errors" 的錯誤訊息), 甚至引發了 Tomcat 上 "java.lang.IllegalStateException: getOutputStream() has already been called for this response" 的錯誤.

起初以為是程式寫法上的問題, 因為有許多人提到這個錯誤是 Tomcat 5 的問題, 經過了一番折騰之後, 問題竟是出在圖檔格式...

分析圖檔之後, 發現它的格式是 "JPEG Bitmap (JPG) YCbCrk", 跟我們一般在網路上看到的 "JPEG Bitmap (JPG) YCbCr" 不同. 從不知道 JPEG 檔還有 color space 的差別

這裡有提到 YCbCrK is a JPEG-based format that has been developed by Adobe, 不知道是不是從 Adobe 產品另存出來的 JPEG 檔都有這個問題, 那麼這個問題就有些嚴重了, 因為太多人以為只有 Photoshop 才能編修圖像 (殺雞焉用牛刀?), 我要怎麼跟使用者去解釋這些東西呢?

問題是解決了, 但我還是很納悶, 為什麼圖檔格式上的錯誤, 會引發伺服器後端的錯誤?