I would access some variables defined in a context from some classes defined in other modules, like I supersimplify in the following:
in main.py
import class_def1
import class_def2
class Starter(object):
#context manager
def __init__(self):
self.att = 'Myfirst'
def __enter__(self):
return self.att
def __exit__(self, exc_type, exc_val, exc_tb):
pass
with Starter() as f:
for name in ('a', 'b', 'c'):
for number in range(2):
c1 = class_def1.MyClass1()
print(c1.attr1)
c2 = class_def2.MyClass2()
print(c2.attr2)
while in class_def1.py:
class MyClass1(object):
global f
global number
def __init__(self):
self.attr1 = ','.join([f,str(number)])
and in class_def2.py:
class MyClass2(object):
def __init__(self):
global f
global name
global number
self.attr2 = ','.join([f,name,str(number)])
This code doesn't work, and I would also like to avoid both the use of "global" and input parameters to init since I have a lot of variables that I would "pass"... Maybe I am asking too much. Any suggestion?
New considerations
Considering the possibility of using dataclasses as DTO (as suggested by @Niel Godfrey Ponciano), I could create a dataclass this way (or maybe even listing also the other variables and setting a default to zero):
@dataclass
class AllInfo:
f: str
with Starter() as f:
info = AllInfo(f)
for name in (a,b,c):
info.name = name
for number in range(2):
info.number = number
c1 = class_def1.MyClass1(info)
print(c1.attr1)
c2 = class_def2.MyClass2(info)
print(c2.attr2)
However, since it is just a matter of storing variables (no methods are required), this is equivalent to:
class AllInfo:
def __init__(self,f):
self.f = f
(the repr method that is automatically added to the dataclass is not relevant in my view).
However, in both cases I need to pass the whole info set. Probably not such a problem, since it is a pointer...
With regards to the __enter__
method of your context manager Starter
, as stated in the docs :
The value returned by this method is bound to the identifier in the as clause of with statements using this context manager.
The value returned is self.att
:
def __enter__(self):
return self.att # Returns the string "Myfirst"
This means that the value of f
will be the string "Myfirst". If what you want for f
is the whole Starter
instance instead of just the attribute Starter.att
, then do return self
instead of return self.att
.
with Starter() as f: # f will be a string "Myfirst"
Now, the next step is to pass that context manager variable ( f
which was from Starter.att
) along with some iterating values name
and number
to your other classes MyClass1
and MyClass2
. One standard way of doing this is by performing a dependency injection , wherein we would inject the dependencies f
, name
, and value
to the target clients which are MyClass1
and MyClass2
. In simpler terms, the design is that MyClass1
and MyClass2
depends on f
, name
, and value
.
You mentioned you don't want to pass to init. But this is one of the standard ways of doing this, and also the simplest. Please proceed to solution-2 below if it fits you better,
class Starter(object):
#context manager
def __init__(self):
self.att = 'Myfirst'
def __enter__(self):
return self.att
def __exit__(self, exc_type, exc_val, exc_tb):
pass
class MyClass2(object):
def __init__(self, f, name, number):
text_list = [f, name, str(number)]
self.attr2 = ','.join(text_list)
with Starter() as f:
for name in ('a', 'b', 'c'):
for number in range(2):
# Inject all dependencies to the class
c2 = MyClass2(f, name, number)
print(c2.attr2)
Output:
Myfirst,a,0
Myfirst,a,1
Myfirst,b,0
Myfirst,b,1
Myfirst,c,0
Myfirst,c,1
If you have a lot of variables but only a selective few of them would be used, this might be applicable. One of the best use cases of this is the builder design pattern .
class Starter(object):
#context manager
def __init__(self):
self.att = 'Myfirst'
def __enter__(self):
return self.att
def __exit__(self, exc_type, exc_val, exc_tb):
pass
class MyClass2(object):
def __init__(self):
self._f = None
self._name = None
self._number = None
# Here, we are using property getters and setters. Of course you can also do it by just directly
# accessing the variable from the outside, just remove the _ in the name to imply that they are
# public and not private as it is now).
@property
def f(self):
return self._f
@f.setter
def f(self, value):
self._f = value
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def number(self):
return self._number
@number.setter
def number(self, value):
self._number = value
@property
def attr2(self):
# Optionally, you can add checks to filter out null variables in the list
text_list = [self.f, self.name, str(self.number)]
return ','.join(text_list)
with Starter() as f:
for name in ('a', 'b', 'c'):
for number in range(2):
c2 = MyClass2()
# Manually inject each dependency to the class. You can add conditionals to select what
# should be injected and what shouldn't.
c2.f = f
c2.name = name
c2.number = number
print(c2.attr2)
Output:
If you have many variables, as suggested by @chepner, you can put them all (or by groups of related data) into its own data structure(s). You can use dataclasses for this purpose (just like struct
in C/C++) which would act as your data transfer object (DTO) .
Python is designed to allow the read of global variables without using the global declaration and If a function needs to modify a variable declared in global scope, it needs to use the global declaration. So the below code should work
class MyClass1(object):
def __init__(self):
self.attr1 = ','.join([f,str(number)])
class MyClass2(object):
def __init__(self):
self.attr2 = ','.join([f,name,str(number)])
class Starter(object):
#context manager
def __init__(self):
self.att = 'Myfirst'
def __enter__(self):
return self.att
def __exit__(self, exc_type, exc_val, exc_tb):
pass
with Starter() as f:
for name in ('a', 'b', 'c'):
for number in range(2):
c1 = MyClass1()
print(c1.attr1)
c2 = MyClass2()
print(c2.attr2)
>>> Myfirst,0
Myfirst,a,0
Myfirst,1
Myfirst,a,1
Myfirst,0
Myfirst,b,0
Myfirst,1
Myfirst,b,1
Myfirst,0
Myfirst,c,0
Myfirst,1
Myfirst,c,1
And if this doesn't help and you really want to access some variable defined in a context in another class you need to pass that variable when initializing. Something like this:
class MyClass1(object):
def __init__(self,f):
self.attr1 = ','.join([f,str(number)])
class MyClass2(object):
def __init__(self,f):
self.attr2 = ','.join([f,name,str(number)])
class Starter(object):
#context manager
def __init__(self):
self.att = 'Myfirst'
def __enter__(self):
return self.att
def __exit__(self, exc_type, exc_val, exc_tb):
pass
with Starter() as f:
for name in ('a', 'b', 'c'):
for number in range(2):
c1 = MyClass1(f)
print(c1.attr1)
c2 = MyClass2(f)
print(c2.attr2)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.