Need help with code: A list variable is used in python function when it seems empty

First posted on stack overflow

I’m creating a Frappe App to reconcile JVs to Invoices, similar ( working ) functionality exists (Payment Reconciliation) so I’m trying to reuse this code. I can’t find the actual assignment for the lst variable anywhere in this python document (using search, its a local variable for this function). So far it seems like an empty list at declaration, however, its treated as if it contains a value.

Or am I just misunderstanding the code itself?

first, lst is declared as a list (empty)

lst = []

The it is assigned to reconciled_entry (still empty)

reconciled_entry = lst

Then it is used as follows: (still empty)

if lst:
        reconcile_against_document(lst)

The function reconcile_against_document function expects an actual list:

Is it still empty???

def reconcile_against_document(args):
    """
        Cancel JV, Update against document, split if required and resubmit jv
    """
    for d in args:

        check_if_advance_entry_modified(d)
        validate_allocated_amount(d)

        # cancel advance entry
        doc = frappe.get_doc(d.voucher_type, d.voucher_no)

        doc.make_gl_entries(cancel=1, adv_adj=1)
...

Main function:

def reconcile(self, args):
        for e in self.get('payments'):
            e.invoice_type = None
            if e.invoice_number and " | " in e.invoice_number:
                e.invoice_type, e.invoice_number = e.invoice_number.split(" | ")

        self.get_invoice_entries()
        self.validate_invoice()
        dr_or_cr = ("credit_in_account_currency"
            if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency")

        lst = []
        dr_or_cr_notes = []
        for e in self.get('payments'):
            reconciled_entry = []
            if e.invoice_number and e.allocated_amount:
                if e.reference_type in ['Sales Invoice', 'Purchase Invoice']:
                    reconciled_entry = dr_or_cr_notes
                else:
                    reconciled_entry = lst

                reconciled_entry.append(self.get_payment_details(e, dr_or_cr))

        if lst:
            reconcile_against_document(lst)

        if dr_or_cr_notes:
            reconcile_dr_cr_note(dr_or_cr_notes)

        msgprint(_("Successfully Reconciled"))
        self.get_unreconciled_entries()

Unfortunately this code lacks commenting @rmehta

Ok, I got some help from @netchampfaris … this guy is awesome!

Python object assignment is funny. From my understanding, when lst (object) is assigned to another variable ( new_lst which also becomes an object variable), what happens to one object variable affects the other

python demo screenshot

>>> lst = []
>>> new_lst = lst
>>> new_lst.append('funny python')
>>> lst
['funny python']
>>> lst.append('not funny python')
>>> new_lst
['funny python', 'not funny python']
>>> lst
['funny python', 'not funny python']
1 Like

Good learning thanks!

This helps explain Python argument passing behaviour https://stackoverflow.com/questions/5234126/python-variable-scope-passing-by-reference-or-copy

For this working example https://www.python-course.eu/passing_arguments.php

Starting with Python 3, print requires parenthesis to avoid a syntax error:

frappe@ubuntu1804lts:~/frappe-bench$ bench console
Apps in this namespace:
frappe, erpnext

In [1]: def ref_demo(x):
…: print (“x=”,x," id=",id(x))
…: x=42
…: print (“x=”,x," id=",id(x))
…:

In [2]: x = 9

In [3]: id(x)
Out[3]: 10914752

In [4]: ref_demo(x)
x= 9 id= 10914752
x= 42 id= 10915808

In [5]: id(x)
Out[5]: 10914752

1 Like