পাইথন: দুটি পরম পথের তুলনা করে আপেক্ষিক পথ পান


143

বলুন, আমার দুটি পরম পথ রয়েছে। আমার একটি পরীক্ষা করে দেখতে হবে যে কোনও একটি পথের দ্বারা উল্লেখ করা অবস্থানটি অন্যটির বংশধর। যদি সত্য হয় তবে আমার পূর্বপুরুষের কাছ থেকে বংশধরদের আপেক্ষিক পথটি খুঁজে নেওয়া দরকার। পাইথনে এটি প্রয়োগের একটি ভাল উপায় কী? আমি যে কোনও গ্রন্থাগার থেকে উপকৃত হতে পারি?

উত্তর:


167

os.path.commonprefix () এবং os.path.relpath () আপনার বন্ধু:

>>> print os.path.commonprefix(['/usr/var/log', '/usr/var/security'])
'/usr/var'
>>> print os.path.commonprefix(['/tmp', '/usr/var'])  # No common prefix: the root is the common prefix
'/'

আপনি এইভাবে পরীক্ষা করতে পারেন যে সাধারণ উপসর্গটি কোনও একটি পাথ কিনা, অর্থাত্ যদি কোনও পাথ একটি সাধারণ পূর্বপুরুষ:

paths = […, …, …]
common_prefix = os.path.commonprefix(list_of_paths)
if common_prefix in paths:
    

এরপরে আপনি আপেক্ষিক পাথগুলি খুঁজে পেতে পারেন:

relative_paths = [os.path.relpath(path, common_prefix) for path in paths]

এমনকি আপনি এই পদ্ধতির সাহায্যে দুটিরও বেশি পাথ পরিচালনা করতে পারেন এবং পরীক্ষা করতে পারেন যে সমস্ত পাথগুলি তার একটির নীচে রয়েছে।

PS : আপনার পাথগুলি কেমন দেখাচ্ছে তার উপর নির্ভর করে আপনি প্রথমে কিছুটা সাধারণীকরণ করতে চাইতে পারেন (এটি এমন পরিস্থিতিতে কার্যকর যেগুলি সর্বদা '/' দিয়ে শেষ হয় কিনা তা জানে না বা কিছু পথ আপেক্ষিক কিনা)। প্রাসঙ্গিক ফাংশনগুলির মধ্যে os.path.abspath () এবং os.path.normpath () অন্তর্ভুক্ত রয়েছে ।

পিপিএস : পিটার ব্রিগেস মন্তব্যগুলিতে যেমন উল্লেখ করেছেন, উপরে বর্ণিত সহজ পদ্ধতি ব্যর্থ হতে পারে:

>>> os.path.commonprefix(['/usr/var', '/usr/var2/log'])
'/usr/var'

যদিও /usr/varহয় না পাথ একটি সাধারণ উপসর্গ। কল commonprefix()করার আগে সমস্ত পাথ '/' দিয়ে শেষ করতে বাধ্য করা এই (নির্দিষ্ট) সমস্যার সমাধান করে।

পিপিপিএস : ব্লুএনোট 10 হিসাবে উল্লেখ করা হয়েছে, স্ল্যাশ যুক্ত করা সাধারণ সমস্যার সমাধান করে না। এখানে তার ফলোআপ প্রশ্নটি রয়েছে: পাইথনের os.path.commonprefix এর মিথ্যাচার কীভাবে রোধ করা যায়?

পিপিপিপিএস : পাইথন ৩.৪ দিয়ে শুরু করে আমাদের কাছে প্যাথলিব রয়েছে , একটি মডিউল যা স্যানার পাথ হেরফের পরিবেশ সরবরাহ করে। আমি অনুমান করি যে প্রতিটি পাথের সমস্ত উপসর্গ (সহ PurePath.parents()) পেয়ে, এই সমস্ত প্যারেন্ট সেটগুলিকে ছেদ করে এবং দীর্ঘতম সাধারণ উপসর্গটি নির্বাচন করে পাথের সেটগুলির সাধারণ উপসর্গ পাওয়া যায়।

পিপিপিপিপিএস : পাইথন 3.5 এই প্রশ্নের যথাযথ সমাধান প্রবর্তন করেছে: এটি os.path.commonpath()একটি বৈধ পথ ফেরায়


ঠিক আমার যা প্রয়োজন আপনার প্রম্পট উত্তরের জন্য ধন্যবাদ। একবারের সময় সীমাবদ্ধতা প্রত্যাহার করে আপনার উত্তর গ্রহণ করবে।
tamakisquare

10
commonprefixযেমন যত্ন সহকারে রাখুন , যেমন সাধারণ উপসর্গ /usr/var/logএবং /usr/var2/logহিসাবে প্রত্যাবর্তন করা হয় /usr/var- যা সম্ভবত আপনি প্রত্যাশা করতেন না। (বৈধ ডিরেক্টরি নয় এমন পাথ ফিরে পাওয়াও সম্ভব))
পিটার ব্রিগস

@ পিটারব্রিগস: ধন্যবাদ, এই সতর্কতা গুরুত্বপূর্ণ is আমি একটি পিপিএস যুক্ত করেছি।
এরিক হে লেবিগোট

1
@ ইওল: স্ল্যাশ যোগ করে কীভাবে সমস্যাটি সমাধান করা যায় তা আমি সত্যিই দেখছি না :(। আমাদের কী হয় ['/usr/var1/log/', '/usr/var2/log/']?
bluenote10

1
@EOL: যেহেতু আমি এই সমস্যা আমি একটি আকর্ষণীয় সমাধান খুঁজে বের করার, যদিও কোনো এই উপ-বিষয় নিয়ে আলোচনা করা ঠিক হতে পারে ব্যর্থ পৃথক প্রশ্ন
bluenote10

86

os.path.relpath:

বর্তমান ডিরেক্টরি থেকে বা alচ্ছিক প্রারম্ভিক বিন্দু থেকে কোনও আপেক্ষিক ফাইলপথকে পথে ফেরান।

>>> from os.path import relpath
>>> relpath('/usr/var/log/', '/usr/var')
'log'
>>> relpath('/usr/var/log/', '/usr/var/sad/')
'../log'

সুতরাং, যদি আপেক্ষিক পথটি শুরু হয় '..'- এর অর্থ হ'ল দ্বিতীয় পথটি প্রথম পথের বংশধর নয়।

পাইথন 3 এ আপনি ব্যবহার করতে পারেন PurePath.relative_to:

Python 3.5.1 (default, Jan 22 2016, 08:54:32)
>>> from pathlib import Path

>>> Path('/usr/var/log').relative_to('/usr/var/log/')
PosixPath('.')

>>> Path('/usr/var/log').relative_to('/usr/var/')
PosixPath('log')

>>> Path('/usr/var/log').relative_to('/etc/')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/pathlib.py", line 851, in relative_to
    .format(str(self), str(formatted)))
ValueError: '/usr/var/log' does not start with '/etc'

2
উপস্থিতি os.pardirযাচাই করা চেক করার চেয়ে বেশি শক্তিশালী ..(সম্মত, যদিও আরও অনেক কনভেনশন নেই)।
এরিক হে লেবিগোট

8
আমি কি ভুল বা os.relpathআরও শক্তিশালী যেহেতু এটি পরিচালনা করে ..এবং PurePath.relative_to()না করে? আমি কিছু অনুপস্থিত করছি?
রায় সালেমি

15

আরেকটি বিকল্প হ'ল

>>> print os.path.relpath('/usr/var/log/', '/usr/var')
log

এটি সর্বদা একটি আপেক্ষিক পথ ফেরায়; এই না সরাসরি ইঙ্গিত পাথ এক (এক উপস্থিতি পরীক্ষা করতে পারবেন অন্যান্য উপরে কিনা os.pardir, যদিও দুটি সম্ভাব্য ফলে আপেক্ষিক পাথ সামনে)।
এরিক হে লেবিগোট

8

পাইথন 3-তে প্যাথলিব ব্যবহার করে জেএমইর পরামর্শের একটি লিখন আপ।

from pathlib import Path
parent = Path(r'/a/b')
son = Path(r'/a/b/c/d')            

if parent in son.parents or parent==son:
    print(son.relative_to(parent)) # returns Path object equivalent to 'c/d'

সুতরাং dir1.relative_to(dir2)তারা যদি একই হয় তবে পিক্সপথ ('।') দেবে। আপনি যখন ব্যবহার করেন if dir2 in dir1.parentsতখন এটি পরিচয় কেস বাদ দেয়। কেউ পথ তুলনা এবং চালানোর জন্য চায় হয়, তাহলে relative_to()যদি তারা পথ-সামঞ্জস্যপূর্ণ, একটি ভাল সমাধান হতে পারে if dir2 in (dir1 / 'x').parentsবা if dir2 in dir1.parents or dir2 == dir1। তারপরে পাথের সামঞ্জস্যের সমস্ত ক্ষেত্রে আচ্ছাদিত করা হয়।
ingyhere

3

খাঁটি পাইথন 2 ডাব্লু / ও ডিপ:

def relpath(cwd, path):
    """Create a relative path for path from cwd, if possible"""
    if sys.platform == "win32":
        cwd = cwd.lower()
        path = path.lower()
    _cwd = os.path.abspath(cwd).split(os.path.sep)
    _path = os.path.abspath(path).split(os.path.sep)
    eq_until_pos = None
    for i in xrange(min(len(_cwd), len(_path))):
        if _cwd[i] == _path[i]:
            eq_until_pos = i
        else:
            break
    if eq_until_pos is None:
        return path
    newpath = [".." for i in xrange(len(_cwd[eq_until_pos+1:]))]
    newpath.extend(_path[eq_until_pos+1:])
    return os.path.join(*newpath) if newpath else "."

এইটি দেখতে ভাল লাগছে, তবে আমি যেমন হোঁচট খাচ্ছি তখন একটি সমস্যা আছে যখন cwdএবং যখন pathএকই রকম হয়। প্রথমে এটি চেক করা উচিত যে এই দু'টি একই কিনা এবং ফেরত """."
আসুন

1

সম্পাদনা করুন: পাইথন 3 দিয়ে সেরা উপায়ে জেএম এর উত্তর দেখুন।

পাথলিব ব্যবহার করে, আপনার নিম্নলিখিত সমাধান রয়েছে:

ধরা যাক আমরা পরীক্ষা করতে চাই যে এর sonবংশধর parentএবং উভয়ই Pathবস্তু are আমরা সেই পথে অংশগুলির একটি তালিকা পেতে পারি list(parent.parts)। তারপরে, আমরা কেবল পরীক্ষা করে দেখি যে পুত্রের জন্ম নেওয়া পিতামাতার বিভাগগুলির তালিকার সমান।

>>> lparent = list(parent.parts)
>>> lson = list(son.parts)
>>> if lson[:len(lparent)] == lparent:
>>> ... #parent is a parent of son :)

আপনি যদি বাকী অংশটি পেতে চান তবে আপনি এটি করতে পারেন

>>> ''.join(lson[len(lparent):])

এটি একটি স্ট্রিং, তবে আপনি অবশ্যই এটি অন্য পাথ অবজেক্টের কনস্ট্রাক্টর হিসাবে ব্যবহার করতে পারেন।


4
এটি এর চেয়ে আরও সহজ: কেবল parent in son.parentsএবং যদি এটি হয় তবে এর সাথে বাকী অংশটি পাওয়া যায় son.relative_to(parent)
jme

@ জেএমএম আপনি উত্তরটি আরও ভাল, আপনি কেন এটি পোস্ট করেন না?
জেরেমি কোচয়
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.