Python Package Building - importing function for u

2019-08-04 10:33发布

问题:

I'm working on building a python package that is basically a wrapper around the request package and designed to make a variety of api calls to a set of databases easier.

Currently, my package has the following directory structure:

package\
  - __init__.py
  - coreFunc.py
  subpackage\
    - __init__.py
    - mod1.py
    - mod2.py

The primary functionality of the package lies in mod1.py and mod2.py. mod1.py looks like this:

import requests as _requests
from package.coreFunc import __func1

class DataPull:
    def __init__(self, arg1):
      self._url = "http://www.somesite.com/info?"
      self._api_param = {'argument':__func1(arg1)}
      self._pull = _requests.get(self._url, params = self._api_param)
    def DataTableOne(self):
        _headers = self._pull.json()['resultSets'][0]['headers']
        _values = self._pull.json()['resultSets'][0]['rowSet']
        return [dict(zip(_headers, value)) for value in _values]
    def DataTableTwo(self):
        _headers = self._pull.json()['resultSets'][1]['headers']
        _values = self._pull.json()['resultSets'][1]['rowSet']
        return [dict(zip(_headers, value)) for value in _values]

within coreFunc.py, I have a few functions that I need to use within specific classes in mod1.py and mod2.py. IN fact, in the third line of the Class definition, I'm using a function (__func1) defined in coreFunc to modify user input to the argument to ensure the correct value is passed to the api call.

coreFunc.py looks like this:

def __func1(x):
    if str(x) == "1999":
    return "1999-00"
elif len(str(x)) == 4:
    try:
        return "-".join([str(x),str(int(x) % 100 + 1)])
    except: 
        raise Exception("Enter the four-digit year")
else: raise Exception("Enter the four-digit year")

I'm using a class for this call because the call results in more than one data table and I am using methods to access each of the data tables.

My issue is that when I try and create an object of class DataPull:

newObj = DataPull(argument)

gives me the following error:

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-24-e3399cf393bd> in <module>()
----> 1 traceback.extract_stack(package.mode1.DataPull("203112"))

C:\Users\Bradley\Anaconda3\lib\site-packages\test-package-py3.4.egg\package\subpackage\mod1.py in __init__(self, arg1)
    140         self._url = "http://www.somesite.com/info?"
--> 141         self._api_param = {"Season":__func1(arg1)}

NameError: name '_DataPull__func1' is not defined

How do I properly import __func1 into mod1.py to fix this error?

回答1:

This is a case of name mangling, anything with __ in front of it within class definition is changed to _classname__attr. Quoting from docs:

Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped. This mangling is done without regard to the syntactic position of the identifier, as long as it occurs within the definition of a class.

This is done at the source code level, so you can change the name of function that is going to be used inside the class definition to something else and it will work fine.


Related CPython code from compile.c:

PyObject *
_Py_Mangle(PyObject *privateobj, PyObject *ident)
{
    /* Name mangling: __private becomes _classname__private.
       This is independent from how the name is used. */
    const char *p, *name = PyString_AsString(ident);
    char *buffer;
    size_t nlen, plen;
    if (privateobj == NULL || !PyString_Check(privateobj) ||
        name == NULL || name[0] != '_' || name[1] != '_') {
        Py_INCREF(ident);
        return ident;
    }
    p = PyString_AsString(privateobj);
    nlen = strlen(name);
    /* Don't mangle __id__ or names with dots.

       The only time a name with a dot can occur is when
       we are compiling an import statement that has a
       package name.

       TODO(jhylton): Decide whether we want to support
       mangling of the module name, e.g. __M.X.
    */
    if ((name[nlen-1] == '_' && name[nlen-2] == '_')
        || strchr(name, '.')) {
        Py_INCREF(ident);
        return ident; /* Don't mangle __whatever__ */
    }
    /* Strip leading underscores from class name */
    while (*p == '_')
        p++;
    if (*p == '\0') {
        Py_INCREF(ident);
        return ident; /* Don't mangle if class is just underscores */
    }
    plen = strlen(p);

    if (plen + nlen >= PY_SSIZE_T_MAX - 1) {
        PyErr_SetString(PyExc_OverflowError,
                        "private identifier too large to be mangled");
        return NULL;
    }

    ident = PyString_FromStringAndSize(NULL, 1 + nlen + plen);
    if (!ident)
        return 0;
    /* ident = "_" + p[:plen] + name # i.e. 1+plen+nlen bytes */
    buffer = PyString_AS_STRING(ident);
    buffer[0] = '_';
    strncpy(buffer+1, p, plen);
    strcpy(buffer+1+plen, name);
    return ident;
}


回答2:

Having two underscores before a function means it is private. You can rename it to _func1 and it will work.

Alternatively, import __func1 as follows:

from package.coreFunc import __func1 as _func1

And use _func1 in __init__ instead of __func1.



标签: python class