简体   繁体   中英

A way to access context manager variable from class instance in Python?

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 .

Solution 1: Using constructor injection

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

Solution 2: Using setter injection

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:

  • Same as for Solution-1

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM