কেন + = তালিকাগুলিতে অপ্রত্যাশিত আচরণ করে?


118

+=পাইথন মধ্যে অপারেটর তালিকার অপ্রত্যাশিতভাবে অপারেটিং বলে মনে হয়। কেউ কি বলতে পারেন এখানে কী চলছে?

class foo:  
     bar = []
     def __init__(self,x):
         self.bar += [x]


class foo2:
     bar = []
     def __init__(self,x):
          self.bar = self.bar + [x]

f = foo(1)
g = foo(2)
print f.bar
print g.bar 

f.bar += [3]
print f.bar
print g.bar

f.bar = f.bar + [4]
print f.bar
print g.bar

f = foo2(1)
g = foo2(2)
print f.bar 
print g.bar 

আউটপুট

[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]

foo += barক্লাসের প্রতিটি ঘটনাকে প্রভাবিত করে foo = foo + barবলে মনে হচ্ছে , যেখানে জিনিসগুলি যেভাবে আচরণ করবে বলে আমি আশা করি সেভাবে আচরণ করে।

+=অপারেটর একটি "যৌগ নিয়োগ অপারেটর" বলা হয়।


তালিকায় 'প্রসারিত' এবং 'সংযোজন' এর মধ্যে পার্থক্যটিও দেখুন
এন 1.1

3
পাইথনের সাথে এটি কিছু ভুল দেখায় বলে আমি মনে করি না। বেশিরভাগ ভাষা এমনকি আপনাকে +অ্যারেতে অপারেটর ব্যবহার করতে দেয় না । আমি মনে করি এটি এ ক্ষেত্রে নিখুঁতভাবে উপলব্ধি করে যা +=সংযোজন করবে।
স্কিলড্রিক

4
আনুষ্ঠানিকভাবে একে বলা হয় 'বর্ধিত অ্যাসাইনমেন্ট'।
মার্টিজন পিটারস

উত্তর:


138

সাধারণ উত্তরটি হ'ল বিশেষ পদ্ধতিটি +=কল করার চেষ্টা করে __iadd__এবং যদি তা না পাওয়া যায় তবে __add__পরিবর্তে এটি ব্যবহারের চেষ্টা করে। সুতরাং সমস্যাটি এই বিশেষ পদ্ধতির মধ্যে পার্থক্য নিয়ে।

__iadd__বিশেষ পদ্ধতি যা এটা mutates বস্তুর এটি উপর কাজ করে, একটি ইন-জায়গা উপরন্তু জন্য। __add__বিশেষ পদ্ধতি একটি নতুন বস্তু ফেরৎ এবং মান জন্য ব্যবহার করা হয় +অপারেটর।

সুতরাং যখন +=অপারেটর কোনও __iadd__সংজ্ঞায়িত বস্তুতে ব্যবহৃত হয় তখন বস্তুটি জায়গায় পরিবর্তিত হবে। অন্যথায় এটি পরিবর্তে প্লেইনটি ব্যবহার করার চেষ্টা করবে __add__এবং একটি নতুন অবজেক্ট ফিরিয়ে দেবে।

এজন্য +=পরিবর্তনের মতো তালিকার জন্য তালিকাগুলির মান পরিবর্তিত হয়, অন্যদিকে যেমন টিপলস, স্ট্রিংস এবং পূর্ণসংখ্যার মতো পরিবর্তনীয় পরিবর্তে নতুন বস্তু পরিবর্তিত হয় ( a += bসমতুল্য হয় a = a + b)।

এমন ধরণের ক্ষেত্রে যা উভয়কে সমর্থন করে __iadd__এবং __add__তাই আপনি কোনটি ব্যবহার করেন সে সম্পর্কে আপনাকে সতর্কতা অবলম্বন করতে হবে। a += bকল __iadd__এবং রূপান্তর করবে a, যেখানে a = a + bএকটি নতুন অবজেক্ট তৈরি করবে এবং এটি নির্ধারণ করবে a। তারা একই অপারেশন না!

>>> a1 = a2 = [1, 2]
>>> b1 = b2 = [1, 2]
>>> a1 += [3]          # Uses __iadd__, modifies a1 in-place
>>> b1 = b1 + [3]      # Uses __add__, creates new list, assigns it to b1
>>> a2
[1, 2, 3]              # a1 and a2 are still the same list
>>> b2
[1, 2]                 # whereas only b1 was changed

অপরিবর্তনীয় প্রকারের জন্য (যেখানে আপনার কোনওটি নেই __iadd__) a += bএবং a = a + bসমতুল্য। এটাই আপনাকে +=অপরিবর্তনীয় প্রকারের জন্য ব্যবহার করতে দেয় , যা আপনি অদ্ভুত নকশার সিদ্ধান্ত হিসাবে মনে করছেন যতক্ষণ না আপনি বিবেচনা না করেন যতক্ষণ না আপনি +=সংখ্যার মতো অপরিবর্তনীয় ধরণের উপর ব্যবহার করতে পারবেন না !


4
__radd__কখনও কখনও বলা যেতে পারে এমন পদ্ধতিও রয়েছে (এটি মূলত সাবক্লাসগুলিতে জড়িত অভিব্যক্তির জন্য প্রাসঙ্গিক)।
jfs

2
দৃষ্টিকোণে: + = কার্যকর যদি মেমরি এবং গতি গুরুত্বপূর্ণ
নরফেল্ড

3
জেনে +=আসলে প্রসারিত একটি তালিকা, এই ব্যাখ্যা দিয়েছে কেন x = []; x = x + {}একটি দেয় TypeErrorযখন x = []; x += {}মাত্র আয় []
জিজোলো

96

সাধারণ ক্ষেত্রে স্কট গ্রিফিথের উত্তর দেখুন । আপনার মতো তালিকাগুলির সাথে কাজ করার সময়, +=অপারেটরটির জন্য একটি শর্টহ্যান্ড someListObject.extend(iterableObject)। প্রসারণের ডকুমেন্টেশন দেখুন ()

extendফাংশন লিস্টে প্যারামিটারের সব উপাদান যোগ হবে।

আপনি যখন foo += somethingতালিকাটি fooস্থানে পরিবর্তন করছেন, তখন আপনি নামটি উল্লেখ করেছেন এমন রেফারেন্স পরিবর্তন করবেন না foo, তবে আপনি সরাসরি তালিকার অবজেক্টটি পরিবর্তন করছেন। এর সাথে foo = foo + something, আপনি আসলে একটি নতুন তালিকা তৈরি করছেন ।

এই উদাহরণ কোডটি এটি ব্যাখ্যা করবে:

>>> l = []
>>> id(l)
13043192
>>> l += [3]
>>> id(l)
13043192
>>> l = l + [3]
>>> id(l)
13059216

আপনি যখন নতুন তালিকাতে পুনরায় নিযুক্ত করেন তখন রেফারেন্স কীভাবে পরিবর্তিত হয় তা নোট করুন l

হিসাবে barএকটি বর্গ পরিবর্তনশীল পরিবর্তে একটি দৃষ্টান্ত পরিবর্তনশীল, জায়গায় পরিবর্তন যে বর্গ সমস্ত উদাহরণ প্রভাবিত করবে। তবে পুনরায় সংজ্ঞা দেওয়ার সময় self.bar, self.barঅন্য শ্রেণীর দৃষ্টান্তগুলিকে প্রভাবিত না করে উদাহরণটির পৃথক উদাহরণ পরিবর্তনশীল থাকবে ।


7
এটি সর্বদা সত্য নয়: a = 1; a + = 1; বৈধ পাইথন, তবে ints এর কোনও "প্রসারিত ()" পদ্ধতি নেই। আপনি এটিকে সাধারণীকরণ করতে পারবেন না।
ই-সন্তুষ্ট

2
কিছু পরীক্ষা সম্পন্ন হয়েছে, স্কট গ্রিফিথস এটি ঠিক পেয়েছে, তাই আপনার জন্য -1 1
ই-সন্তুষ্ট

11
@ ই-স্ট্যাটিস: ওপি স্পষ্টভাবে তালিকাগুলির বিষয়ে কথা বলছিল, এবং আমি স্পষ্ট করে বলেছি যে আমি তালিকা সম্পর্কেও কথা বলছি। আমি কিছু সাধারণ করছি না।
AndiDog

-1 সরানো হয়েছে, উত্তরটি ভাল ধারণা করা হয়েছে। আমি এখনও গ্রিফিথসের উত্তর যদিও ভাল বলে মনে করি।
ই-সন্তুষ্ট

প্রথমে এটি চিন্তা করতে যে অদ্ভুত মতানুযায়ী a += bথেকে ভিন্ন a = a + bদুই তালিকার জন্য aএবং b। তবে তা বোঝা যায়; extendপ্রায়শই প্রায়শই তালিকাগুলির সাথে তাল মিলিয়ে করানো না হয়ে পুরো তালিকার একটি নতুন অনুলিপি তৈরি করার চেয়ে বেশি সময় জটিলতা থাকে। যদি বিকাশকারীদের সতর্কতা অবলম্বন করা উচিত যে তারা জায়গায় মূল তালিকাগুলি সংশোধন না করে, তবে টিউপসগুলি অপরিবর্তনীয় বস্তু হওয়ার চেয়ে ভাল বিকল্প। +=টিপলস সহ মূল টিপলটি সংশোধন করতে পারে না।
প্রাণজাল মিত্তাল

22

এখানে সমস্যাটি হ'ল barশ্রেণীর বৈশিষ্ট্য হিসাবে সংজ্ঞায়িত করা হয়, উদাহরণের পরিবর্তনশীল নয়।

ইন foo, ক্লাস বৈশিষ্ট্যটি পদ্ধতিতে পরিবর্তিত হয় init, এজন্য সমস্ত দৃষ্টান্ত প্রভাবিত হয়।

ইন foo2, একটি উদাহরণ ভেরিয়েবলকে (খালি) শ্রেণি বৈশিষ্ট্যটি ব্যবহার করে সংজ্ঞায়িত করা হয় এবং প্রতিটি উদাহরণ তার নিজস্ব হয় bar

"সঠিক" বাস্তবায়নটি হ'ল:

class foo:
    def __init__(self, x):
        self.bar = [x]

অবশ্যই শ্রেণীর বৈশিষ্ট্যগুলি সম্পূর্ণ আইনী। আসলে, আপনি ক্লাসের উদাহরণ তৈরি না করে এগুলি অ্যাক্সেস এবং সংশোধন করতে পারেন:

class foo:
    bar = []

foo.bar = [x]

8

এখানে দুটি জিনিস জড়িত রয়েছে:

1. class attributes and instance attributes
2. difference between the operators + and += for lists

+অপারেটর __add__একটি তালিকায় পদ্ধতি কল । এটি এর ক্রিয়াকলাপ থেকে সমস্ত উপাদান নেয় এবং তাদের ক্রম বজায় করে এমন উপাদানগুলির সমন্বয়ে একটি নতুন তালিকা তৈরি করে।

+=অপারেটর তালিকায় কল __iadd__পদ্ধতি। এটি একটি পুনরাবৃত্তযোগ্য লাগে এবং পুনরাবৃত্তের সমস্ত উপাদানগুলিকে তালিকার তালিকায় যুক্ত করে। এটি একটি নতুন তালিকা অবজেক্ট তৈরি করে না।

ক্লাসে fooস্টেটমেন্টটি self.bar += [x]কোনও অ্যাসাইনমেন্ট স্টেটমেন্ট নয় তবে বাস্তবে অনুবাদ করে

self.bar.__iadd__([x])  # modifies the class attribute  

যা তালিকায় স্থান পরিবর্তন করে এবং তালিকার পদ্ধতির মতো কাজ করে extend

শ্রেণিতে foo2, বিপরীতে, initপদ্ধতিতে অ্যাসাইনমেন্ট বিবৃতি

self.bar = self.bar + [x]  

ডিকনস্ট্রাক্ট করা যেতে পারে:
উদাহরণটির কোনও বৈশিষ্ট্য নেই bar(একই নামের একটি শ্রেণীর বৈশিষ্ট্য রয়েছে যদিও) তাই এটি শ্রেণীর বৈশিষ্ট্যটি অ্যাক্সেস করে barএবং এটি যুক্ত করে একটি নতুন তালিকা তৈরি xকরে। বিবৃতিটি অনুবাদ করে:

self.bar = self.bar.__add__([x]) # bar on the lhs is the class attribute 

তারপরে এটি একটি উদাহরণ অ্যাট্রিবিউট তৈরি barকরে এবং এতে নতুন তৈরি তালিকাটি নির্ধারণ করে। নোট করুন যে barঅ্যাসাইনমেন্টের barআরএইচএসের উপর lhs এর চেয়ে পৃথক।

শ্রেণীর উদাহরণগুলির জন্য foo, barএকটি শ্রেণীর বৈশিষ্ট্য এবং উদাহরণ বৈশিষ্ট্য নয়। সুতরাং শ্রেণীর বৈশিষ্ট্যে কোনও পরিবর্তন barসমস্ত দৃষ্টান্তের জন্য প্রতিফলিত হবে।

বিপরীতে, শ্রেণীর প্রতিটি ঘটনার foo2নিজস্ব উদাহরণ বৈশিষ্ট্য রয়েছে barযা একই নামের শ্রেণীর বৈশিষ্ট্য থেকে পৃথক bar

f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]  
print f.__class__.bar # accessing the class attribute. prints []  

আশা করি এটি জিনিস পরিষ্কার করে দেয়।


5

যদিও অনেক সময় পেরিয়ে গেছে এবং অনেকগুলি সঠিক কথা বলা হয়েছিল, এর কোনও উত্তর নেই যা উভয় প্রভাবকেই বান্ডিল করে।

আপনার 2 টি প্রভাব রয়েছে:

  1. একটি "বিশেষ", সম্ভবত তালিকার অলক্ষিত আচরণ +=( স্কট গ্রিফিথস দ্বারা বর্ণিত হিসাবে )
  2. শ্রেণীর বৈশিষ্ট্যগুলির পাশাপাশি উদাহরণের বৈশিষ্ট্যগুলি জড়িত রয়েছে ( ক্যান বার্ক বেডার দ্বারা বর্ণিত )

ক্লাসে foo, __init__পদ্ধতিটি শ্রেণীর বৈশিষ্ট্য পরিবর্তন করে। এটি self.bar += [x]অনুবাদ কারণ self.bar = self.bar.__iadd__([x])__iadd__()অন্তর্নিহিত পরিবর্তনের জন্য, সুতরাং এটি তালিকাটি পরিবর্তন করে এবং এটিতে একটি রেফারেন্স দেয়।

নোট করুন যে ইনস্ট্যান্ট ডিকটি সংশোধিত হয়েছে যদিও এটি সাধারণত প্রয়োজন হবে না কারণ শ্রেণি ডিকটিতে ইতিমধ্যে একই কাজ রয়েছে। সুতরাং এই বিবরণটি প্রায় অলক্ষিত হয় - আপনি যদি foo.bar = []পরে কিছু না করেন তবে । এখানে দৃষ্টান্তগুলির barঅনুরূপ তথ্যের জন্য একই ধন্যবাদ রয়েছে।

ক্লাসে foo2, তবে, শ্রেণীর barব্যবহৃত হয়, কিন্তু ছোঁয়া হয় না। পরিবর্তে, [x]এটিকে যুক্ত করা হয়, একটি নতুন অবজেক্ট তৈরি করে, যেমন self.bar.__add__([x])এখানে বলা হয়, যা বস্তুকে পরিবর্তন করে না। ফলাফলটি তখন উদাহরণস্বরূপ ডকের মধ্যে রাখা হয়, উদাহরণটিকে ডিক হিসাবে নতুন তালিকা প্রদান করে, যখন শ্রেণীর বৈশিষ্ট্যটি পরিবর্তিত থাকে।

এর মধ্যে পার্থক্য ... = ... + ...এবং ... += ...পরবর্তী কাজগুলি পাশাপাশি প্রভাবিত করে:

f = foo(1) # adds 1 to the class's bar and assigns f.bar to this as well.
g = foo(2) # adds 2 to the class's bar and assigns g.bar to this as well.
# Here, foo.bar, f.bar and g.bar refer to the same object.
print f.bar # [1, 2]
print g.bar # [1, 2]

f.bar += [3] # adds 3 to this object
print f.bar # As these still refer to the same object,
print g.bar # the output is the same.

f.bar = f.bar + [4] # Construct a new list with the values of the old ones, 4 appended.
print f.bar # Print the new one
print g.bar # Print the old one.

f = foo2(1) # Here a new list is created on every call.
g = foo2(2)
print f.bar # So these all obly have one element.
print g.bar 

আপনি অবজেক্টগুলির পরিচয় যাচাই করতে পারবেন print id(foo), id(f), id(g)( ()যদি আপনি পাইথন 3 এ থাকেন তবে অতিরিক্তগুলি ভুলে যাবেন না )।

বিটিডাব্লু: +=অপারেটরটিকে "অগমেন্টেড অ্যাসাইনমেন্ট" বলা হয় এবং সাধারণত যতদূর সম্ভব ইনপ্লেস পরিবর্তনগুলি করা যায়।


5

অন্যান্য উত্তরগুলি এটির অনেকটা আচ্ছাদিত বলে মনে হবে, যদিও এটি অগমেন্টেড অ্যাসাইনমেন্টস পিইপি 203 উল্লেখ করা এবং উল্লেখ করা উপযুক্ত বলে মনে হচ্ছে :

বাম-হাতের বস্তুটি সমর্থন করলে এবং [বাম-হাতের দিকটি একবারে মূল্যায়ন করা হয় the স্থানে) অপারেশনটি "জায়গায় জায়গায়" ব্যতীত তারা [অগমেন্টেড অ্যাসাইনমেন্ট অপারেটরগণ] তাদের সাধারণ বাইনারি ফর্মের মতো একই অপারেটরটি প্রয়োগ করে।

...

পাইথনে বর্ধিত অ্যাসাইনমেন্টের পেছনের ধারণাটি হ'ল বাম হাতের অপারেণ্ডে বাইনারি অপারেশনের ফলাফল সংরক্ষণের সাধারণ অনুশীলনটি লেখার পক্ষে সহজ উপায় নয়, তবে বাম-হাতের ক্রিয়াকলাপটিকে প্রশ্ন করার উপায়ও আপনার নিজের পরিবর্তিত অনুলিপি তৈরির পরিবর্তে এটি 'নিজেই' পরিচালনা করা উচিত তা জেনে রাখুন।


1
>>> elements=[[1],[2],[3]]
>>> subset=[]
>>> subset+=elements[0:1]
>>> subset
[[1]]
>>> elements
[[1], [2], [3]]
>>> subset[0][0]='change'
>>> elements
[['change'], [2], [3]]

>>> a=[1,2,3,4]
>>> b=a
>>> a+=[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4, 5])
>>> a=[1,2,3,4]
>>> b=a
>>> a=a+[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4])

0
>>> a = 89
>>> id(a)
4434330504
>>> a = 89 + 1
>>> print(a)
90
>>> id(a)
4430689552  # this is different from before!

>>> test = [1, 2, 3]
>>> id(test)
48638344L
>>> test2 = test
>>> id(test)
48638344L
>>> test2 += [4]
>>> id(test)
48638344L
>>> print(test, test2)  # [1, 2, 3, 4] [1, 2, 3, 4]```
([1, 2, 3, 4], [1, 2, 3, 4])
>>> id(test2)
48638344L # ID is different here

আমরা দেখতে পাই যে যখন আমরা একটি অপরিবর্তনীয় বস্তুটি (এই ক্ষেত্রে পূর্ণসংখ্যার) সংশোধন করার চেষ্টা করি তখন পাইথন কেবল তার পরিবর্তে আমাদের একটি আলাদা অবজেক্ট দেয়। অন্যদিকে, আমরা একটি পরিবর্তনীয় অবজেক্টে (একটি তালিকা) পরিবর্তন করতে সক্ষম এবং এটি জুড়ে একই জিনিস থাকতে পারে have

রেফ: https://medium.com/@tyastropheus/tricky-python-i-memory-management-for- পরিবর্তনযোগ্য- অপরিবর্তনীয়- প্রকল্পগুলি 1-21507d1e5b95

অগভীর অনুলিপি এবং ডিপকপিটি বুঝতে নীচে url দেখুন

https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/


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