Generate a new object each time a name is imported

  • Thread starter Steven D'Aprano
  • Start date
S

Steven D'Aprano

I would like to generate a new object each time I import a name from a
module, rather than getting the same object each time. For example,
currently I might do something like this:

# Module
count = 0
def factory():
# Generate a unique object each time this is called
global count
count += 1
return "Object #%d" % count


# Calling module
from Module import factory
a = factory() # a == "Object #1"
b = factory() # b == "Object #2"
del factory


I'm looking for a way to hide the generation of objects from the caller,
so I could do something like this:

from Module import factory() as a # a == "Object #1"
from Module import factory() as b # b == "Object #2"

except of course that syntax is illegal.
 
J

Jonathan Gardner

I'm looking for a way to hide the generation of objects from the caller,
so I could do something like this:

from Module import factory() as a  # a == "Object #1"
from Module import factory() as b  # b == "Object #2"

Explicit is better than implicit. In other words, I don't see why you
need to hide this. Just import the factory function and call it.

from Module import factory
a = factory()
 
H

Hrvoje Niksic

Steven D'Aprano said:
I'm looking for a way to hide the generation of objects from the caller,
so I could do something like this:

from Module import factory() as a # a == "Object #1"
from Module import factory() as b # b == "Object #2"

except of course that syntax is illegal.

That sounds reasonable (zen quotes aside), but it's just not possible in
current Python. It would require for any use of "a" in subsequent code
to not only refer to an object, but also to automagically call it. I
know of no mechanism to do that. While Python allows intricate
customization of access to objects, simply referring to a name remains
fixed in meaning, probably intentionally.

(The "cell" mechanism used to implement closures sounds like it could
help, but a cell only stores a single value, not a factory, and it
doesn't apply to global variables anyway.)
 
P

Peter Otten

Steven said:
I would like to generate a new object each time I import a name from a
module, rather than getting the same object each time. For example,
currently I might do something like this:

# Module
count = 0
def factory():
# Generate a unique object each time this is called
global count
count += 1
return "Object #%d" % count


# Calling module
from Module import factory
a = factory() # a == "Object #1"
b = factory() # b == "Object #2"
del factory


I'm looking for a way to hide the generation of objects from the caller,
so I could do something like this:

from Module import factory() as a # a == "Object #1"
from Module import factory() as b # b == "Object #2"

except of course that syntax is illegal.

How about
.... def __init__(self):
.... self._n = 0
.... @property
.... def a(self):
.... try:
.... return self._n
.... finally:
.... self._n += 1
....
import sys
sys.modules["yadda"] = A()
from yadda import a
from yadda import a as b
a, b
(0, 1)

Peter
 
P

Peter Otten

wrote:

KNode cannot parse your From-string correctly. Strange.
Peter Otten said:
import sys
sys.modules["yadda"] = A()

OMG.... wow. I bow to you. But I'm not sure whether that's bowing in
awe or in terror.

I don't know who invented it, but it's an old trick. It even made it into
the standard library (email.LazyImporter).

For the record: I don't recommend it.

Peter
 
T

Terry Reedy

Peter said:
Steven D'Aprano wrote:

How about

For newbies who do not get how the following works, but would like to
know, I am adding some explanation.
... def __init__(self):
... self._n = 0
... @property
... def a(self):
... try:
... return self._n
... finally:
... self._n += 1

The @property decorator turns the 'a' function into the hidden getter
function for what looks to the outside world like a simple instance
attribute named 'a'.
import sys
sys.modules["yadda"] = A()

sys.modules is a system namespace that normally associates names with
modules. It is used by import statements to find existing modules.
However, there is no requirement that the associated object actually be
a module. A module is simply a collection of objects accessed as
attributes. One can get and set attributes of a module, and but hardly
anyhing else. Other attribute collection objects will do as well as far
as imports are concerned. 'If it quack like a duck (module in this case)...'

The above sets the 'module' to an *instance* of class A.

This looks for the 'module' named 'yadda'. It finds one - the instance
of A. It then requests attribute 'a' (of that instance). That request is
routed to the getter function.

This import statement could be in any module, not just the one that set
A() as a module surrogate.

As Peter mentioned in his followup, module surrogates were intended for
lazy imports of expensive-to-compute attributes that might never be
needed, so their creation could be delayed to whenever needed, if ever.

Tricks like the above are not recommended for normal usage, but do
illstrate some aspects of the language.

Terry Jan Reedy
 
J

Jean-Michel Pichavant

Steven said:
I would like to generate a new object each time I import a name from a
module, rather than getting the same object each time. For example,
currently I might do something like this:

# Module
count = 0
def factory():
# Generate a unique object each time this is called
global count
count += 1
return "Object #%d" % count


# Calling module
from Module import factory
a = factory() # a == "Object #1"
b = factory() # b == "Object #2"
del factory


I'm looking for a way to hide the generation of objects from the caller,
so I could do something like this:

from Module import factory() as a # a == "Object #1"
from Module import factory() as b # b == "Object #2"

except of course that syntax is illegal.
Why making standard statements do what they're not meant to do ?

You could write
>import Module
>
>a = factory()
>b = factory()
But you already know that.


So what's the purpose of making
from Module import factory as a
from Module import factory as b

return 2 different objects ? If I had to write this code I would expect 'a is b' to return 'True'.

This is no "don't do that" answer, it's a sincere question: what is the
benefit of your /new/ syntax ?

JM
 
S

Steven D'Aprano

So what's the purpose of making


return 2 different objects ? If I had to write this code I would expect
'a is b' to return 'True'.

This is no "don't do that" answer, it's a sincere question: what is the
benefit of your /new/ syntax ?


Consider it "properties for modules":

a = obj.factory
b = obj.factory

doesn't promise that a is b, because factory might be a property that
returns a new object each time.

Actually, this behaviour pre-dates properties. If obj has a __getattr__
or __getattribute__ method, then it could do the same thing.
 
M

Michele Simionato

Peter Otten said:
import sys
sys.modules["yadda"] = A()

OMG.... wow.  I bow to you.  But I'm not sure whether that's bowing in
awe or in terror.

I had to play this kind of tricks on our production code, not so long
ago. Not that I am pride of it, but it was the lesser evil to cope
with a wrong design. The scenario: a large legacy code base
based on the idea of using a Python module to keep configuration
parameters. The idea is fine as long as the parameters are
immutable, but in our case the parameters could be changed.
In theory the parameters should have been set only once,
however in practice this was not guaranteed: every piece
of code could change the parameters at some moment, and things
could get "interesting" to debug.
Throwing away the configuration system was not an option, because
it would have involved changing hundreds of modules, so I set out
for a compromise: the parameters are still mutable, but they
can be changed only once. This was implemented by replacing
the module with a configuration object using custom
descriptors. Here is the code:

$ echo config.py
import sys

class WriteOnce(object):
"WriteOnce descriptor"
def __init__(self, default):
self.default = self.value = default
self.already_set = False
def __get__(self, obj, objcls):
return self.value
def __set__(self, obj, value):
if value != self.value and self.already_set:
raise TypeError('You cannot set twice a write-once
attribute!')
self.value = value
self.already_set = True

class Config(object):
"A singleton to be used as a module"
parameter = WriteOnce(0)

sys.modules['config'] = Config()

The usage is
Traceback (most recent call last):
...
TypeError: You cannot set twice a write-once attribute!

Just to say that there are use cases where replacing modules with
objects may have sense.
Still, a better design would just have passed immutable configuration
objects around
(changing the configuration would mean to create a new object).
Unfortunately there are still a lot a people without a functional
mindset :-(
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,127
Latest member
CyberDefense
Top