47

I have gone through these questions,

  1. Python assigning multiple variables to same value? list behavior
    concerned with tuples, I want just variables may be a string, integer or dictionary
  2. More elegant way of declaring multiple variables at the same time
    The question has something I want to ask, but the accepted answer is much complex

so what I'm trying to achieve,

I declare variables as follows, and I want to reduce these declarations to as less line of code as possible.

details = None
product_base = None
product_identity = None
category_string = None
store_id = None
image_hash = None
image_link_mask = None
results = None
abort = False
data = {}

What is the simplest, easy to maintain ?

NerdOnTour
  • 634
  • 4
  • 15

7 Answers7

72

I agree with the other answers but would like to explain the important point here.

None object is singleton object. How many times you assign None object to a variable, same object is used. So

x = None
y = None

is equal to

x = y = None

but you should not do the same thing with any other object in python. For example,

x = {}  # each time a dict object is created
y = {}

is not equal to

x = y = {}  # same dict object assigned to x ,y. We should not do this.
Shan
  • 964
  • 9
  • 11
34

First of all I would advice you not to do this. It's unreadable and un-Pythonic. However you can reduce the number of lines with something like:

details, product_base, product_identity, category_string, store_id, image_hash, image_link_mask, results = [None] * 8
abort = False
data = {}
Aske Doerge
  • 1,331
  • 10
  • 17
15
(
    details,
    producy_base,
    product_identity,
    category_string,
    store_id,
    image_hash,
    image_link_mask,
    results,
) = (None, None, None, None, None, None, None, None)

abort = False
data = {}

That's how I do.

Szymon Maszke
  • 22,747
  • 4
  • 43
  • 83
DasFranck
  • 379
  • 3
  • 11
7

I have a one-line lambda function I use that helps me out with this.

nones = lambda n: [None for _ in range(n)]
v, w, x, y, z = nones(5)

The lambda is the same thing as this.

def nones(n):
    return [None for _ in range(n)]
jerblack
  • 1,203
  • 15
  • 15
2

A mix of previous answers :

from copy import deepcopy

def default(number, value = None):
    if type(value) is dict or object:
        return [deepcopy(value) for _ in range(number)]
    else:
        return [value] * number
    
o, p, q, r, s = default(5)
t, u = default(2, false)
v, w, x = default(3, {})

class GPU:
  def __init__(self, m, p):
    self.model = m
    self.price = p
 
rtx_3080 = GPU("RTX 3080", 99999)

y, z = default(2, rtx_3080)

Edit:

Tried to optimize deepcopy call by better handling mutable variables with pandas/numpy types as well. Might have missed some other ones. If someone find a better way to check for mutability, feel free to share. Althought, this maybe over-engineering whatever your use case is...

import builtins
from copy import deepcopy
from numbers import Number
from pandas import api, DataFrame

mutables = (dict, list, set, DataFrame)
immutables = (str, tuple, frozenset, Number)

def is_built_in_type(var):
    return type(var).__name__ in dir(builtins)

def is_mutable(var):
    return var is not None and (api.types.is_list_like(var) or isinstance(var, mutables) or not is_built_in_type(var))

def is_immutable(var):
    return var is None or isinstance(var, immutables)

def default(number, value=None):
    if is_mutable(value):
        return [deepcopy(value) for _ in range(number)]
    elif is_immutable(value):
        return [value] * number
    else:
        raise ValueError("Unexpected value type")

a, b, c, d, e = default(5)
f, g, h = default(3, False)
i, j = default(2, "False")
k, l, m, n = default(4, (3, 2, 1))
o, p = default(2, 3.14159265358979)
q, r, s = default(3, [1, 2, 3])
t, u = default(2, {})
v, w, x = default(3, DataFrame({'col1': [7, 13, 42, 73, 666], 'col2': [1, 0.6, 2, 1.4, 0.3]}))

class GPU:
    def __init__(self, m, p):
        self.model = m
        self.price = p

rtx_3080 = GPU("RTX 3080", 99999)

y, z = default(2, rtx_3080)
belgacea
  • 1,084
  • 1
  • 15
  • 33
  • 1
    `v, w, x = default(3, {})` will not work as expected: because you instantiated `{}` in the call to `default()`, you actually assign the *same* dictionary to each variable: run `v["a"] = "b"` and then print `w`, and you'll see that `w=={'a': 'b'}`. – joanis Oct 08 '21 at 13:07
  • 1
    You could patch this code by returning an array of deepcopies of value if you really wanted to keep this approach. This would work `return [copy.deepcopy(value) for _ in range(number)]`. – joanis Oct 08 '21 at 13:09
  • @joanis Thanks for noticing. I edited my answer. – belgacea Oct 08 '21 at 23:52
  • 1
    Much better, and it will work in general, although I suspect the deepcopy line is called systematically, because `type(value) is dict or object` is the same as `(type(value) is dict) or object` , and `object` evaluated in a Boolean context is always true. While I think that optimization is superfluous anyway, what you're really trying to test is whether `value` is mutable or not. In Python, everything is an object, so something like `if isinstance(value, dict) or isinstance(value, object):`, which I think is closer to what you were trying to do, would also be systematically true. – joanis Oct 12 '21 at 18:59
  • @joanis deepcopy was indeed called all the time. `if isinstance(value, (dict, object))` gives the same behavior. As I am not aware of any type/class descriptor for mutability in Python, I added some type checking for the most common use cases, but it starts to feel a bit too much for what I was aiming to achieve in a first place. – belgacea Oct 20 '21 at 04:17
  • 1
    It makes the code a lot more complicated. I would actually stick with a single unconditional deepcopy line, on the premise that the deepcopy of `None`, `False`, `True`, `()`, are all super cheap, so it's not worth trying to optimize that call out. Furthermore, I just tested, and the deepcopy of a string or a tuple containing only immutables turns out to be a optimized back to a ref copy by `copy.deepcopy` itself, so it looks like all the magic we're trying to add already exists. So, for code simplicity, and at no performance cost, I would make `default()` a one-line function. – joanis Oct 20 '21 at 12:28
1

After having participated in the discussion under @belgacea's answer, I'd like to post the variant of that answer that I think is the best:

import copy

def duplicate(value, n):
    return [copy.deepcopy(value) for _ in range(n)]

a, b, c = duplicate([0], 3)
o, p, q, r, s = duplicate(None, 5)
t, u = duplicate(False, 2)
v, w, x = duplicate({}, 3)
y, z = duplicate(MyClass(), 2)

In all cases, we're guaranteed by deepcopy() that mutable objects are not shared among the different variables initialized together, which is the important gotcha to avoid.

But I've simplified the code to its simplest expression, taking advantage of the fact that deepcopy is already well optimized to do a shallow copy when its input is immutable. I've tested this with strings and tuples, and if x is recursively immutable, e.g., x = ((1,2),"foo"), then x is copy.deepcopy(x) returns True, so there's no benefit in trying to avoid calling deepcopy on values that don't need a deep copy.

I've also changed the signature and name of the function to something that I think will make better self-documenting code where it is used.

joanis
  • 10,635
  • 14
  • 30
  • 40
-2

This does not directly answer the question, but it is related -- I use an instance of an empty class to group similar attributes, so I do not have to clutter up my init method by listing them all.

class Empty:
    pass

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.w = Empty()          # widgets
        self.master = master
        self.pack()
        self.create_widgets()

    def create_widgets(self):
        self.w.entry = tk.Entry(self, bg="orange", fg="black", font=FONT)

What is the difference between SimpleNamespace and empty class definition?

Stan
  • 156
  • 1
  • 6