class 中的可订阅 objects

Subscriptable objects in class

我有一个名为 SparsePython 3.4.3 class 来表示稀疏矩阵和它们之间的运算。

连同这个 class 我有函数定义,包括如何重载加法运算、打印函数等。但是,这些函数是在稀疏矩阵表示为字典时编写的,键表示 non-zero 条目和相应的 non-zero 条目作为值。

这些函数在之前的实现中运行良好,但是当稀疏矩阵作为 class 实现时,这种方法无法产生

TypeError: ` 'Sparse' object is not subscriptable`. 

如何避免这个问题?我附上相关代码以供参考。 错误回溯

Traceback (most recent call last):
  File "sparse_driver.py", line 11, in <module>
    print(s)
  File "/mnt/DATA/Dropbox/Courses/Python/HW4/SparseClass.py", line 42, in __str__
    sparse_functions.sparse_print(self)
  File "/mnt/DATA/Dropbox/Courses/Python/HW4/sparse_functions.py", line 56, in sparse_print
    nrow = a['-1'][0]
TypeError: 'Sparse' object is not subscriptable

函数定义

import random
# Deepcopy is imported in order not to change the matrix a itself
from copy import deepcopy
def sparse_gen(num_row, num_col, spar):
    # Total number of elements
    nelem = num_row * num_col
    # Number of nonzero elements
    n_nonzero = int(nelem * spar)
    # Position of nonzero elements
    poselem = random.sample(range(nelem), n_nonzero)
    # Create an empty dictionary here
    spardict = {};
    # Add entries into the dictionary here  in a for loop
    for i in range(len(poselem)):
        # Create a string from the number in poselem and use it as a key
        key = str(poselem[i])
        # Select -1 or 1  randomly
        spardict[key]  = random.choice([-1,1])
    # Add the size of the matrix with the key value -1
    spardict['-1'] = [num_row, num_col]
    # Return the sparse matrix for reference later on
    return spardict
# Here is the function for addition of two matrices
def sparse_add(a, b):
    # First lets check if the matrix sizes are equal or not
    if a['-1'] != b['-1'] :
        print('Matrix sizes should be equal. Exiting!')
        return
    # Copy matrix a into matrix c the sum a + b = c
    c = deepcopy(a)
    # Delete the size info from  b and retrieve the keys
    del b['-1']
    bkeys = b.keys()
    # Write the for loop for summation iterating over keys of b
    for bkey in iter(bkeys):
        if (bkey in a):
            if ((c[bkey] + b[bkey]) == 0):
                del c[bkey]
                continue
            else:
                c[bkey] += b[bkey]
        else:
                c[bkey] = b[bkey]
    # Since it gives rise to problems later readd b['-1']
    b['-1'] = a['-1']
    return c
# The function for printing sparse matrices is defined here
def sparse_print(a):
    # Lets retrieve the size information first (rows, columns)
    # Remember the size information is a list
    nrow = a['-1'][0]
    ncol = a['-1'][1]
    # Lets write a for loop to print out the relevant info
    for i in range(nrow * ncol):
        # if the i is in a print that value otherwise print 0
        return_str = ""
        if str(i) in a:
            return_str += "%3d " %  a[str(i)]
        else:
            return_str += "%3d " % 0
        # Print new line if we hit the end of the row
        if ((i + 1) % nrow) == 0:
            return_str += "\n"
    return_str += "\n\n"# Two new line characters to avoid confusion
    return return_str
# The function for converting sparse matrices to full is defined here
def mat2sparse(a):
    spardict = {}
    num_row = len(a)
    num_col = len(a[0])
    nelem = num_row * num_col
    # Input the size information
    spardict['-1'] = [num_row, num_col]
    for i in range(num_row):
        for j in range(num_col):
            if a[i][j] != 0:
                # Calculate the position of the key
                key = str(i*num_col + j)
                spardict[key] = a[i][j]
    return spardict

Class定义

import sparse_functions
class GetAttr(type):
    def __getitem__(cls, x):
        return getattr(cls, x)
class Sparse:
    __metaclass__ = GetAttr
    """ Class for sparse matrices"""
    # This is the constructor when dimensions and sparsity are specified
    def __init__(self, *args):
        # The name of the helper function is sparse_gen
        if len(args) == 3:
            num_row = args[0]
            num_col = args[1]
            spar = args[2]
            if ((type(args[0]) is not int) or (type(args[1]) is not int)):
                raise TypeError('The first two arguments should be integers!')
            elif not ((args[0] > 0) and (args[1] > 0)):
                raise ValueError('The first two agruments should be positive!')
            elif not ((args[2] < 1) and (args[2] > 0)):
                raise ValueError('Sparsity must be between 0 and 1!')
            self.sparse_rep = sparse_functions.sparse_gen(num_row, num_col, spar)
        elif len(args) == 1:
            if (type(args[0] is not list)):
                raise TypeError('The only argument supplied should be a list!')
            # The list of lists matrix is provided convert it to sparse
            self.sparse_rep = sparse_functions.mat2sparse(arg[0])
        else:
            raise AttributeError('Invalid number of arguments. There should be either one argument or three arguments!')
    # Overload the addition operation
    def __add__(a,b):
        # Here we can make use of the already defined functions
        return sparse_functions.sparse_add(a,b)
    # Overload the subtraction operator
    def __sub__(a,b):
        return sparse_functions.sparse_add(a,-b)
    # Overload the print function
    def __str__(self):
        sparse_functions.sparse_print(self)

Driver

import random
from SparseClass import *
s=Sparse(4,4,0.2)
p=Sparse(4,4,0.3)
# Both of the following lines are problematic
print(s)
s + p

Functions

Class Definition

Driver

...this approach fails yielding a TypeError of 'Sparse' object is not subscriptable. How can I circumvent this issue?

通过使用 inheritance:

class GetAttr:
    def __getitem__(cls, x):
        return getattr(cls, x)

class Sparse(GetAttr):
    x = 10

s = Sparse()
print(s['x'])

--output:--
10

顺便说一下,在 python3.4 __metaclass__ 行中:

class Dog:
    __metaclass__ = SomeClass 

什么都不做。我必须写:

class Dog(metaclass=SomeClass):

关于metaclasses的几点:

  1. 当您将 SomeClass 指定为 metaclass 时,将调用 SomeClass 来创建 Dog class。

  2. 狗 class 将是元class 的 __new__() 方法的 return 值。

  3. metaclass中的其他方法不被继承

这是一个例子:

class MyMetaClass(type):
    def stop(self):
        print('I will stop.')

    def __new__(cls, name, parents, dct):
        dct["start"] = lambda self: print('I will start\n.')  #See explanation below
        return super().__new__(cls, name, parents, dct)

class Dog(metaclass=MyMetaClass):
    pass

d = Dog()
d.start()
d.stop()

--output:--
I will start.

Traceback (most recent call last):
  File "1.py", line 14, in <module>
    d.stop()
AttributeError: 'Dog' object has no attribute 'stop'

请注意,在 Python 中,对象被实现为 dictionaries,这意味着一个实例有一个字典,其中存储了变量,而一个 class 有一个字典,其中有方法和变量存储。在上面的代码中,dct 将是新创建的 class 的 class 字典,而在 __new__() 中,您可以将内容存储在 class 字典中,然后它们将在新 class.

中提供

上面的例子增加了对你的 meta 做这个的可能性class:

class MyMetaClass(type):
    def __getitem__(cls, x):
        return getattr(cls, x)

    def __new__(cls, name, parents, dct):
        dct["__getitem__"] = cls.__getitem__   #<*****HERE
        return super().__new__(cls, name, parents, dct)


class Dog(metaclass=MyMetaClass):
    x = 10

d = Dog()
print(d['x'])

--output:--
10

class 的 __init__() 方法可用于初始化 class 的实例,类似地,metaclass 的 __new__()方法可以用来初始化一个class.

这里的问题是你混淆了 Meta Classes 的使用(classes 是 它们的实例) 和 Super Classes (classes 是他们的 sub-classes).

Meta Classes 作用于 classes,表明它们定义的方法(如 __getitem__)有一个参数,我们通常将其表示为 class 来自:__getitem__(cls, index).

在用户定义 class 上分配自定义元 class 时,用户定义 class 的实例将不会使用自定义元 [=90] 中定义的方法=],他们的 classes 会。

这与 Super Classes 形成对比,我们从中 继承 适用于用户定义 class.[=41 实例的方法=]

在不实施你正在尝试做的事情的情况下,我将举一个小例子来证明这一点(使用 Py3.5),我希望你能理解它的要点。

首先,基础元class定义__getitem__()

class MetaCls(type):
   def __getitem__(cls, index):
       print("Using meta __getitem__ on classes that have my type"

如果我们将 Base Class 定义为使用MetaCls 我们可以看到这适用于 classes 但不适用于 classes:

# metaclass is defined in header:
class Base(metaclass=MetaCls):
    pass

# works on classes
Base[0]
Using meta __getitem__ on classes that have my type

base_instance = Base()

# fails on instances
base_instance[0]  # TypeError!

为了使订阅也适用于实例,我们需要定义适当的 Super Class,我们可以从中子class 或者,在 [=90 中重载我们的 __getitem__ 方法=] Base:

class SuperBase:
    def __getitem__(self, index):
        print("Using SuperBase __getitem__ on instances that derive from classes that subclass me")

class Base(SuperBase, metaclass=MetaCls):
    pass

现在,__getitem__ 适用于 classes 实例,对于 classes 它使用 MetaCls.__getitem__(cls, index) 而对于它使用的实例 SuperBase.__getitem__(self, index):

Base[0]  # meta invocation
Using meta __getitem__ on classes that have my type

base_instance = Base()

base_instance[0]  # super invocation
Using SuperBase __getitem__ on instances that derive from classes that subclass me

最后,不确定您为什么考虑在这种情况下定义自定义元,因为 Super Class 似乎更合适,但我希望您有自己的理由。

无论哪种方式,即使您确实定义了带有重载 __getitem__ 的超级 Class,这也确实会导致另一个错误:

保持你粘贴的其余部分,只添加一个 superclass 而不是 metaclass:

print(d) # Raises the following error:

      1 class GetAttr:
      2     def __getitem__(self, index):
----> 3         return getattr(self, index)
      4 class Sparse(GetAttr):
      5     """ Class for sparse matrices"""

AttributeError: 'Sparse' object has no attribute '-1'

这是有道理的; print(d) 将导致调用 nrow = a['-1'][0],后者将使用 Sparceindex= -1.

的实例调用 __getitem__

__getitem__,因为它的程序将尝试从你的 class Sparce 中获取名为 -1 的属性,这显然不存在。

我建议在 Sparce class 和 内部 __getitem__ 中重载 __getitem__ 执行一些索引操作您的实例中的类数组对象。 (可能 self.sparse_rep ?)