Search code examples
python-3.xmoduleinitattributeerror

module has no attribute even though the module actually contains the script


Here is the layout of the simulations I am running

----main directory
                  -----my_script.py
                  -----settings_centroid.py
                  -----utilities (directory)
                  -----gizmo_analysis (directory)
                                                 ----gizmo_analysis.py
                                                 ----gizmo_diagnostic.py
                                                 ----gizmo_file.py
                                                 ----gizmo_ic.py
                                                 ----gizmo_io.py
                                                 ----gizmo_star.py
                                                 ----gizmo_track
                                                 ----gizmo_yield.py
                                                 ----__init__.py
                                                 ----
                  -----gizmo_read (directory)
                                             -----center.py
                                             -----constant.py
                                             -----coordinate.py
                                             -----read.py
                                             -----__init__.py

My my_script.py is:

import gizmo_analysis
import gizmo_read
import utilities as ut
import settings_centroid

settings_centroid.init()
.
.
.

My settings_centroid.py script is:

import utilities as ut
import gizmo_analysis
import rockstar_analysis
import gizmo_read

def init():
    global h, omega_m, omega_l, part, species, properties
    species, properties = 'all', 'all'    
    part=gizmo_read.read.Read.read_snapshot(species, properties, directory='./output/')
.
.
.

My gizmo_analysis.py is:

#!/usr/bin/env python3

from __future__ import absolute_import, division, print_function  # python 2 compatability

import collections
import numpy as np
from numpy import Inf
import matplotlib
from matplotlib import pyplot as plt
from matplotlib.ticker import AutoMinorLocator
from matplotlib import colors
# local ----
import utilities as ut
import gizmo_analysis
import rockstar_analysis
import settings_centroid

settings_centroid.init()
.
.
.

My read.py is:

# system ----
from __future__ import absolute_import, division, print_function  # python 2 compatibility
import collections
import glob
import h5py
import numpy as np
from scipy import integrate, interpolate
# local ----

from . import center, constant, coordinate
import settings_centroid
#from .. import settings_centroid

settings_centroid.init()

snapshot_index = settings_centroid.snapshot_number


# store particles as dictionary class
class DictClass(dict):
    pass


class ReadClass():
    '''
    Read Gizmo snapshot.
    '''

    def __init__(self):
        '''
        Set properties for snapshot files.
        '''
        self.snapshot_name_base = 'snap*[!txt]'  # avoid accidentally reading snapshot indices file
        self.file_extension = '.hdf5'

        self.gas_eos = 5 / 3  # gas equation of state

        # create ordered dictionary to convert particle species name to its id,
        # set all possible species, and set the order in which to read species
        self.species_dict = collections.OrderedDict()
        # dark-matter species
        self.species_dict['dark'] = 1  # dark matter at highest resolution
        self.species_dict['dark.2'] = 2  # dark matter at all lower resolutions
        # baryon species
        self.species_dict['gas'] = 0
        self.species_dict['star'] = 4

        self.species_all = tuple(self.species_dict.keys())
        self.species_read = list(self.species_all)

        # use to translate between element name and index in element table
        self.element_dict = {}
        self.element_dict['total'] = 0
        self.element_dict['he'] = 1
        self.element_dict['c'] = 2
        self.element_dict['n'] = 3
        self.element_dict['o'] = 4
        self.element_dict['ne'] = 5
        self.element_dict['mg'] = 6
        self.element_dict['si'] = 7
        self.element_dict['s'] = 8
        self.element_dict['ca'] = 9
        self.element_dict['fe'] = 10


    def read_snapshot(
        self, species='all', properties='all', directory='.', particle_subsample_factor=None):
        '''
        Read properties for input particle species from simulation snapshot file[s].
        Return particle catalog as a dictionary class.

        Parameters
        ----------
        species : string or list : name[s] of particle species:
            'all' = all species in file
            'star' = stars
            'gas' = gas
            'dark' = dark matter at highest resolution
            'dark.2' = dark matter at lower resolution
        properties : string or list : name[s] of particle properties to read - options:
            'all' = all species in file
            otherwise, list subset from among read_particles.property_dict
                for example: ['mass', 'position', 'velocity']
        directory : string : directory of snapshot file[s]
        particle_subsample_factor : int : factor to periodically subsample particles, to save memory

        Returns
        -------
        part : dictionary class : catalog of particles at snapshot
        '''

        #snapshot_index = snapshot_index  # corresponds to z = 0

        # parse input species to read
        if species == 'all' or species == ['all'] or not species:
            # read all species in snapshot
            species = self.species_all
        else:
            # read subsample of species in snapshot
            if np.isscalar(species):
                species = [species]  # ensure is list
            # check if input species names are valid
            for spec_name in list(species):
                if spec_name not in self.species_dict:
                    species.remove(spec_name)
                    print('! not recognize input species = {}'.format(spec_name))
        self.species_read = list(species)

        # read header from snapshot file
        header = self.read_header(snapshot_index, directory)

        # read particles from snapshot file[s]
        part = self.read_particles(snapshot_index, directory, properties, header)

        # assign auxilliary information to particle dictionary class
        # store header dictionary
        part.info = header
        for spec_name in part:
            part[spec_name].info = part.info

        # get and store cosmological parameters
        part.Cosmology = CosmologyClass(
            header['omega_lambda'], header['omega_matter'], hubble=header['hubble'])
        for spec_name in part:
            part[spec_name].Cosmology = part.Cosmology

        # store information about snapshot time
        time = part.Cosmology.get_time(header['redshift'], 'redshift')
        part.snapshot = {
            'index': snapshot_index,
            'redshift': header['redshift'],
            'scalefactor': header['scalefactor'],
            'time': time,
            'time.lookback': part.Cosmology.get_time(0) - time,
            'time.hubble': constant.Gyr_per_sec / part.Cosmology.get_hubble_parameter(0),
        }
        for spec_name in part:
            part[spec_name].snapshot = part.snapshot

        # adjust properties for each species
        self.adjust_particle_properties(part, header, particle_subsample_factor)

        # assign galaxy center position and velocity, principal axes rotation vectors
        self.read_galaxy_center_coordinates(part, directory)
        # alternately can assign these on the fly
        #center.assign_center(part)
        #center.assign_principal_axes(part)

        # adjust coordinates to be relative to galaxy center position and velocity
        # and aligned with principal axes
        self.adjust_particle_coordinates(part)

        return part

    def read_header(self, snapshot_index=snapshot_index, directory='.'):
        '''
        Read header from snapshot file.

        Parameters
        ----------
        snapshot_index : int : index (number) of snapshot file
        directory : directory of snapshot

        Returns
        -------
        header : dictionary class : header dictionary
        '''
        # convert name in snapshot's header dictionary to custom name preference
        header_dict = {
            # 6-element array of number of particles of each type in file
            'NumPart_ThisFile': 'particle.numbers.in.file',
            # 6-element array of total number of particles of each type (across all files)
            'NumPart_Total': 'particle.numbers.total',
            'NumPart_Total_HighWord': 'particle.numbers.total.high.word',
            # mass of each particle species, if all particles are same
            # (= 0 if they are different, which is usually true)
            'MassTable': 'particle.masses',
            'Time': 'time',  # [Gyr/h]
            'BoxSize': 'box.length',  # [kpc/h comoving]
            'Redshift': 'redshift',
            # number of output files per snapshot
            'NumFilesPerSnapshot': 'file.number.per.snapshot',
            'Omega0': 'omega_matter',
            'OmegaLambda': 'omega_lambda',
            'HubbleParam': 'hubble',
            'Flag_Sfr': 'has.star.formation',
            'Flag_Cooling': 'has.cooling',
            'Flag_StellarAge': 'has.star.age',
            'Flag_Metals': 'has.metals',
            'Flag_Feedback': 'has.feedback',
            'Flag_DoublePrecision': 'has.double.precision',
            'Flag_IC_Info': 'has.ic.info',
            # level of compression of snapshot file
            'CompactLevel': 'compression.level',
            'Compactify_Version': 'compression.version',
            'ReadMe': 'compression.readme',
        }

        header = {}  # dictionary to store header information

        if directory[-1] != '/':
            directory += '/'

        file_name = self.get_snapshot_file_name(directory, snapshot_index)

        print('reading header from:\n  {}'.format(file_name.replace('./', '')))
        print()

        # open snapshot file
        with h5py.File(file_name, 'r') as file_in:
            header_in = file_in['Header'].attrs  # load header dictionary

            for prop_in in header_in:
                prop = header_dict[prop_in]
                header[prop] = header_in[prop_in]  # transfer to custom header dict

        # convert header quantities
        header['scalefactor'] = float(header['time'])
        del(header['time'])
        header['box.length/h'] = float(header['box.length'])
        header['box.length'] /= header['hubble']  # convert to [kpc comoving]

        print('snapshot contains the following number of particles:')
        # keep only species that have any particles
        read_particle_number = 0

        species_read = list(self.species_read)
        for species_name in species_read:
            if species_name not in self.species_all:
                species_read.append(species_name)

        for spec_name in species_read:
            spec_id = self.species_dict[spec_name]
            print('  {:6s} (id = {}): {} particles'.format(
                  spec_name, spec_id, header['particle.numbers.total'][spec_id]))

            if header['particle.numbers.total'][spec_id] > 0:
                read_particle_number += header['particle.numbers.total'][spec_id]
            elif spec_name in self.species_read:
                self.species_read.remove(spec_name)

        if read_particle_number <= 0:
            raise ValueError('snapshot file[s] contain no particles of species = {}'.format(
                             self.species_read))

        print()

        return header

Here is the init.py inside gizmo_analysis:

from __future__ import absolute_import  # python 2 compatability

from . import gizmo_io as io
from . import gizmo_analysis as analysis
from . import gizmo_ic as ic
from . import gizmo_diagnostic as diagnostic
from . import gizmo_file as file
from . import gizmo_track as track
from . import gizmo_star as star

Here is the init.py inside gizmo_read:

#from __future__ import absolute_import  # python 2 compatability


from . import read
from . import center
from . import constant
from . import coordinate
#from .. import settings_centroid

But here is the error message I receive when running my_script.py:

Traceback (most recent call last):
  File "my_script.py", line 6, in <module>
    settings_centroid.init()
  File "/usr5/username/settings_centroid.py", line 9, in init
    part=gizmo_read.read.Read.read_snapshot(species, properties, directory='./output/')
AttributeError: module 'gizmo_read' has no attribute 'read'

My understanding is that I am not aware of the use of inti.py between scripts as opposed to packages. I really do want that all my scripts under main directory actually "see" settings_centroid.py. However, for some reason, I don't think this is happening right now. After implementing the two different changes suggested by Adam, Christian, and J_H, I am still getting error messages.


Solution

  • Edit: I changed my answer after better understanding the problem.


    Your problem comes from circular imports (see this tutorial for instance): your files settings_centroid.py and gizmo_read/read.py both include each other.

    When you import settings_centroid.py, it imports reads.py that directly runs settings_centroid.init(), but at that point, Python didn't load all the symbols that are within settings_centroid.py, hence it doesn't find init().

    Circular imports bring tricky problems to solve. My advice would be to refactor your code to avoid them, which could take a bit of time if your codebase is already big.

    One option, that may not make sense with the logic of your whole code, (if it doesn't, sorry you'll have to think this through) if settings_centroid.py is something of a helper class, is to make a sub-package with it and try to limit its dependency on other modules.

    If you really can't refactor, you can try to limit your imports to function scopes. For instance, settings_centroid.py could become

    import utilities as ut
    import gizmo_analysis
    import rockstar_analysis
    # import gizmo_read <-- important, remove this import
    
    def init():
        import gizmo_read  # <-- do the import here
        global h, omega_m, omega_l, part, species, properties
        species, properties = 'all', 'all'
        part=gizmo_read.read.ReadClass.read_snapshot(species, properties, directory='./output/')
    

    But I'm pretty sure you'll have other similar problems down the line.

    Hope it helps :)