分类目录归档:知识问答

什么时候应该在Python中使用类?

问题:什么时候应该在Python中使用类?

我已经用python编程了大约两年了。主要是数据(pandas,mpl,numpy),还有自动化脚本和小型Web应用程序。我试图成为一个更好的程序员,并增加我的python知识,而困扰我的一件事是我从未使用过一个类(除了为小型Web应用程序复制随机烧瓶代码外)。我通常理解它们是什么,但是我似乎无法为为什么在一个简单的函数中需要它们的问题而wrap之以鼻。

为了使我的问题更具针对性:我编写了大量的自动报告,这些报告总是涉及从多个数据源(mongo,sql,postgres,api)中提取数据,执行一些或少量的数据整理和格式化,将数据写入csv / excel / html,通过电子邮件发送出去。脚本范围从〜250行到〜600行。我有什么理由要使用类来做到这一点吗?为什么?

I have been programming in python for about two years; mostly data stuff (pandas, mpl, numpy), but also automation scripts and small web apps. I’m trying to become a better programmer and increase my python knowledge and one of the things that bothers me is that I have never used a class (outside of copying random flask code for small web apps). I generally understand what they are, but I can’t seem to wrap my head around why I would need them over a simple function.

To add specificity to my question: I write tons of automated reports which always involve pulling data from multiple data sources (mongo, sql, postgres, apis), performing a lot or a little data munging and formatting, writing the data to csv/excel/html, send it out in an email. The scripts range from ~250 lines to ~600 lines. Would there be any reason for me to use classes to do this and why?


回答 0

类是面向对象程序设计的支柱。OOP高度关注代码的组织,可重用性和封装。

首先,免责声明:OOP与函数式编程在某种程度上相反,后者是Python中经常使用的一种不同范例。并非每个使用Python(或肯定是大多数语言)编程的人都使用OOP。在Java 8中,您可以做很多事情,而这些都不是面向对象的。如果您不想使用OOP,请不要使用。如果您只是编写一次性脚本来处理将不再使用的数据,那么请按原样编写。

但是,使用OOP的原因很多。

原因如下:

  • 组织:OOP定义了在代码中描述和定义数据与过程的众所周知的标准方法。数据和过程都可以存储在不同的定义级别(在不同的类中),并且有谈论这些定义的标准方法。也就是说,如果您以标准方式使用OOP,它将帮助您以后的自己和他人理解,编辑和使用您的代码。同样,您可以使用数据结构的名称并方便地引用它们,而不是使用复杂的任意数据存储机制(命令或列表的命令,集合的命令或列表的命令或其他命令)。

  • 状态:OOP可帮助您定义和跟踪状态。例如,在一个经典的示例中,如果您要创建一个处理学生的程序(例如,年级程序),则可以将您需要的所有有关他们的信息都保留在一个位置(姓名,年龄,性别,年级,类,年级,教师,同龄人,饮食,特殊需求等),并且只要对象还活着并且可以轻松访问,此数据就会保留下来。

  • 封装:通过封装,过程和数据一起存储。方法(功能的OOP术语)与操作和产生的数据一起定义。在像Java这样的允许访问控制的语言中,或者在Python中,取决于您描述公共API的方式,这意味着可以向用户隐藏方法和数据。这意味着,如果您需要或想要更改代码,则可以对代码的实现做任何您想做的事,但要使公共API保持不变。

  • 继承:通过继承,您可以在一个位置(一个类)中定义数据和过程,然后在以后覆盖或扩展该功能。例如,在Python中,我经常看到人们创建dict该类的子类以添加其他功能。常见的更改是覆盖从不存在的字典中请求键以基于未知键提供默认值时引发异常的方法。这允许您现在或以后扩展自己的代码,允许其他人扩展您的代码,并允许您扩展其他人的代码。

  • 可重用性:所有这些原因以及其他原因都可以提高代码的可重用性。面向对象的代码使您可以编写可靠的(经过测试的)代码一次,然后反复使用。如果需要针对特定​​用例进行调整,则可以从现有的类继承并覆盖现有的行为。如果您需要更改某些内容,则可以在保留现有公共方法签名的同时进行全部更改,并且没有一个人是明智的(希望如此)。

同样,有几个原因不使用OOP,而您则不需要。但是幸运的是,使用Python之类的语言,您可以使用一点或很多,这取决于您。

学生用例的一个示例(不能保证代码质量,仅是一个示例):

面向对象

class Student(object):
    def __init__(self, name, age, gender, level, grades=None):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
        self.grades = grades or {}

    def setGrade(self, course, grade):
        self.grades[course] = grade

    def getGrade(self, course):
        return self.grades[course]

    def getGPA(self):
        return sum(self.grades.values())/len(self.grades)

# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})

# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())

标准区

def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}

students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}

# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))

Classes are the pillar of Object Oriented Programming. OOP is highly concerned with code organization, reusability, and encapsulation.

First, a disclaimer: OOP is partially in contrast to Functional Programming, which is a different paradigm used a lot in Python. Not everyone who programs in Python (or surely most languages) uses OOP. You can do a lot in Java 8 that isn’t very Object Oriented. If you don’t want to use OOP, then don’t. If you’re just writing one-off scripts to process data that you’ll never use again, then keep writing the way you are.

However, there are a lot of reasons to use OOP.

Some reasons:

  • Organization: OOP defines well known and standard ways of describing and defining both data and procedure in code. Both data and procedure can be stored at varying levels of definition (in different classes), and there are standard ways about talking about these definitions. That is, if you use OOP in a standard way, it will help your later self and others understand, edit, and use your code. Also, instead of using a complex, arbitrary data storage mechanism (dicts of dicts or lists or dicts or lists of dicts of sets, or whatever), you can name pieces of data structures and conveniently refer to them.

  • State: OOP helps you define and keep track of state. For instance, in a classic example, if you’re creating a program that processes students (for instance, a grade program), you can keep all the info you need about them in one spot (name, age, gender, grade level, courses, grades, teachers, peers, diet, special needs, etc.), and this data is persisted as long as the object is alive, and is easily accessible.

  • Encapsulation: With encapsulation, procedure and data are stored together. Methods (an OOP term for functions) are defined right alongside the data that they operate on and produce. In a language like Java that allows for access control, or in Python, depending upon how you describe your public API, this means that methods and data can be hidden from the user. What this means is that if you need or want to change code, you can do whatever you want to the implementation of the code, but keep the public APIs the same.

  • Inheritance: Inheritance allows you to define data and procedure in one place (in one class), and then override or extend that functionality later. For instance, in Python, I often see people creating subclasses of the dict class in order to add additional functionality. A common change is overriding the method that throws an exception when a key is requested from a dictionary that doesn’t exist to give a default value based on an unknown key. This allows you to extend your own code now or later, allow others to extend your code, and allows you to extend other people’s code.

  • Reusability: All of these reasons and others allow for greater reusability of code. Object oriented code allows you to write solid (tested) code once, and then reuse over and over. If you need to tweak something for your specific use case, you can inherit from an existing class and overwrite the existing behavior. If you need to change something, you can change it all while maintaining the existing public method signatures, and no one is the wiser (hopefully).

Again, there are several reasons not to use OOP, and you don’t need to. But luckily with a language like Python, you can use just a little bit or a lot, it’s up to you.

An example of the student use case (no guarantee on code quality, just an example):

Object Oriented

class Student(object):
    def __init__(self, name, age, gender, level, grades=None):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
        self.grades = grades or {}

    def setGrade(self, course, grade):
        self.grades[course] = grade

    def getGrade(self, course):
        return self.grades[course]

    def getGPA(self):
        return sum(self.grades.values())/len(self.grades)

# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})

# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())

Standard Dict

def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}

students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}

# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))

回答 1

每当您需要维护函数状态时,都无法使用生成器来实现(生成而不是返回的函数)。生成器保持自己的状态。

如果要覆盖任何标准运算符,则需要一个类。

每当您用于访问者模式时,就需要使用类。使用生成器,上下文管理器(与生成器相比,比作为类更好地实现)和POD类型(字典,列表和元组等),可以更有效,更干净地完成所有其他设计模式。

如果要编写“ pythonic”代码,则应优先使用上下文管理器和生成器,而不要使用类。会更干净。

如果要扩展功能,几乎总是可以通过包含而不是继承来实现它。

每个规则都有exceptions。如果要快速封装功能(即编写测试代码而不是库级别的可重用代码),则可以将状态封装在类中。这很简单,不需要重用。

如果您需要C ++样式析构函数(RIIA),则绝对不希望使用类。您需要上下文管理器。

Whenever you need to maintain a state of your functions and it cannot be accomplished with generators (functions which yield rather than return). Generators maintain their own state.

If you want to override any of the standard operators, you need a class.

Whenever you have a use for a Visitor pattern, you’ll need classes. Every other design pattern can be accomplished more effectively and cleanly with generators, context managers (which are also better implemented as generators than as classes) and POD types (dictionaries, lists and tuples, etc.).

If you want to write “pythonic” code, you should prefer context managers and generators over classes. It will be cleaner.

If you want to extend functionality, you will almost always be able to accomplish it with containment rather than inheritance.

As every rule, this has an exception. If you want to encapsulate functionality quickly (ie, write test code rather than library-level reusable code), you can encapsulate the state in a class. It will be simple and won’t need to be reusable.

If you need a C++ style destructor (RIIA), you definitely do NOT want to use classes. You want context managers.


回答 2

我想你做对了。当您需要模拟一些业务逻辑或具有困难关系的困难现实过程时,类是合理的。例如:

  • 具有共享状态的几个功能
  • 多个相同状态变量的副本
  • 扩展现有功能的行为

我也建议您观看这部经典影片

I think you do it right. Classes are reasonable when you need to simulate some business logic or difficult real-life processes with difficult relations. As example:

  • Several functions with share state
  • More than one copy of the same state variables
  • To extend the behavior of an existing functionality

I also suggest you to watch this classic video


回答 3

一个类定义了一个现实世界的实体。如果您正在处理独立存在的事物,并且具有与其他事物不同的自己的逻辑,则应该为其创建一个类。例如,一个封装数据库连接的类。

如果不是这种情况,则无需创建类

A class defines a real world entity. If you are working on something that exists individually and has its own logic that is separate from others, you should create a class for it. For example, a class that encapsulates database connectivity.

If this not the case, no need to create class


回答 4

这取决于您的想法和设计。如果您是一位优秀的设计师,那么OOP会以各种设计模式的形式自然出现。对于简单的脚本级别,处理OOP可能会产生开销。简单考虑一下OOP的基本好处,例如可重用和可扩展,并确定是否需要它们。OOP使复杂的事情变得越来越简单。使用OOP或不使用OOP都可以使事情简单。使用哪个更简单。

Its depends on your idea and design. if you are good designer than OOPs will come out naturally in the form of various design patterns. For a simple script level processing OOPs can be overhead. Simple consider the basic benefits of OOPs like reusable and extendable and make sure if they are needed or not. OOPs make complex things simpler and simpler things complex. Simply keeps the things simple in either way using OOPs or not Using OOPs. which ever is simpler use that.


OpenCV –未校准立体声系统的深度图

问题:OpenCV –未校准立体声系统的深度图

我正在尝试使用未经校准的方法获得深度图。我可以通过使用SIFT查找对应点然后使用来获得基本矩阵cv2.findFundamentalMat。然后cv2.stereoRectifyUncalibrated,我用于获取每个图像的单应性矩阵。最后,我使用它cv2.warpPerspective来校正和计算视差,但这并不能创建良好的深度图。值非常高,所以我想知道是否必须使用warpPerspective或是否必须根据所获得的单应性矩阵计算旋转矩阵stereoRectifyUncalibrated

我不确定投影矩阵是否与通过校正得到的单应矩阵有关stereoRectifyUncalibrated

代码的一部分:

#Obtainment of the correspondent point with SIFT
sift = cv2.SIFT()

###find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(dst1,None)
kp2, des2 = sift.detectAndCompute(dst2,None)

###FLANN parameters
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)

flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

good = []
pts1 = []
pts2 = []

###ratio test as per Lowe's paper
for i,(m,n) in enumerate(matches):
    if m.distance < 0.8*n.distance:
        good.append(m)
        pts2.append(kp2[m.trainIdx].pt)
        pts1.append(kp1[m.queryIdx].pt)
    
    
pts1 = np.array(pts1)
pts2 = np.array(pts2)

#Computation of the fundamental matrix
F,mask= cv2.findFundamentalMat(pts1,pts2,cv2.FM_LMEDS)


# Obtainment of the rectification matrix and use of the warpPerspective to transform them...
pts1 = pts1[:,:][mask.ravel()==1]
pts2 = pts2[:,:][mask.ravel()==1]

pts1 = np.int32(pts1)
pts2 = np.int32(pts2)

p1fNew = pts1.reshape((pts1.shape[0] * 2, 1))
p2fNew = pts2.reshape((pts2.shape[0] * 2, 1))
    
retBool ,rectmat1, rectmat2 = cv2.stereoRectifyUncalibrated(p1fNew,p2fNew,F,(2048,2048))

dst11 = cv2.warpPerspective(dst1,rectmat1,(2048,2048))
dst22 = cv2.warpPerspective(dst2,rectmat2,(2048,2048))

#calculation of the disparity
stereo = cv2.StereoBM(cv2.STEREO_BM_BASIC_PRESET,ndisparities=16*10, SADWindowSize=9)
disp = stereo.compute(dst22.astype(uint8), dst11.astype(uint8)).astype(np.float32)
plt.imshow(disp);plt.colorbar();plt.clim(0,400)#;plt.show()
plt.savefig("0gauche.png")

#plot depth by using disparity focal length `C1[0,0]` from stereo calibration and `T[0]` the distance between cameras

plt.imshow(C1[0,0]*T[0]/(disp),cmap='hot');plt.clim(-0,500);plt.colorbar();plt.show()

这是使用未校准方法(和warpPerspective)校正后的照片:

在此处输入图片说明

这是使用校准方法校正后的图片:

在此处输入图片说明

我不知道两种图片之间的区别有多么重要。对于校准方法,它似乎没有对齐。

使用未校准方法的视差图:

在此处输入图片说明

:深度计算与C1[0,0]*T[0]/(disp) 从以T stereoCalibrate。该值很高。

————编辑后————

我试图用通过“ stereoRectifyUncalibrated”获得的单应性矩阵“装载”重构矩阵([Devernay97][Garcia01]),但结果仍然不理想。我这样做正确吗?

Y=np.arange(0,2048)
X=np.arange(0,2048)
(XX_field,YY_field)=np.meshgrid(X,Y)

#I mount the X, Y and disparity in a same 3D array 
stock = np.concatenate((np.expand_dims(XX_field,2),np.expand_dims(YY_field,2)),axis=2)
XY_disp = np.concatenate((stock,np.expand_dims(disp,2)),axis=2)

XY_disp_reshape = XY_disp.reshape(XY_disp.shape[0]*XY_disp.shape[1],3)

Ts = np.hstack((np.zeros((3,3)),T_0)) #i use only the translations obtained with the rectified calibration...Is it correct?


# I establish the projective matrix with the homography matrix
P11 = np.dot(rectmat1,C1)
P1 = np.vstack((np.hstack((P11,np.zeros((3,1)))),np.zeros((1,4))))
P1[3,3] = 1

# P1 = np.dot(C1,np.hstack((np.identity(3),np.zeros((3,1)))))

P22 = np.dot(np.dot(rectmat2,C2),Ts)
P2 = np.vstack((P22,np.zeros((1,4))))
P2[3,3] = 1

lambda_t = cv2.norm(P1[0,:].T)/cv2.norm(P2[0,:].T)


#I define the reconstruction matrix
Q = np.zeros((4,4))

Q[0,:] = P1[0,:].T
Q[1,:] = P1[1,:].T
Q[2,:] = lambda_t*P2[1,:].T - P1[1,:].T
Q[3,:] = P1[2,:].T

#I do the calculation to get my 3D coordinates
test = []
for i in range(0,XY_disp_reshape.shape[0]):
    a = np.dot(inv(Q),np.expand_dims(np.concatenate((XY_disp_reshape[i,:],np.ones((1))),axis=0),axis=1))
    test.append(a)

test = np.asarray(test)

XYZ = test[:,:,0].reshape(XY_disp.shape[0],XY_disp.shape[1],4)

I’m trying to get a depth map with an uncalibrated method. I can obtain the fundamental matrix by finding correspondent points with SIFT and then using cv2.findFundamentalMat. I then use cv2.stereoRectifyUncalibrated to get the homography matrices for each image. Finally I use cv2.warpPerspective to rectify and compute the disparity, but this doesn’t create a good depth map. The values are very high so I’m wondering if I have to use warpPerspective or if I have to calculate a rotation matrix from the homography matrices I got with stereoRectifyUncalibrated.

I’m not sure of the projective matrix with the case of homography matrix obtained with the stereoRectifyUncalibrated to rectify.

A part of the code:

#Obtainment of the correspondent point with SIFT
sift = cv2.SIFT()

###find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(dst1,None)
kp2, des2 = sift.detectAndCompute(dst2,None)

###FLANN parameters
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)

flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

good = []
pts1 = []
pts2 = []

###ratio test as per Lowe's paper
for i,(m,n) in enumerate(matches):
    if m.distance < 0.8*n.distance:
        good.append(m)
        pts2.append(kp2[m.trainIdx].pt)
        pts1.append(kp1[m.queryIdx].pt)
    
    
pts1 = np.array(pts1)
pts2 = np.array(pts2)

#Computation of the fundamental matrix
F,mask= cv2.findFundamentalMat(pts1,pts2,cv2.FM_LMEDS)


# Obtainment of the rectification matrix and use of the warpPerspective to transform them...
pts1 = pts1[:,:][mask.ravel()==1]
pts2 = pts2[:,:][mask.ravel()==1]

pts1 = np.int32(pts1)
pts2 = np.int32(pts2)

p1fNew = pts1.reshape((pts1.shape[0] * 2, 1))
p2fNew = pts2.reshape((pts2.shape[0] * 2, 1))
    
retBool ,rectmat1, rectmat2 = cv2.stereoRectifyUncalibrated(p1fNew,p2fNew,F,(2048,2048))

dst11 = cv2.warpPerspective(dst1,rectmat1,(2048,2048))
dst22 = cv2.warpPerspective(dst2,rectmat2,(2048,2048))

#calculation of the disparity
stereo = cv2.StereoBM(cv2.STEREO_BM_BASIC_PRESET,ndisparities=16*10, SADWindowSize=9)
disp = stereo.compute(dst22.astype(uint8), dst11.astype(uint8)).astype(np.float32)
plt.imshow(disp);plt.colorbar();plt.clim(0,400)#;plt.show()
plt.savefig("0gauche.png")

#plot depth by using disparity focal length `C1[0,0]` from stereo calibration and `T[0]` the distance between cameras

plt.imshow(C1[0,0]*T[0]/(disp),cmap='hot');plt.clim(-0,500);plt.colorbar();plt.show()

Here are the rectified pictures with the uncalibrated method (and warpPerspective):

enter image description here

Here are the rectified pictures with the calibrated method:

enter image description here

I don’t know how the difference is so important between the two kind of pictures. And for the calibrated method, it doesn’t seem aligned.

The disparity map using the uncalibrated method:

enter image description here

The depths are calculated with : C1[0,0]*T[0]/(disp) with T from the stereoCalibrate. The values are very high.

———— EDIT LATER ————

I tried to “mount” the reconstruction matrix ([Devernay97], [Garcia01]) with the homography matrix obtained with “stereoRectifyUncalibrated”, but the result is still not good. Am I doing this correctly?

Y=np.arange(0,2048)
X=np.arange(0,2048)
(XX_field,YY_field)=np.meshgrid(X,Y)

#I mount the X, Y and disparity in a same 3D array 
stock = np.concatenate((np.expand_dims(XX_field,2),np.expand_dims(YY_field,2)),axis=2)
XY_disp = np.concatenate((stock,np.expand_dims(disp,2)),axis=2)

XY_disp_reshape = XY_disp.reshape(XY_disp.shape[0]*XY_disp.shape[1],3)

Ts = np.hstack((np.zeros((3,3)),T_0)) #i use only the translations obtained with the rectified calibration...Is it correct?


# I establish the projective matrix with the homography matrix
P11 = np.dot(rectmat1,C1)
P1 = np.vstack((np.hstack((P11,np.zeros((3,1)))),np.zeros((1,4))))
P1[3,3] = 1

# P1 = np.dot(C1,np.hstack((np.identity(3),np.zeros((3,1)))))

P22 = np.dot(np.dot(rectmat2,C2),Ts)
P2 = np.vstack((P22,np.zeros((1,4))))
P2[3,3] = 1

lambda_t = cv2.norm(P1[0,:].T)/cv2.norm(P2[0,:].T)


#I define the reconstruction matrix
Q = np.zeros((4,4))

Q[0,:] = P1[0,:].T
Q[1,:] = P1[1,:].T
Q[2,:] = lambda_t*P2[1,:].T - P1[1,:].T
Q[3,:] = P1[2,:].T

#I do the calculation to get my 3D coordinates
test = []
for i in range(0,XY_disp_reshape.shape[0]):
    a = np.dot(inv(Q),np.expand_dims(np.concatenate((XY_disp_reshape[i,:],np.ones((1))),axis=0),axis=1))
    test.append(a)

test = np.asarray(test)

XYZ = test[:,:,0].reshape(XY_disp.shape[0],XY_disp.shape[1],4)

回答 0

TLDR;对边缘更平滑的图像使用StereoSGBM(半全局块匹配),如果您希望它仍然更平滑,请使用一些后期过滤

OP没有提供原始图像,因此我使用Tsukuba的是Middlebury数据集

常规StereoBM的结果

立体声

StereoSGBM的结果(已调整)

立体声

我在文学中能找到的最好结果

在此处输入图片说明

有关详细信息,请参见此处的出版物。

后过滤示例(请参见下面的链接)

后置过滤器示例

OP的问题的理论/其他考虑

校正后的校正图像中较大的黑色区域使我相信,对于这些校正效果不是很好。可能有多种原因在起作用,例如物理设置,校准时可能亮起的照明等等,但是为此有很多相机校准教程,我的理解是,您正在寻找一种方法来从未经校准的设置中获得更好的深度图(虽然不是100%清晰,但是标题似乎支持这一点,我认为这就是人们来这里寻找的地方)。

您的基本方法是正确的,但结果肯定可以改善。深度映射的这种形式不在产生最高质量的映射的那些之中(尤其是未经校准的)。最大的改进可能来自使用不同的立体声匹配算法。照明也可能会产生重大影响。正确的图像(至少对我的肉眼而言)似乎光线不足,可能会干扰重建。您可以先尝试将其亮度提高到另一个水平,或者在可能的情况下收集新图像。从这里开始,我将假定您无权使用原始相机,因此,我将考虑收集新图像,更改设置或执行校准超出范围。(如果您确实有权访问设置和摄像机,

您过去曾StereoBM计算过有效的视差(深度图),但StereoSGBM更适合此应用程序(更好地处理更平滑的边缘)。您可以在下面看到区别。

本文更深入地解释了这些差异:

块匹配专注于高纹理图像(想像一棵树的图片),而半全局块匹配则专注于子像素级匹配和纹理更平滑的图片(想像走廊的图片)。

如果没有任何明确的固有摄像机参数,有关摄像机设置的详细信息(例如焦距,摄像机之间的距离,到被摄物体的距离等),图像中的已知尺寸或运动(以使用来自运动的结构),则可以仅获得3D重建,直到投影变换;您也不会有比例感或旋转感,但是仍然可以生成相对深度图。您可能会遭受一些镜筒变形和其他变形的困扰,这些变形可以通过适当的相机校准来消除,但是只要相机不可怕(镜头系统不太失真)并且设置得相当漂亮,您就可以获得合理的结果。接近规范配置(这基本上意味着它们的方向应使其光轴尽可能接近平行,并且它们的视场充分重叠)。但是,这似乎不是OP的问题,因为他确实设法通过未校准的方法获得了正确的校正图像。

基本程序

  1. 在两张图像中至少找到5个匹配良好的点,可以用来计算基本矩阵(可以使用任何喜欢的检测器和匹配器,我保留了FLANN,但是使用ORB进行了检测,因为SIFT不在OpenCV的主版本中对于4.2.0)
  2. 用以下公式计算基本矩阵F findFundamentalMat
  3. 使用stereoRectifyUncalibrated和取消图像失真warpPerspective
  4. 计算视差(深度图) StereoSGBM

结果要好得多:

与ORB和FLANN匹配

火柴

未失真的图像(先左后右)

左未变形
正确的权利

差距

立体声BM

此结果看起来与OP的问题(斑点,间隙,某些区域的错误深度)相似。

立体声

StereoSGBM(已调整)

这个结果看起来要好得多,并且使用与OP大致相同的方法,减去最终的视差计算,这让我认为,如果提供OP,OP将在他的图像上看到类似的改进。

立体声

后过滤

OpenCV文档中有一篇很好的文章。如果您需要非常平滑的地图,建议您查看一下。

上面的示例照片是MPI Sintel数据ambush_2集中场景的第1帧。

后置过滤器示例

完整代码(在OpenCV 4.2.0上测试):

import cv2
import numpy as np
import matplotlib.pyplot as plt

imgL = cv2.imread("tsukuba_l.png", cv2.IMREAD_GRAYSCALE)  # left image
imgR = cv2.imread("tsukuba_r.png", cv2.IMREAD_GRAYSCALE)  # right image


def get_keypoints_and_descriptors(imgL, imgR):
    """Use ORB detector and FLANN matcher to get keypoints, descritpors,
    and corresponding matches that will be good for computing
    homography.
    """
    orb = cv2.ORB_create()
    kp1, des1 = orb.detectAndCompute(imgL, None)
    kp2, des2 = orb.detectAndCompute(imgR, None)

    ############## Using FLANN matcher ##############
    # Each keypoint of the first image is matched with a number of
    # keypoints from the second image. k=2 means keep the 2 best matches
    # for each keypoint (best matches = the ones with the smallest
    # distance measurement).
    FLANN_INDEX_LSH = 6
    index_params = dict(
        algorithm=FLANN_INDEX_LSH,
        table_number=6,  # 12
        key_size=12,  # 20
        multi_probe_level=1,
    )  # 2
    search_params = dict(checks=50)  # or pass empty dictionary
    flann = cv2.FlannBasedMatcher(index_params, search_params)
    flann_match_pairs = flann.knnMatch(des1, des2, k=2)
    return kp1, des1, kp2, des2, flann_match_pairs


def lowes_ratio_test(matches, ratio_threshold=0.6):
    """Filter matches using the Lowe's ratio test.

    The ratio test checks if matches are ambiguous and should be
    removed by checking that the two distances are sufficiently
    different. If they are not, then the match at that keypoint is
    ignored.

    /programming/51197091/how-does-the-lowes-ratio-test-work
    """
    filtered_matches = []
    for m, n in matches:
        if m.distance < ratio_threshold * n.distance:
            filtered_matches.append(m)
    return filtered_matches


def draw_matches(imgL, imgR, kp1, des1, kp2, des2, flann_match_pairs):
    """Draw the first 8 mathces between the left and right images."""
    # https://docs.opencv.org/4.2.0/d4/d5d/group__features2d__draw.html
    # https://docs.opencv.org/2.4/modules/features2d/doc/common_interfaces_of_descriptor_matchers.html
    img = cv2.drawMatches(
        imgL,
        kp1,
        imgR,
        kp2,
        flann_match_pairs[:8],
        None,
        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,
    )
    cv2.imshow("Matches", img)
    cv2.imwrite("ORB_FLANN_Matches.png", img)
    cv2.waitKey(0)


def compute_fundamental_matrix(matches, kp1, kp2, method=cv2.FM_RANSAC):
    """Use the set of good mathces to estimate the Fundamental Matrix.

    See  https://en.wikipedia.org/wiki/Eight-point_algorithm#The_normalized_eight-point_algorithm
    for more info.
    """
    pts1, pts2 = [], []
    fundamental_matrix, inliers = None, None
    for m in matches[:8]:
        pts1.append(kp1[m.queryIdx].pt)
        pts2.append(kp2[m.trainIdx].pt)
    if pts1 and pts2:
        # You can play with the Threshold and confidence values here
        # until you get something that gives you reasonable results. I
        # used the defaults
        fundamental_matrix, inliers = cv2.findFundamentalMat(
            np.float32(pts1),
            np.float32(pts2),
            method=method,
            # ransacReprojThreshold=3,
            # confidence=0.99,
        )
    return fundamental_matrix, inliers, pts1, pts2


############## Find good keypoints to use ##############
kp1, des1, kp2, des2, flann_match_pairs = get_keypoints_and_descriptors(imgL, imgR)
good_matches = lowes_ratio_test(flann_match_pairs, 0.2)
draw_matches(imgL, imgR, kp1, des1, kp2, des2, good_matches)


############## Compute Fundamental Matrix ##############
F, I, points1, points2 = compute_fundamental_matrix(good_matches, kp1, kp2)


############## Stereo rectify uncalibrated ##############
h1, w1 = imgL.shape
h2, w2 = imgR.shape
thresh = 0
_, H1, H2 = cv2.stereoRectifyUncalibrated(
    np.float32(points1), np.float32(points2), F, imgSize=(w1, h1), threshold=thresh,
)

############## Undistort (Rectify) ##############
imgL_undistorted = cv2.warpPerspective(imgL, H1, (w1, h1))
imgR_undistorted = cv2.warpPerspective(imgR, H2, (w2, h2))
cv2.imwrite("undistorted_L.png", imgL_undistorted)
cv2.imwrite("undistorted_R.png", imgR_undistorted)

############## Calculate Disparity (Depth Map) ##############

# Using StereoBM
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)
disparity_BM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_BM, "gray")
plt.colorbar()
plt.show()

# Using StereoSGBM
# Set disparity parameters. Note: disparity range is tuned according to
#  specific parameters obtained through trial and error.
win_size = 2
min_disp = -4
max_disp = 9
num_disp = max_disp - min_disp  # Needs to be divisible by 16
stereo = cv2.StereoSGBM_create(
    minDisparity=min_disp,
    numDisparities=num_disp,
    blockSize=5,
    uniquenessRatio=5,
    speckleWindowSize=5,
    speckleRange=5,
    disp12MaxDiff=2,
    P1=8 * 3 * win_size ** 2,
    P2=32 * 3 * win_size ** 2,
)
disparity_SGBM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_SGBM, "gray")
plt.colorbar()
plt.show()

TLDR; Use StereoSGBM (Semi Global Block Matching) for images with smoother edges and use some post filtering if you want it smoother still

OP didn’t provide original images, so I’m using Tsukuba from the Middlebury data set.

Result with regular StereoBM

stereobm

Result with StereoSGBM (tuned)

stereosgbm

Best result I could find in literature

enter image description here

See the publication here for details.

Example of post filtering (see link below)

post filter example

Theory/Other considerations from OP’s question

The large black areas of your calibrated rectified images would lead me to believe that for those, calibration was not done very well. There’s a variety of reasons that could be at play, maybe the physical setup, maybe lighting when you did calibration, etc., but there are plenty of camera calibration tutorials out there for that and my understanding is that you are asking for a way to get a better depth map from an uncalibrated setup (this isn’t 100% clear, but the title seems to support this and I think that’s what people will come here to try to find).

Your basic approach is correct, but the results can definitely be improved. This form of depth mapping is not among those that produce the highest quality maps (especially being uncalibrated). The biggest improvement will likely come from using a different stereo matching algorithm. The lighting may also be having a significant effect. The right image (at least to my naked eye) appears to be less well lit which could interfere with the reconstruction. You could first try brightening it to the same level as the other, or gather new images if that is possible. From here out, I’ll assume you have no access to the original cameras, so I’ll consider gathering new images, altering the setup, or performing calibration to be out of scope. (If you do have access to the setup and cameras, then I would suggest checking calibration and using a calibrated method as this will work better).

You used StereoBM for calculating your disparity (depth map) which does work, but StereoSGBM is much better suited for this application (it handles smoother edges better). You can see the difference below.

This article explains the differences in more depth:

Block matching focuses on high texture images (think a picture of a tree) and semi-global block matching will focus on sub pixel level matching and pictures with more smooth textures (think a picture of a hallway).

Without any explicit intrinsic camera parameters, specifics about the camera setup (like focal distance, distance between the cameras, distance to the subject, etc.), a known dimension in the image, or motion (to use structure from motion), you can only obtain 3D reconstruction up to a projective transform; you won’t have a sense of scale or necessarily rotation either, but you can still generate a relative depth map. You will likely suffer from some barrel and other distortions which could be removed with proper camera calibration, but you can get reasonable results without it as long as the cameras aren’t terrible (lens system isn’t too distorted) and are set up pretty close to canonical configuration (which basically means they are oriented such that their optical axes are as close to parallel as possible, and their fields of view overlap sufficiently). This doesn’t however appear to be the OPs issue as he did manage to get alright rectified images with the uncalibrated method.

Basic Procedure

  1. Find at least 5 well-matched points in both images you can use to calculate the Fundamental Matrix (you can use any detector and matcher you like, I kept FLANN but used ORB to do detection as SIFT isn’t in the main version of OpenCV for 4.2.0)
  2. Calculate the Fundamental Matrix, F, with findFundamentalMat
  3. Undistort your images with stereoRectifyUncalibrated and warpPerspective
  4. Calculate Disparity (Depth Map) with StereoSGBM

The results are much better:

Matches with ORB and FLANN

Matches

Undistorted images (left, then right)

undistorted left
undistorted right

Disparity

StereoBM

This result looks similar to the OPs problems (speckling, gaps, wrong depths in some areas).

stereobm

StereoSGBM (tuned)

This result looks much better and uses roughly the same method as the OP, minus the final disparity calculation, making me think the OP would see similar improvements on his images, had they been provided.

stereosgbm

Post filtering

There’s a good article about this in the OpenCV docs. I’d recommend looking at it if you need really smooth maps.

The example photos above are frame 1 from the scene ambush_2 in the MPI Sintel Dataset.

post filter example

Full code (Tested on OpenCV 4.2.0):

import cv2
import numpy as np
import matplotlib.pyplot as plt

imgL = cv2.imread("tsukuba_l.png", cv2.IMREAD_GRAYSCALE)  # left image
imgR = cv2.imread("tsukuba_r.png", cv2.IMREAD_GRAYSCALE)  # right image


def get_keypoints_and_descriptors(imgL, imgR):
    """Use ORB detector and FLANN matcher to get keypoints, descritpors,
    and corresponding matches that will be good for computing
    homography.
    """
    orb = cv2.ORB_create()
    kp1, des1 = orb.detectAndCompute(imgL, None)
    kp2, des2 = orb.detectAndCompute(imgR, None)

    ############## Using FLANN matcher ##############
    # Each keypoint of the first image is matched with a number of
    # keypoints from the second image. k=2 means keep the 2 best matches
    # for each keypoint (best matches = the ones with the smallest
    # distance measurement).
    FLANN_INDEX_LSH = 6
    index_params = dict(
        algorithm=FLANN_INDEX_LSH,
        table_number=6,  # 12
        key_size=12,  # 20
        multi_probe_level=1,
    )  # 2
    search_params = dict(checks=50)  # or pass empty dictionary
    flann = cv2.FlannBasedMatcher(index_params, search_params)
    flann_match_pairs = flann.knnMatch(des1, des2, k=2)
    return kp1, des1, kp2, des2, flann_match_pairs


def lowes_ratio_test(matches, ratio_threshold=0.6):
    """Filter matches using the Lowe's ratio test.

    The ratio test checks if matches are ambiguous and should be
    removed by checking that the two distances are sufficiently
    different. If they are not, then the match at that keypoint is
    ignored.

    https://stackoverflow.com/questions/51197091/how-does-the-lowes-ratio-test-work
    """
    filtered_matches = []
    for m, n in matches:
        if m.distance < ratio_threshold * n.distance:
            filtered_matches.append(m)
    return filtered_matches


def draw_matches(imgL, imgR, kp1, des1, kp2, des2, flann_match_pairs):
    """Draw the first 8 mathces between the left and right images."""
    # https://docs.opencv.org/4.2.0/d4/d5d/group__features2d__draw.html
    # https://docs.opencv.org/2.4/modules/features2d/doc/common_interfaces_of_descriptor_matchers.html
    img = cv2.drawMatches(
        imgL,
        kp1,
        imgR,
        kp2,
        flann_match_pairs[:8],
        None,
        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,
    )
    cv2.imshow("Matches", img)
    cv2.imwrite("ORB_FLANN_Matches.png", img)
    cv2.waitKey(0)


def compute_fundamental_matrix(matches, kp1, kp2, method=cv2.FM_RANSAC):
    """Use the set of good mathces to estimate the Fundamental Matrix.

    See  https://en.wikipedia.org/wiki/Eight-point_algorithm#The_normalized_eight-point_algorithm
    for more info.
    """
    pts1, pts2 = [], []
    fundamental_matrix, inliers = None, None
    for m in matches[:8]:
        pts1.append(kp1[m.queryIdx].pt)
        pts2.append(kp2[m.trainIdx].pt)
    if pts1 and pts2:
        # You can play with the Threshold and confidence values here
        # until you get something that gives you reasonable results. I
        # used the defaults
        fundamental_matrix, inliers = cv2.findFundamentalMat(
            np.float32(pts1),
            np.float32(pts2),
            method=method,
            # ransacReprojThreshold=3,
            # confidence=0.99,
        )
    return fundamental_matrix, inliers, pts1, pts2


############## Find good keypoints to use ##############
kp1, des1, kp2, des2, flann_match_pairs = get_keypoints_and_descriptors(imgL, imgR)
good_matches = lowes_ratio_test(flann_match_pairs, 0.2)
draw_matches(imgL, imgR, kp1, des1, kp2, des2, good_matches)


############## Compute Fundamental Matrix ##############
F, I, points1, points2 = compute_fundamental_matrix(good_matches, kp1, kp2)


############## Stereo rectify uncalibrated ##############
h1, w1 = imgL.shape
h2, w2 = imgR.shape
thresh = 0
_, H1, H2 = cv2.stereoRectifyUncalibrated(
    np.float32(points1), np.float32(points2), F, imgSize=(w1, h1), threshold=thresh,
)

############## Undistort (Rectify) ##############
imgL_undistorted = cv2.warpPerspective(imgL, H1, (w1, h1))
imgR_undistorted = cv2.warpPerspective(imgR, H2, (w2, h2))
cv2.imwrite("undistorted_L.png", imgL_undistorted)
cv2.imwrite("undistorted_R.png", imgR_undistorted)

############## Calculate Disparity (Depth Map) ##############

# Using StereoBM
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)
disparity_BM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_BM, "gray")
plt.colorbar()
plt.show()

# Using StereoSGBM
# Set disparity parameters. Note: disparity range is tuned according to
#  specific parameters obtained through trial and error.
win_size = 2
min_disp = -4
max_disp = 9
num_disp = max_disp - min_disp  # Needs to be divisible by 16
stereo = cv2.StereoSGBM_create(
    minDisparity=min_disp,
    numDisparities=num_disp,
    blockSize=5,
    uniquenessRatio=5,
    speckleWindowSize=5,
    speckleRange=5,
    disp12MaxDiff=2,
    P1=8 * 3 * win_size ** 2,
    P2=32 * 3 * win_size ** 2,
)
disparity_SGBM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_SGBM, "gray")
plt.colorbar()
plt.show()


回答 1

可能存在几个导致质量低下的问题Depth ChannelDisparity Channel导致我们产生质量低下的立体声序列。以下是其中的6个问题:

可能的问题我

  • 公式不完整

作为一个词uncalibrated意味着,stereoRectifyUncalibrated实例方法计算整改转化为你,如果你不知道或者不知道您的立体声对,并在环境中的相对位置的内部参数。

cv.StereoRectifyUncalibrated(pts1, pts2, fm, imgSize, rhm1, rhm2, thres)

哪里:

# pts1    –> an array of feature points in a first camera
# pts2    –> an array of feature points in a first camera
# fm      –> input fundamental matrix
# imgSize -> size of an image
# rhm1    -> output rectification homography matrix for a first image
# rhm2    -> output rectification homography matrix for a second image
# thres   –> optional threshold used to filter out outliers

您的方法看起来像这样:

cv2.StereoRectifyUncalibrated(p1fNew, p2fNew, F, (2048, 2048))

所以,你不要考虑三个参数:rhm1rhm2thres。如果为a threshold > 0,则在计算单应性之前会拒绝所有不符合极线几何的点对。否则,所有点均视为内点。该公式如下所示:

(pts2[i]^t * fm * pts1[i]) > thres

# t   –> translation vector between coordinate systems of cameras

因此,我认为由于公式计算不完整,可能会出现视觉错误。

您可以在官方资源上阅读相机校准和3D重建


可能的问题二

  • 轴间距离

interaxial distance左右相机镜头之间必须牢固not greater than 200 mm。当interaxial distance大于interocular距离时,这种效果称为hyperstereoscopyhyperdivergence,不仅导致场景中的深度夸张,而且导致观看者的身体不便。阅读Autodesk的“立体电影制作白皮书”以了解有关此主题的更多信息。

在此处输入图片说明


可能的问题三

  • 平行摄影机与Toed-In摄影机模式

Disparity Map由于不正确的相机模式计算,可能会导致视觉上的误差。许多立体学家更喜欢,Toe-In camera mode但例如皮克斯(Pixar)更喜欢Parallel camera mode

在此处输入图片说明

在此处输入图片说明


可能的问题四

  • 垂直对齐

在立体视觉中,如果发生垂直偏移(即使其中一个视图向上偏移1毫米),也会破坏稳固的立体声体验。因此,在生成Disparity Map音频之前,必须确保立体声对的左右视图已相应对齐。请参阅Technicolor立体镜白皮书,了解立体声方面的15个常见问题。

立体声整流矩阵:

   ┌                  ┐
   |  f   0   cx  tx  |
   |  0   f   cy  ty  |   # use "ty" value to fix vertical shift in one image
   |  0   0   1   0   |
   └                  ┘

这是一个StereoRectify方法:

cv.StereoRectify(cameraMatrix1, cameraMatrix2, distCoeffs1, distCoeffs2, imageSize, R, T, R1, R2, P1, P2, Q=None, flags=CV_CALIB_ZERO_DISPARITY, alpha=-1, newImageSize=(0, 0)) -> (roi1, roi2)


可能的问题五

  • 镜头变形

镜头失真是立体声合成中非常重要的主题。在生成之前,Disparity Map您需要先取消左右视图的失真,然后再生成视差通道,然后再次对这两个视图进行重新扭曲。

在此处输入图片说明

在此处输入图片说明


可能的问题六

  • 低质量深度通道,无抗锯齿

为了创建高质量的图像,Disparity Map您需要左右Depth Channels必须预先生成。在3D封装中工作时,只需单击一下即可渲染高质量的深度通道(边缘清晰)。但是从视频序列中生成高质量的深度通道并不容易,因为立体声对必须在您的环境中移动才能为将来的深度运动算法生成初始数据。如果帧中没有运动,则深度通道将非常差。

在此处输入图片说明

此外,Depth通道本身还有另一个缺点–因为的边缘没有抗锯齿,所以的边缘与RGB的边缘不匹配。

在此处输入图片说明


视差渠道代码段:

在这里,我想代表一种生成的快速方法Disparity Map

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

imageLeft = cv.imread('paris_left.png', 0)
imageRight = cv.imread('paris_right.png', 0)
stereo = cv.StereoBM_create(numDisparities=16, blockSize=15)
disparity = stereo.compute(imageLeft, imageRight)
plt.imshow(disparity, 'gray')
plt.show()

在此处输入图片说明

There might be several possible issues resulting in low-quality Depth Channel and Disparity Channel what leads us to low-quality stereo sequence. Here are 6 of those issues:

Possible issue I

  • Incomplete Formula

As a word uncalibrated implies, stereoRectifyUncalibrated instance method calculates a rectification transformations for you, in case you don’t know or can’t know intrinsic parameters of your stereo pair and its relative position in the environment.

cv.StereoRectifyUncalibrated(pts1, pts2, fm, imgSize, rhm1, rhm2, thres)

where:

# pts1    –> an array of feature points in a first camera
# pts2    –> an array of feature points in a first camera
# fm      –> input fundamental matrix
# imgSize -> size of an image
# rhm1    -> output rectification homography matrix for a first image
# rhm2    -> output rectification homography matrix for a second image
# thres   –> optional threshold used to filter out outliers

And your method looks this way:

cv2.StereoRectifyUncalibrated(p1fNew, p2fNew, F, (2048, 2048))

So, you do not take into account three parameters: rhm1, rhm2 and thres. If a threshold > 0, all point pairs that don’t comply with a epipolar geometry are rejected prior to computing the homographies. Otherwise, all points are considered inliers. This formula looks like this:

(pts2[i]^t * fm * pts1[i]) > thres

# t   –> translation vector between coordinate systems of cameras

Thus, I believe that visual inaccuracies might appear due to an incomplete formula’s calculation.

You can read Camera Calibration and 3D Reconstruction on official resource.


Possible issue II

  • Interaxial Distance

A robust interaxial distance between left and right camera lenses must be not greater than 200 mm. When the interaxial distance is larger than the interocular distance, the effect is called hyperstereoscopy or hyperdivergence and results not only in depth exaggeration in the scene but also in viewer’s physical inconvenience. Read Autodesk’s Stereoscopic Filmmaking Whitepaper to find out more on this topic.

enter image description here


Possible issue III

  • Parallel vs Toed-In camera mode

Visual inaccuracies in resulted Disparity Map may occur due to incorrect Camera Mode calculation. Many stereographers prefer Toe-In camera mode but Pixar, for example, prefers Parallel camera mode.

enter image description here

enter image description here


Possible issue IV

  • Vertical Alignment

In stereoscopy, if a vertical shift occurs (even if one of the views is shifted up by 1 mm) it ruins a robust stereo experience. So, before generating Disparity Map you must be sure that left and right views of your stereo pair are accordingly aligned. Look at Technicolor Sterreoscopic Whitepaper about 15 common problems in stereo.

Stereo Rectification Matrix:

   ┌                  ┐
   |  f   0   cx  tx  |
   |  0   f   cy  ty  |   # use "ty" value to fix vertical shift in one image
   |  0   0   1   0   |
   └                  ┘

Here’s a StereoRectify method:

cv.StereoRectify(cameraMatrix1, cameraMatrix2, distCoeffs1, distCoeffs2, imageSize, R, T, R1, R2, P1, P2, Q=None, flags=CV_CALIB_ZERO_DISPARITY, alpha=-1, newImageSize=(0, 0)) -> (roi1, roi2)


Possible issue V

  • Lens Distortion

Lens Distortion is very important topic in stereo composition. Before generating a Disparity Map you need to undistort left and right views, after this generate a disparity channel, and then redistort both views again.

enter image description here

enter image description here


Possible issue VI

  • Low-quality Depth channel without anti-aliasing

For creating a high-quality Disparity Map you need left and right Depth Channels that must be pre-generated. When you work in 3D package you can render a high-quality Depth Channel (with crisp edges) with just one click. But generating a high-quality depth channel from video sequence is not easy because stereo pair has to move in your environment for producing an initial data for future depth-from-motion algorithm. If there’s no motion in a frame a depth channel will be extremely poor.

enter image description here

Also, Depth channel itself has one more drawback – its edges do not match the edges of the RGB because it has no anti-aliasing.

enter image description here


Disparity channel code snippet:

Here I’d like to represent a quick approach to generate a Disparity Map:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

imageLeft = cv.imread('paris_left.png', 0)
imageRight = cv.imread('paris_right.png', 0)
stereo = cv.StereoBM_create(numDisparities=16, blockSize=15)
disparity = stereo.compute(imageLeft, imageRight)
plt.imshow(disparity, 'gray')
plt.show()

enter image description here


Django:为什么某些模型字段会相互冲突?

问题:Django:为什么某些模型字段会相互冲突?

我想创建一个包含2个指向用户的链接的对象。例如:

class GameClaim(models.Model):
    target = models.ForeignKey(User)
    claimer = models.ForeignKey(User)
    isAccepted = models.BooleanField()

但是运行服务器时出现以下错误:

  • 字段“目标”的访问器与相关字段“ User.gameclaim_set”冲突。在’target’的定义中添加related_name参数。

  • 字段“ claimer”的访问器与相关字段“ User.gameclaim_set”冲突。在“ claimer”的定义中添加一个related_name参数。

您能否解释为什么我会收到错误以及如何解决这些错误?

I want to create an object that contains 2 links to Users. For example:

class GameClaim(models.Model):
    target = models.ForeignKey(User)
    claimer = models.ForeignKey(User)
    isAccepted = models.BooleanField()

but I am getting the following errors when running the server:

  • Accessor for field ‘target’ clashes with related field ‘User.gameclaim_set’. Add a related_name argument to the definition for ‘target’.

  • Accessor for field ‘claimer’ clashes with related field ‘User.gameclaim_set’. Add a related_name argument to the definition for ‘claimer’.

Can you please explain why I am getting the errors and how to fix them?


回答 0

您有两个用户外键。Django自动创建一个从User到GameClaim的反向关系,通常是gameclaim_set。但是,由于您有两个FK,因此将具有两个gameclaim_set属性,这显然是不可能的。因此,您需要告诉Django用于反向关系的名称。

使用related_nameFK定义中的属性。例如

class GameClaim(models.Model):
    target = models.ForeignKey(User, related_name='gameclaim_targets')
    claimer = models.ForeignKey(User, related_name='gameclaim_users')
    isAccepted = models.BooleanField()

You have two foreign keys to User. Django automatically creates a reverse relation from User back to GameClaim, which is usually gameclaim_set. However, because you have two FKs, you would have two gameclaim_set attributes, which is obviously impossible. So you need to tell Django what name to use for the reverse relation.

Use the related_name attribute in the FK definition. e.g.

class GameClaim(models.Model):
    target = models.ForeignKey(User, related_name='gameclaim_targets')
    claimer = models.ForeignKey(User, related_name='gameclaim_users')
    isAccepted = models.BooleanField()

回答 1

User模型试图创建具有相同名称的两个领域,一个是GameClaims说有User作为target,而另一个用于GameClaims该有User作为claimer。这是上的文档related_name,这是Django允许您设置属性名称的方法,以便自动生成的属性不会冲突。

The User model is trying to create two fields with the same name, one for the GameClaims that have that User as the target, and another for the GameClaims that have that User as the claimer. Here’s the docs on related_name, which is Django’s way of letting you set the names of the attributes so the autogenerated ones don’t conflict.


回答 2

OP没有使用抽象基类…但是,如果您使用的是,您会发现在FK中硬编码related_name(例如…,related_name =“ myname”)会导致许多此类冲突错误-从基类继承的每个类一个。下面提供的链接包含解决方法,这很简单,但绝对不明显。

从Django文档…

如果在ForeignKey或ManyToManyField上使用related_name属性,则必须始终为该字段指定唯一的反向名称。这通常会在抽象基类中引起问题,因为此类的字段包含在每个子类中,并且每次属性(包括related_name)的值都完全相同。

更多信息在这里

The OP isn’t using a abstract base class… but if you are, you will find that hard coding the related_name in the FK (e.g. …, related_name=”myname”) will result in a number of these conflict errors – one for each inherited class from the base class. The link provided below contains the workaround, which is simple, but definitely not obvious.

From the django docs…

If you are using the related_name attribute on a ForeignKey or ManyToManyField, you must always specify a unique reverse name for the field. This would normally cause a problem in abstract base classes, since the fields on this class are included into each of the child classes, with exactly the same values for the attributes (including related_name) each time.

More info here.


回答 3

有时,您related_name 实际上必须在使用继承的任何时候使用额外的格式设置。

class Value(models.Model):
    value = models.DecimalField(decimal_places=2, max_digits=5)
    animal = models.ForeignKey(
        Animal, related_name="%(app_label)s_%(class)s_related")

    class Meta:
        abstract = True

class Height(Value):
    pass

class Weigth(Value):
    pass

class Length(Value):
    pass

这里没有冲突,但是related_name定义一次,Django将小心创建唯一的关系名称。

然后在Value类的子级中,您可以访问:

herdboard_height_related
herdboard_lenght_related
herdboard_weight_related

Sometimes you have to use extra formatting in related_name – actually, any time when inheritance is used.

class Value(models.Model):
    value = models.DecimalField(decimal_places=2, max_digits=5)
    animal = models.ForeignKey(
        Animal, related_name="%(app_label)s_%(class)s_related")

    class Meta:
        abstract = True

class Height(Value):
    pass

class Weigth(Value):
    pass

class Length(Value):
    pass

No clash here, but related_name is defined once and Django will take care for creating unique relation names.

then in children of Value class, you’ll have access to:

herdboard_height_related
herdboard_lenght_related
herdboard_weight_related

回答 4

当我将子模块作为应用程序添加到Django项目时,似乎偶尔会遇到这种情况,例如,给定以下结构:

myapp/
myapp/module/
myapp/module/models.py

如果我将以下内容添加到INSTALLED_APPS:

'myapp',
'myapp.module',

Django似乎两次处理了myapp.mymodule models.py文件,并抛出了以上错误。可以通过在INSTALLED_APPS列表中不包括主模块来解决此问题:

'myapp.module',

包含myapp代替myapp.module会导致所有数据库表都使用不正确的名称创建,因此这似乎是正确的方法。

我在寻找此问题的解决方案时遇到了这篇文章,所以我想把它放在这里:)

I seem to come across this occasionally when I add a submodule as an application to a django project, for example given the following structure:

myapp/
myapp/module/
myapp/module/models.py

If I add the following to INSTALLED_APPS:

'myapp',
'myapp.module',

Django seems to process the myapp.mymodule models.py file twice and throws the above error. This can be resolved by not including the main module in the INSTALLED_APPS list:

'myapp.module',

Including the myapp instead of myapp.module causes all the database tables to be created with incorrect names, so this seems to be the correct way to do it.

I came across this post while looking for a solution to this problem so figured I’d put this here :)


回答 5

只是添加约旦的答案(感谢约旦的提示),如果您在应用程序上方导入级别,然后导入应用程序,则也可能发生这种情况,例如

myproject/ apps/ foo_app/ bar_app/

因此,如果要导入应用程序foo_app和bar_app,则可能会遇到此问题。我的应用程序foo_app和bar_app都列在设置中。INSTALLED_APPS

而且您还是想避免导入应用程序,因为那样您会将相同的应用程序安装在2个不同的命名空间中

apps.foo_appfoo_app

Just adding to Jordan’s answer (thanks for the tip Jordan) it can also happen if you import the level above the apps and then import the apps e.g.

myproject/ apps/ foo_app/ bar_app/

So if you are importing apps, foo_app and bar_app then you could get this issue. I had apps, foo_app and bar_app all listed in settings.INSTALLED_APPS

And you want to avoid importing apps anyway, because then you have the same app installed in 2 different namespaces

apps.foo_app and foo_app


如何在python中使用当前日期和时间创建文件名?

问题:如何在python中使用当前日期和时间创建文件名?

这是一个功能代码(成功创建文件)

sys.stdout = open('filename1.xml', 'w')

现在,我正在尝试使用当前日期时间来命名文件(我不是python的专家)

filename1 = datetime.now().strftime("%Y%m%d-%H%M%S")
sys.stdout = open(filename1 + '.xml', 'w')

我想写出一个具有确切日期和时间的文件名,它是程序已经创建的xml文件,我只需要命名该文件即可。上面的代码不起作用。

[编辑] -返回错误

  File "./fix.py", line 226, in <module>
    filenames = datetime.now().strftime("%Y%m%d-%H%M%S")
AttributeError: 'module' object has no attribute 'now'

Here is a functional code (Create file with success)

sys.stdout = open('filename1.xml', 'w')

Now I’m trying to name the file with the current Date Time (I’m not an expert in python)

filename1 = datetime.now().strftime("%Y%m%d-%H%M%S")
sys.stdout = open(filename1 + '.xml', 'w')

I want to write out a file name with the exact date and time, it is a xml file, that the program has already create, I just need to name the file. The above code is not working.

[EDITED] – The error returned

  File "./fix.py", line 226, in <module>
    filenames = datetime.now().strftime("%Y%m%d-%H%M%S")
AttributeError: 'module' object has no attribute 'now'

回答 0

不使用时datetime,这可以解决您的问题(回答您的问题),该问题是使用您指定的当前时间和日期格式获取字符串:

import time
timestr = time.strftime("%Y%m%d-%H%M%S")
print timestr

Yield:

20120515-155045

因此您的文件名可以追加或使用此字符串。

While not using datetime, this solves your problem (answers your question) of getting a string with the current time and date format you specify:

import time
timestr = time.strftime("%Y%m%d-%H%M%S")
print timestr

yields:

20120515-155045

so your filename could append or use this string.


回答 1

nowdatetime模块中类中的类方法datetime。所以你需要

datetime.datetime.now()

或者您可以使用其他导入

from datetime import datetime

通过这种方式,您可以datetime.now按照问题中的代码使用。

now is a class method in the class datetime in the module datetime. So you need

datetime.datetime.now()

Or you can use a different import

from datetime import datetime

Done this way allows you to use datetime.now as per the code in the question.


回答 2

更改此行

filename1 = datetime.now().strftime("%Y%m%d-%H%M%S")

filename1 = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")

注意额外的datetime。或者,将您更改 import datetimefrom datetime import datetime

Change this line

filename1 = datetime.now().strftime("%Y%m%d-%H%M%S")

To

filename1 = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")

Note the extra datetime. Alternatively, change your import datetime to from datetime import datetime


回答 3

这个更容易被人类理解。

from datetime import datetime

datetime.now().strftime("%d-%m-%Y_%I-%M-%S_%p")
'15-08-2019_11-57-48_PM'

This one is much more human readable.

from datetime import datetime

datetime.now().strftime("%Y_%m_%d-%I_%M_%S_%p")
'2020_08_12-03_29_22_AM'

回答 4

我很惊讶没有单个格式化程序返回默认的(并且安全的)“用于追加文件名”-时间的格式,我们可以简单地编写 FD.write('mybackup'+time.strftime('%(formatter here)') + 'ext'

"%x" instead of "%Y%m%d-%H%M%S"

I’m surprised there is not some single formatter that returns a default (and safe) ‘for appending in filename’ – format of the time, We could simply write FD.write('mybackup'+time.strftime('%(formatter here)') + 'ext'

"%x" instead of "%Y%m%d-%H%M%S"

回答 5

我需要在文件夹名称中包括日期时间戳,以便从Web刮板转储文件。

# import time and OS modules to use to build file folder name
import time
import os 

# Build string for directory to hold files
# Output Configuration
#   drive_letter = Output device location (hard drive) 
#   folder_name = directory (folder) to receive and store PDF files

drive_letter = r'D:\\' 
folder_name = r'downloaded-files'
folder_time = datetime.now().strftime("%Y-%m-%d_%I-%M-%S_%p")
folder_to_save_files = drive_letter + folder_name + folder_time 

# IF no such folder exists, create one automatically
if not os.path.exists(folder_to_save_files):
    os.mkdir(folder_to_save_files)

Here’s some that I needed to include the date-time stamp in the folder name for dumping files from a web scraper.

# import time and OS modules to use to build file folder name
import time
import os 

# Build string for directory to hold files
# Output Configuration
#   drive_letter = Output device location (hard drive) 
#   folder_name = directory (folder) to receive and store PDF files

drive_letter = r'D:\\' 
folder_name = r'downloaded-files'
folder_time = datetime.now().strftime("%Y-%m-%d_%I-%M-%S_%p")
folder_to_save_files = drive_letter + folder_name + folder_time 

# IF no such folder exists, create one automatically
if not os.path.exists(folder_to_save_files):
    os.mkdir(folder_to_save_files)

尽管我调用pyplot.show(),但matplotlib不会显示我的绘图

问题:尽管我调用pyplot.show(),但matplotlib不会显示我的绘图

matplotlib上需要帮助。是的,我没有忘记调用pyplot.show()。

$ ipython –pylab

import matplotlib.pyplot as p 
p.plot(range(20), range(20))

matplotlib.lines.Line2D at 0xade2b2c作为输出返回。

p.show()

没事了 没有错误讯息。没有新窗口。没有。我matplotlib使用pip进行安装,但未收到任何错误消息。

细节:

我用,

  • 的Ubuntu
  • IPython v0.11
  • Python v2.6.6
  • matplotlib v1.0.1

Help required on matplotlib. Yes, I did not forget calling the pyplot.show().

$ ipython –pylab

import matplotlib.pyplot as p 
p.plot(range(20), range(20))

It returns matplotlib.lines.Line2D at 0xade2b2c as the output.

p.show()

There is nothing to happen. No error message. No new window. Nothing. I install matplotlib by using pip and I didn’t take any error messages.

Details:

I use,

  • Ubuntu
  • IPython v0.11
  • Python v2.6.6
  • matplotlib v1.0.1

回答 0

如果将后端设置为template~/.matplotlib/matplotlibrc,则可以重现您的症状:

〜/ .matplotlib / matplotlibrc:

# backend      : GtkAgg
backend      : template

请注意,该文件matplotlibrc可能不在目录中~/.matplotlib/。在这种情况下,以下代码显示其位置:

>>> import matplotlib
>>> matplotlib.matplotlib_fname()

In [1]: import matplotlib.pyplot as p

In [2]: p.plot(range(20),range(20))
Out[2]: [<matplotlib.lines.Line2D object at 0xa64932c>]

In [3]: p.show()

如果您~/.matplotlib/matplotlibrc将后端编辑为,并将其更改为GtkAgg,则应该会看到一个图。您可以使用以下命令列出计算机上所有可用的后端

import matplotlib.rcsetup as rcsetup
print(rcsetup.all_backends)

它应该返回类似以下的列表:

['GTK', 'GTKAgg', 'GTKCairo', 'FltkAgg', 'MacOSX', 'QtAgg', 'Qt4Agg',
'TkAgg', 'WX', 'WXAgg', 'CocoaAgg', 'agg', 'cairo', 'emf', 'gdk', 'pdf',
'ps', 'svg', 'template']

参考:

If I set my backend to template in ~/.matplotlib/matplotlibrc, then I can reproduce your symptoms:

~/.matplotlib/matplotlibrc:

# backend      : GtkAgg
backend      : template

Note that the file matplotlibrc may not be in directory ~/.matplotlib/. In this case, the following code shows where it is:

>>> import matplotlib
>>> matplotlib.matplotlib_fname()

In [1]: import matplotlib.pyplot as p

In [2]: p.plot(range(20),range(20))
Out[2]: [<matplotlib.lines.Line2D object at 0xa64932c>]

In [3]: p.show()

If you edit ~/.matplotlib/matplotlibrc and change the backend to something like GtkAgg, you should see a plot. You can list all the backends available on your machine with

import matplotlib.rcsetup as rcsetup
print(rcsetup.all_backends)

It should return a list like:

['GTK', 'GTKAgg', 'GTKCairo', 'FltkAgg', 'MacOSX', 'QtAgg', 'Qt4Agg',
'TkAgg', 'WX', 'WXAgg', 'CocoaAgg', 'agg', 'cairo', 'emf', 'gdk', 'pdf',
'ps', 'svg', 'template']

Reference:


回答 1

我在Ubuntu 12.04上遇到了完全相同的问题,因为我使用以下命令安装了matplotlib(在virtualenv中)

pip install matplotlib

长话短说,我的建议是:不要尝试使用pip或手工安装matplotlib;让真正的软件包管理器(例如apt-get / synaptic)为您安装它及其所有依赖项。

不幸的是,matplotlib的后端(用于实际绘制图的替代方法)具有pip无法处理的各种依赖关系。更糟糕的是,它无声地失败了。也就是说,pip install matplotlib似乎成功安装了matplotlib。但是,当您尝试使用它时(例如pyplot.show()),将不会出现绘图窗口。我尝试了网络上人们建议的所有不同后端(Qt4Agg,GTK等),但它们都失败了(即,当我尝试导入matplotlib.pyplot时,我得到了,ImportError因为它试图导入缺少的某些依赖项)。然后,我研究了如何安装这些依赖项,但这只是让我想放弃使用pip(在virtualenv内)作为任何具有非Python软件包依赖项的软件包的可行安装解决方案。

整个经历使我爬回apt-get / synaptic(即Ubuntu软件包管理器)来安装matplotlib之类的软件。那很好。当然,这意味着您只能安装到您的系统目录中,没有virtualenv的好处,并且您受困于Ubuntu发行的版本,这可能落后于当前版本…

I ran into the exact same problem on Ubuntu 12.04, because I installed matplotlib (within a virtualenv) using

pip install matplotlib

To make long story short, my advice is: don’t try to install matplotlib using pip or by hand; let a real package manager (e.g. apt-get / synaptic) install it and all its dependencies for you.

Unfortunately, matplotlib’s backends (alternative methods for actually rendering your plots) have all sorts of dependencies that pip will not deal with. Even worse, it fails silently; that is, pip install matplotlib appears to install matplotlib successfully. But when you try to use it (e.g. pyplot.show()), no plot window will appear. I tried all the different backends that people on the web suggest (Qt4Agg, GTK, etc.), and they all failed (i.e. when I tried to import matplotlib.pyplot, I get ImportError because it’s trying to import some dependency that’s missing). I then researched how to install those dependencies, but it just made me want to give up using pip (within virtualenv) as a viable installation solution for any package that has non-Python package dependencies.

The whole experience sent me crawling back to apt-get / synaptic (i.e. the Ubuntu package manager) to install software like matplotlib. That worked perfectly. Of course, that means you can only install into your system directories, no virtualenv goodness, and you are stuck with the versions that Ubuntu distributes, which may be way behind the current version…


回答 2

%matplotlib内联

对于使用笔记本的我来说,在绘图工作之前添加以上行。

%matplotlib inline

For me working with notebook, adding the above line before the plot works.


回答 3

备查,

我遇到了同样的问题-pylab没有在ipython下显示。通过更改ipython的配置文件{ipython_config.py}已解决了该问题。在配置文件中

c.InteractiveShellApp.pylab = 'auto'

我将’auto’更改为’qt’,现在我看到了图表

For future reference,

I have encountered the same problem — pylab was not showing under ipython. The problem was fixed by changing ipython’s config file {ipython_config.py}. In the config file

c.InteractiveShellApp.pylab = 'auto'

I changed ‘auto’ to ‘qt’ and now I see graphs


回答 4

只需输入:

plt.ion()

请在23:30 参见https://www.youtube.com/watch?v=1zmV8lZsHF4

plt使用是因为我的导入:import matplotlib.pyplotas plt

我在带有iTerm2的Mac上使用python2.7。

Just type:

plt.ion()

See https://www.youtube.com/watch?v=1zmV8lZsHF4 at 23:30 !

plt is used because of my import: import matplotlib.pyplot as plt

I’m using python2.7 on a mac with iTerm2.


回答 5

解决我问题的方法只是在顶部的ipython Notebook中使用以下两行

%matplotib inline
%pylab inline

而且有效。我正在使用Ubuntu16.04和ipython-5.1

What solved my problem was just using the below two lines in ipython notebook at the top

%matplotib inline
%pylab inline

And it worked. I’m using Ubuntu16.04 and ipython-5.1


回答 6

我必须从源代码安装matplotlib才能使它工作。关键说明(来自http://www.pyimagesearch.com/2015/08/24/resolved-matplotlib-figures-not-showing-up-or-displaying/)为:

$ workon plotting
$ pip uninstall matplotlib
$ git clone https://github.com/matplotlib/matplotlib.git
$ cd matplotlib
$ python setup.py install

正如@unutbu所说,通过更改后端,我遇到了很多问题,所有不同的后端都不起作用。

I had to install matplotlib from source to get this to work. The key instructions (from http://www.pyimagesearch.com/2015/08/24/resolved-matplotlib-figures-not-showing-up-or-displaying/) are:

$ workon plotting
$ pip uninstall matplotlib
$ git clone https://github.com/matplotlib/matplotlib.git
$ cd matplotlib
$ python setup.py install

By changing the backend, as @unutbu says, I just ran into loads more problems with all the different backends not working either.


回答 7

在导入pylab之前添加以下两行似乎对我有用

import matplotlib
matplotlib.use("gtk")

import sys
import pylab
import numpy as np

Adding the following two lines before importing pylab seems to work for me

import matplotlib
matplotlib.use("gtk")

import sys
import pylab
import numpy as np

回答 8

确保启用此启动脚本:(“首选项”>“控制台”>“高级选项”)

/usr/lib/python2.7/dist-packages/spyderlib/scientific_startup.py

如果启用了标准的PYTHONSTARTUP,则不会有交互式绘图

Be sure to have this startup script enabled : ( Preferences > Console > Advanced Options )

/usr/lib/python2.7/dist-packages/spyderlib/scientific_startup.py

If the standard PYTHONSTARTUP is enabled you won’t have an interactive plot


回答 9

类似@Rikki,我通过升级解决了这个问题matplotlibpip install matplotlib --upgrade。如果无法升级,则可以卸载并重新安装。

pip uninstall matplotlib
pip install matplotlib

Similar to @Rikki, I solved this problem by upgrading matplotlib with pip install matplotlib --upgrade. If you can’t upgrade uninstalling and reinstalling may work.

pip uninstall matplotlib
pip install matplotlib

回答 10

对我来说,如果我只是在macOS 下创建一个 matplotlibrc文件,就会发生问题~/.matplotlib。在其中添加“后端:macosx”可解决此问题。

我认为这是一个错误:如果backend未在我的代码中指定,matplotlibrc则应采用默认值。

For me the problem happens if I simply create an empty matplotlibrc file under ~/.matplotlib on macOS. Adding “backend: macosx” in it fixes the problem.

I think it is a bug: if backend is not specified in my matplotlibrc it should take the default value.


回答 11

运行代码后,包括:

import pylab as p
p.show()

After running your code include:

import pylab as p
p.show()

回答 12

我发现我需要window = Tk(),然后window.mainloop()

I found that I needed window = Tk() and then window.mainloop()


回答 13

对于Ubuntu 12.04:

sudo apt-get install python-qt4
virtualenv .env --no-site-packages
source .env/bin/activate
easy_install -U distribute
ln -s /usr/lib/python2.7/dist-packages/PyQt4 .
ln -s /usr/lib/python2.7/dist-packages/sip.so .
pip install matplotlib

For Ubuntu 12.04:

sudo apt-get install python-qt4
virtualenv .env --no-site-packages
source .env/bin/activate
easy_install -U distribute
ln -s /usr/lib/python2.7/dist-packages/PyQt4 .
ln -s /usr/lib/python2.7/dist-packages/sip.so .
pip install matplotlib

切片NumPy 2d数组,或者如何从nxn数组(n> m)中提取mxm子矩阵?

问题:切片NumPy 2d数组,或者如何从nxn数组(n> m)中提取mxm子矩阵?

我想切片一个NumPy nxn数组。我想提取该数组的m行和列的任意选择(即,行/列数中没有任何模式),使其成为一个新的mxm数组。对于此示例,假设数组为4×4,我想从中提取2×2数组。

这是我们的数组:

from numpy import *
x = range(16)
x = reshape(x,(4,4))

print x
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

要删除的行和列相同。最简单的情况是,当我想提取在开始或结尾处的2×2子矩阵时,即:

In [33]: x[0:2,0:2]
Out[33]: 
array([[0, 1],
       [4, 5]])

In [34]: x[2:,2:]
Out[34]: 
array([[10, 11],
       [14, 15]])

但是,如果我需要删除其他混合的行/列怎么办?如果我需要删除第一行和第三行/行,从而提取子矩阵,该[[5,7],[13,15]]怎么办?行/线可以有任何组成。我读到某个地方,我只需要使用行/列的索引数组/索引列表来索引我的数组,但这似乎不起作用:

In [35]: x[[1,3],[1,3]]
Out[35]: array([ 5, 15])

我找到了一种方法,即:

    In [61]: x[[1,3]][:,[1,3]]
Out[61]: 
array([[ 5,  7],
       [13, 15]])

第一个问题是,尽管我可以接受,但很难阅读。如果有人有更好的解决方案,我当然想听听。

另一件事是我在一个论坛上读到用数组索引数组会迫使NumPy复制所需的数组,因此在处理大型数组时,这可能会成为问题。为什么这样/这个机制如何运作?

I want to slice a NumPy nxn array. I want to extract an arbitrary selection of m rows and columns of that array (i.e. without any pattern in the numbers of rows/columns), making it a new, mxm array. For this example let us say the array is 4×4 and I want to extract a 2×2 array from it.

Here is our array:

from numpy import *
x = range(16)
x = reshape(x,(4,4))

print x
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

The line and columns to remove are the same. The easiest case is when I want to extract a 2×2 submatrix that is at the beginning or at the end, i.e. :

In [33]: x[0:2,0:2]
Out[33]: 
array([[0, 1],
       [4, 5]])

In [34]: x[2:,2:]
Out[34]: 
array([[10, 11],
       [14, 15]])

But what if I need to remove another mixture of rows/columns? What if I need to remove the first and third lines/rows, thus extracting the submatrix [[5,7],[13,15]]? There can be any composition of rows/lines. I read somewhere that I just need to index my array using arrays/lists of indices for both rows and columns, but that doesn’t seem to work:

In [35]: x[[1,3],[1,3]]
Out[35]: array([ 5, 15])

I found one way, which is:

    In [61]: x[[1,3]][:,[1,3]]
Out[61]: 
array([[ 5,  7],
       [13, 15]])

First issue with this is that it is hardly readable, although I can live with that. If someone has a better solution, I’d certainly like to hear it.

Other thing is I read on a forum that indexing arrays with arrays forces NumPy to make a copy of the desired array, thus when treating with large arrays this could become a problem. Why is that so / how does this mechanism work?


回答 0

如Sven所述,x[[[0],[2]],[1,3]]将返回与1和3列匹配的0和2行,同时x[[0,2],[1,3]]将在数组中返回值x [0,1]和x [2,3]。

有一个有用的函数可以帮助我完成第一个示例numpy.ix_。您可以使用进行与我的第一个示例相同的操作x[numpy.ix_([0,2],[1,3])]。这样可以避免您必须输入所有这些多余的括号。

As Sven mentioned, x[[[0],[2]],[1,3]] will give back the 0 and 2 rows that match with the 1 and 3 columns while x[[0,2],[1,3]] will return the values x[0,1] and x[2,3] in an array.

There is a helpful function for doing the first example I gave, numpy.ix_. You can do the same thing as my first example with x[numpy.ix_([0,2],[1,3])]. This can save you from having to enter in all of those extra brackets.


回答 1

为了回答这个问题,我们必须研究如何在Numpy中为多维数组建立索引。首先说您有x问题中的数组。分配给的缓冲区x将包含从0到15的16个升序整数。如果要访问一个元素,例如x[i,j]NumPy必须找出该元素相对于缓冲区起始位置的存储位置。这是通过有效计算i*x.shape[1]+j(并乘以一个int的大小以获得实际的内存偏移量)来完成的。

如果通过基本切片提取子y = x[0:2,0:2]数组,则结果对象将与共享基础缓冲区x。但是,如果您同意,会发生什么y[i,j]?NumPy无法用于i*y.shape[1]+j计算数组中的偏移量,因为所属的数据y在内存中不是连续的。

NumPy通过引入步幅来解决此问题。在计算要访问的内存偏移量时x[i,j],实际计算的是i*x.strides[0]+j*x.strides[1](并且这已经包括int大小的因数):

x.strides
(16, 4)

y像上面那样提取时,NumPy不会创建新的缓冲区,但是创建一个引用相同缓冲区的新数组对象(否则y将等于x。)然后,新数组对象将具有不同的形状,x并且可能以不同的开头偏移到缓冲区中,但将与x(至少在这种情况下)共享跨步:

y.shape
(2,2)
y.strides
(16, 4)

这样,计算的内存偏移量y[i,j]将产生正确的结果。

但是NumPy应该做什么z=x[[1,3]]呢?如果原始缓冲区用于,则跨步机制将不允许正确的索引编制z。从理论上讲 NumPy 可以添加比跨步更复杂的机制,但是这会使元素访问相对昂贵,从而在某种程度上违背了数组的整体思想。此外,视图不再是真正的轻量级对象。

关于索引的NumPy文档对此进行了详细介绍

哦,几乎忘了您的实际问题:这是如何使具有多个列表的索引按预期工作:

x[[[1],[3]],[1,3]]

这是因为索引数组以相同的形状广播。当然,对于此特定示例,您也可以使用基本切片:

x[1::2, 1::2]

To answer this question, we have to look at how indexing a multidimensional array works in Numpy. Let’s first say you have the array x from your question. The buffer assigned to x will contain 16 ascending integers from 0 to 15. If you access one element, say x[i,j], NumPy has to figure out the memory location of this element relative to the beginning of the buffer. This is done by calculating in effect i*x.shape[1]+j (and multiplying with the size of an int to get an actual memory offset).

If you extract a subarray by basic slicing like y = x[0:2,0:2], the resulting object will share the underlying buffer with x. But what happens if you acces y[i,j]? NumPy can’t use i*y.shape[1]+j to calculate the offset into the array, because the data belonging to y is not consecutive in memory.

NumPy solves this problem by introducing strides. When calculating the memory offset for accessing x[i,j], what is actually calculated is i*x.strides[0]+j*x.strides[1] (and this already includes the factor for the size of an int):

x.strides
(16, 4)

When y is extracted like above, NumPy does not create a new buffer, but it does create a new array object referencing the same buffer (otherwise y would just be equal to x.) The new array object will have a different shape then x and maybe a different starting offset into the buffer, but will share the strides with x (in this case at least):

y.shape
(2,2)
y.strides
(16, 4)

This way, computing the memory offset for y[i,j] will yield the correct result.

But what should NumPy do for something like z=x[[1,3]]? The strides mechanism won’t allow correct indexing if the original buffer is used for z. NumPy theoretically could add some more sophisticated mechanism than the strides, but this would make element access relatively expensive, somehow defying the whole idea of an array. In addition, a view wouldn’t be a really lightweight object anymore.

This is covered in depth in the NumPy documentation on indexing.

Oh, and nearly forgot about your actual question: Here is how to make the indexing with multiple lists work as expected:

x[[[1],[3]],[1,3]]

This is because the index arrays are broadcasted to a common shape. Of course, for this particular example, you can also make do with basic slicing:

x[1::2, 1::2]

回答 2

我认为这x[[1,3]][:,[1,3]]很难理解。如果您想更加清楚自己的意图,可以执行以下操作:

a[[1,3],:][:,[1,3]]

我不是切片专家,但是通常情况下,如果尝试切片为数组并且值是连续的,则会返回一个视图,其中步幅值已更改。

例如,在输入33和34中,尽管得到2×2数组,步幅为4。因此,当索引下一行时,指针将移动到内存中的正确位置。

显然,这种机制不能很好地用于索引数组的情况。因此,numpy将必须进行复制。毕竟,许多其他矩阵数学函数依赖于大小,步幅和连续的内存分配。

I don’t think that x[[1,3]][:,[1,3]] is hardly readable. If you want to be more clear on your intent, you can do:

a[[1,3],:][:,[1,3]]

I am not an expert in slicing but typically, if you try to slice into an array and the values are continuous, you get back a view where the stride value is changed.

e.g. In your inputs 33 and 34, although you get a 2×2 array, the stride is 4. Thus, when you index the next row, the pointer moves to the correct position in memory.

Clearly, this mechanism doesn’t carry well into the case of an array of indices. Hence, numpy will have to make the copy. After all, many other matrix math function relies on size, stride and continuous memory allocation.


回答 3

如果要跳过每隔一行和每隔一列,则可以使用基本切片:

In [49]: x=np.arange(16).reshape((4,4))
In [50]: x[1:4:2,1:4:2]
Out[50]: 
array([[ 5,  7],
       [13, 15]])

这将返回一个视图,而不是数组的副本。

In [51]: y=x[1:4:2,1:4:2]

In [52]: y[0,0]=100

In [53]: x   # <---- Notice x[1,1] has changed
Out[53]: 
array([[  0,   1,   2,   3],
       [  4, 100,   6,   7],
       [  8,   9,  10,  11],
       [ 12,  13,  14,  15]])

z=x[(1,3),:][:,(1,3)]使用高级索引并因此返回副本:

In [58]: x=np.arange(16).reshape((4,4))
In [59]: z=x[(1,3),:][:,(1,3)]

In [60]: z
Out[60]: 
array([[ 5,  7],
       [13, 15]])

In [61]: z[0,0]=0

请注意,它x是不变的:

In [62]: x
Out[62]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

如果要选择任意行和列,则不能使用基本切片。您必须使用高级索引,使用类似x[rows,:][:,columns],where rowscolumnsare sequence的方法。当然,这将为您提供原始阵列的副本,而不是视图。正如人们所期望的那样,因为numpy数组使用连续内存(具有恒定的步幅),并且将无法生成具有任意行和列的视图(因为这将需要非恒定的步幅)。

If you want to skip every other row and every other column, then you can do it with basic slicing:

In [49]: x=np.arange(16).reshape((4,4))
In [50]: x[1:4:2,1:4:2]
Out[50]: 
array([[ 5,  7],
       [13, 15]])

This returns a view, not a copy of your array.

In [51]: y=x[1:4:2,1:4:2]

In [52]: y[0,0]=100

In [53]: x   # <---- Notice x[1,1] has changed
Out[53]: 
array([[  0,   1,   2,   3],
       [  4, 100,   6,   7],
       [  8,   9,  10,  11],
       [ 12,  13,  14,  15]])

while z=x[(1,3),:][:,(1,3)] uses advanced indexing and thus returns a copy:

In [58]: x=np.arange(16).reshape((4,4))
In [59]: z=x[(1,3),:][:,(1,3)]

In [60]: z
Out[60]: 
array([[ 5,  7],
       [13, 15]])

In [61]: z[0,0]=0

Note that x is unchanged:

In [62]: x
Out[62]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

If you wish to select arbitrary rows and columns, then you can’t use basic slicing. You’ll have to use advanced indexing, using something like x[rows,:][:,columns], where rows and columns are sequences. This of course is going to give you a copy, not a view, of your original array. This is as one should expect, since a numpy array uses contiguous memory (with constant strides), and there would be no way to generate a view with arbitrary rows and columns (since that would require non-constant strides).


回答 4

使用numpy时,您可以为索引的每个部分传递一个切片-因此,x[0:2,0:2]上面的示例有效。

如果您只想平均跳过列或行,则可以传递包含三个成分(即开始,停止,步进)的切片。

同样,对于上面的示例:

>>> x[1:4:2, 1:4:2]
array([[ 5,  7],
       [13, 15]])

这基本上是:第一维中的切片,从索引1开始,在索引等于或大于4时停止,并在每次遍历中将2加到索引上。第二维相同。同样:这仅适用于恒定的步骤。

您必须在内部执行完全不同的语法- x[[1,3]][:,[1,3]]实际要做的是创建一个仅包含原始数组中第1行和第3行的新数组(与x[[1,3]]部件一起完成),然后重新切片-创建第三个数组-仅包含上一个数组的第1列和第3列。

With numpy, you can pass a slice for each component of the index – so, your x[0:2,0:2] example above works.

If you just want to evenly skip columns or rows, you can pass slices with three components (i.e. start, stop, step).

Again, for your example above:

>>> x[1:4:2, 1:4:2]
array([[ 5,  7],
       [13, 15]])

Which is basically: slice in the first dimension, with start at index 1, stop when index is equal or greater than 4, and add 2 to the index in each pass. The same for the second dimension. Again: this only works for constant steps.

The syntax you got to do something quite different internally – what x[[1,3]][:,[1,3]] actually does is create a new array including only rows 1 and 3 from the original array (done with the x[[1,3]] part), and then re-slice that – creating a third array – including only columns 1 and 3 of the previous array.


回答 5

我在这里有一个类似的问题:以最Python的方式在ndarray的sub-ndarray中编写。Python 2

遵循针对您的案例的上一篇解决方案后,解决方案如下所示:

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

使用ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

这是:

array([[ 5,  7],
       [13, 15]])

I have a similar question here: Writting in sub-ndarray of a ndarray in the most pythonian way. Python 2 .

Following the solution of previous post for your case the solution looks like:

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

An using ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

Which is:

array([[ 5,  7],
       [13, 15]])

回答 6

我不确定这有多有效,但是您可以使用range()在两个轴上切片

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 

I’m not sure how efficient this is but you can use range() to slice in both axis

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 

我怎样才能告诉PyCharm参数期望是什么类型?

问题:我怎样才能告诉PyCharm参数期望是什么类型?

当涉及到构造函数,赋值和方法调用时,PyCharm IDE非常擅长分析我的源代码并弄清楚每个变量应该是什么类型。我很喜欢它,因为它给了我很好的代码完成和参数信息,并且如果我尝试访问一个不存在的属性,它会给我警告。

但是当涉及到参数时,它一无所知。代码完成下拉列表无法显示任何内容,因为它们不知道参数的类型。代码分析无法查找警告。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

peasant = Person("Dennis", 37)
# PyCharm knows that the "peasant" variable is of type Person
peasant.dig_filth()   # shows warning -- Person doesn't have a dig_filth method

class King:
    def repress(self, peasant):
        # PyCharm has no idea what type the "peasant" parameter should be
        peasant.knock_over()   # no warning even though knock_over doesn't exist

King().repress(peasant)
# Even if I call the method once with a Person instance, PyCharm doesn't
# consider that to mean that the "peasant" parameter should always be a Person

这在一定程度上是有意义的。其他呼叫站点可以为该参数传递任何内容。但是,如果我的方法希望参数的类型为,则pygame.Surface我希望能够以某种方式向PyCharm指出,因此它可以Surface在其代码完成下拉列表中向我显示所有的属性,并在警告时突出显示警告我调用了错误的方法,依此类推。

有没有办法给PyCharm一个提示,然后说“ psst,该参数应该是X类型”?(或者,也许是本着动态语言的精神,“这个参数应该像X一样嘎嘎叫”?对此我可以接受。)


编辑:下面的CrazyCoder的答案就可以了。对于像我这样想要快速摘要的任何新手,这里是:

class King:
    def repress(self, peasant):
        """
        Exploit the workers by hanging on to outdated imperialist dogma which
        perpetuates the economic and social differences in our society.

        @type peasant: Person
        @param peasant: Person to repress.
        """
        peasant.knock_over()   # Shows a warning. And there was much rejoicing.

相关部分是@type peasant: Person文档字符串的行。

如果还转到“文件”>“设置”>“ Python集成工具”,并将“文档字符串格式”设置为“ Epytext”,则PyCharm的“视图”>“快速文档查找”将漂亮地打印参数信息,而不是仅按原样打印所有@ -lines。

When it comes to constructors, and assignments, and method calls, the PyCharm IDE is pretty good at analyzing my source code and figuring out what type each variable should be. I like it when it’s right, because it gives me good code-completion and parameter info, and it gives me warnings if I try to access an attribute that doesn’t exist.

But when it comes to parameters, it knows nothing. The code-completion dropdowns can’t show anything, because they don’t know what type the parameter will be. The code analysis can’t look for warnings.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

peasant = Person("Dennis", 37)
# PyCharm knows that the "peasant" variable is of type Person
peasant.dig_filth()   # shows warning -- Person doesn't have a dig_filth method

class King:
    def repress(self, peasant):
        # PyCharm has no idea what type the "peasant" parameter should be
        peasant.knock_over()   # no warning even though knock_over doesn't exist

King().repress(peasant)
# Even if I call the method once with a Person instance, PyCharm doesn't
# consider that to mean that the "peasant" parameter should always be a Person

This makes a certain amount of sense. Other call sites could pass anything for that parameter. But if my method expects a parameter to be of type, say, pygame.Surface, I’d like to be able to indicate that to PyCharm somehow, so it can show me all of Surface‘s attributes in its code-completion dropdown, and highlight warnings if I call the wrong method, and so on.

Is there a way I can give PyCharm a hint, and say “psst, this parameter is supposed to be of type X”? (Or perhaps, in the spirit of dynamic languages, “this parameter is supposed to quack like an X”? I’d be fine with that.)


EDIT: CrazyCoder’s answer, below, does the trick. For any newcomers like me who want the quick summary, here it is:

class King:
    def repress(self, peasant):
        """
        Exploit the workers by hanging on to outdated imperialist dogma which
        perpetuates the economic and social differences in our society.

        @type peasant: Person
        @param peasant: Person to repress.
        """
        peasant.knock_over()   # Shows a warning. And there was much rejoicing.

The relevant part is the @type peasant: Person line of the docstring.

If you also go to File > Settings > Python Integrated Tools and set “Docstring format” to “Epytext”, then PyCharm’s View > Quick Documentation Lookup will pretty-print the parameter information instead of just printing all the @-lines as-is.


回答 0

是的,您可以对方法及其参数使用特殊的文档格式,以便PyCharm可以知道类型。最新的PyCharm版本支持大多数常见的doc格式

例如,PyCharm从@param样式注释中提取类型。

另请参见reStructuredTextdocstring约定(PEP 257)。

另一个选择是Python 3注释。

参阅PyCharm文档部分以获取更多详细信息和示例。

Yes, you can use special documentation format for methods and their parameters so that PyCharm can know the type. Recent PyCharm version supports most common doc formats.

For example, PyCharm extracts types from @param style comments.

See also reStructuredText and docstring conventions (PEP 257).

Another option is Python 3 annotations.

Please refer to the PyCharm documentation section for more details and samples.


回答 1

如果您使用的是Python 3.0或更高版本,则还可以在函数和参数上使用注释。PyCharm会将这些解释为参数或返回值应具有的类型:

class King:
    def repress(self, peasant: Person) -> bool:
        peasant.knock_over() # Shows a warning. And there was much rejoicing.

        return peasant.badly_hurt() # Lets say, its not known from here that this method will always return a bool

有时,这对于不需要文档字符串的非公共方法很有用。另外一个好处是,这些注释可以通过代码访问:

>>> King.repress.__annotations__
{'peasant': <class '__main__.Person'>, 'return': <class 'bool'>}

更新:从PEP 484(已为Python 3.5接受)开始,使用注释指定参数和返回类型也是官方约定。

If you are using Python 3.0 or later, you can also use annotations on functions and parameters. PyCharm will interpret these as the type the arguments or return values are expected to have:

class King:
    def repress(self, peasant: Person) -> bool:
        peasant.knock_over() # Shows a warning. And there was much rejoicing.

        return peasant.badly_hurt() # Lets say, its not known from here that this method will always return a bool

Sometimes this is useful for non-public methods, that do not need a docstring. As an added benefit, those annotations can be accessed by code:

>>> King.repress.__annotations__
{'peasant': <class '__main__.Person'>, 'return': <class 'bool'>}

Update: As of PEP 484, which has been accepted for Python 3.5, it is also the official convention to specify argument and return types using annotations.


回答 2

PyCharm从@type pydoc字符串中提取类型。在此处此处查看PyCharm文档,以及Epydoc文档。它位于PyCharm的“旧版”部分,也许缺少某些功能。

class King:
    def repress(self, peasant):
        """
        Exploit the workers by hanging on to outdated imperialist dogma which
        perpetuates the economic and social differences in our society.

        @type peasant: Person
        @param peasant: Person to repress.
        """
        peasant.knock_over()   # Shows a warning. And there was much rejoicing.

相关部分是@type peasant: Person文档字符串的行。

我的目的不是要从CrazyCoder或原始提问者那里窃取分数,而应尽一切可能给予分数。我只是以为简单的答案应该在“答案”栏中。

PyCharm extracts types from a @type pydoc string. See PyCharm docs here and here, and Epydoc docs. It’s in the ‘legacy’ section of PyCharm, perhaps it lacks some functionality.

class King:
    def repress(self, peasant):
        """
        Exploit the workers by hanging on to outdated imperialist dogma which
        perpetuates the economic and social differences in our society.

        @type peasant: Person
        @param peasant: Person to repress.
        """
        peasant.knock_over()   # Shows a warning. And there was much rejoicing.

The relevant part is the @type peasant: Person line of the docstring.

My intention is not to steal points from CrazyCoder or the original questioner, by all means give them their points. I just thought the simple answer should be in an ‘answer’ slot.


回答 3

我正在使用PyCharm Professional 2016.1编写py2.6-2.7代码,发现使用reStructuredText可以以更简洁的方式表达类型:

class Replicant(object):
    pass


class Hunter(object):
    def retire(self, replicant):
        """ Retire the rogue or non-functional replicant.
        :param Replicant replicant: the replicant to retire.
        """
        replicant.knock_over()  # Shows a warning.

参见:https : //www.jetbrains.com/help/pycharm/2016.1/type-hinting-in-pycharm.html#legacy

I’m using PyCharm Professional 2016.1 writing py2.6-2.7 code, and I found that using reStructuredText I can express types in a more succint way:

class Replicant(object):
    pass


class Hunter(object):
    def retire(self, replicant):
        """ Retire the rogue or non-functional replicant.
        :param Replicant replicant: the replicant to retire.
        """
        replicant.knock_over()  # Shows a warning.

See: https://www.jetbrains.com/help/pycharm/2016.1/type-hinting-in-pycharm.html#legacy


回答 4

您还可以断言一个类型,Pycharm会推断出它:

def my_function(an_int):
    assert isinstance(an_int, int)
    # Pycharm now knows that an_int is of type int
    pass

You can also assert for a type and Pycharm will infer it:

def my_function(an_int):
    assert isinstance(an_int, int)
    # Pycharm now knows that an_int is of type int
    pass

用Python注释函数的正确方法是什么?

问题:用Python注释函数的正确方法是什么?

有没有一种普遍接受的方法来注释Python中的函数?可以接受以下内容吗?

#########################################################
# Create a new user
#########################################################
def add(self):

Is there a generally accepted way to comment functions in Python? Is the following acceptable?

#########################################################
# Create a new user
#########################################################
def add(self):

回答 0

正确的方法是提供文档字符串。这样,help(add)还将吐出您的评论。

def add(self):
    """Create a new user.
    Line 2 of comment...
    And so on... 
    """

那是三个双引号来打开评论,另外三个双引号来结束它。您也可以使用任何有效的Python字符串。它不必是多行的,双引号可以替换为单引号。

请参阅:PEP 257

The correct way to do it is to provide a docstring. That way, help(add) will also spit out your comment.

def add(self):
    """Create a new user.
    Line 2 of comment...
    And so on... 
    """

That’s three double quotes to open the comment and another three double quotes to end it. You can also use any valid Python string. It doesn’t need to be multiline and double quotes can be replaced by single quotes.

See: PEP 257


回答 1

使用其他人已经写过的文档字符串。

您甚至可以更进一步,并在文档字符串中添加一个文档测试,从而使对功能的自动测试变得轻而易举

Use a docstring, as others have already written.

You can even go one step further and add a doctest to your docstring, making automated testing of your functions a snap.


回答 2

使用文档字符串

作为模块,函数,类或方法定义中的第一条语句出现的字符串文字。这样的文档字符串成为该__doc__对象的特殊属性。

通常,所有模块都应具有文档字符串,并且模块导出的所有函数和类也应具有文档字符串。公共方法(包括__init__构造函数)也应具有文档字符串。包可以记录在__init__.py包目录中文件的模块文档字符串中。

Python代码其他地方出现的字符串文字也可以用作文档。它们无法被Python字节码编译器识别,并且不能作为运行时对象属性(即未分配给__doc__)进行访问,但是软件工具可以提取两种类型的额外docstring:

  1. 在模块,类或__init__方法的顶级进行简单分配后立即出现的字符串文字称为“属性文档字符串”。
  2. 在另一个文档字符串之后立即出现的字符串文字称为“其他文档字符串”。

有关属性和附加文档字符串的详细说明,请参见PEP 258,“ Docutils设计规范” [2]

Use a docstring:

A string literal that occurs as the first statement in a module, function, class, or method definition. Such a docstring becomes the __doc__ special attribute of that object.

All modules should normally have docstrings, and all functions and classes exported by a module should also have docstrings. Public methods (including the __init__ constructor) should also have docstrings. A package may be documented in the module docstring of the __init__.py file in the package directory.

String literals occurring elsewhere in Python code may also act as documentation. They are not recognized by the Python bytecode compiler and are not accessible as runtime object attributes (i.e. not assigned to __doc__ ), but two types of extra docstrings may be extracted by software tools:

  1. String literals occurring immediately after a simple assignment at the top level of a module, class, or __init__ method are called “attribute docstrings”.
  2. String literals occurring immediately after another docstring are called “additional docstrings”.

Please see PEP 258 , “Docutils Design Specification” [2] , for a detailed description of attribute and additional docstrings…


回答 3

良好评论的原则是相当主观的,但是这里有一些准则:

  • 函数注释应描述函数的意图,而不是实现
  • 概述您的功能对系统状态所做的任何假设。如果它使用任何全局变量(tsk,tsk),请列出它们。
  • 提防过多的ASCII艺术。散列很长的字符串似乎使注释更易于阅读,但是当注释更改时,它们可能很烦人
  • 利用提供“自动文档”的语言功能,例如,Python中的文档字符串,Perl中的POD和Java中的Javadoc

The principles of good commenting are fairly subjective, but here are some guidelines:

  • Function comments should describe the intent of a function, not the implementation
  • Outline any assumptions that your function makes with regards to system state. If it uses any global variables (tsk, tsk), list those.
  • Watch out for excessive ASCII art. Having long strings of hashes may seem to make the comments easier to read, but they can be annoying to deal with when comments change
  • Take advantage of language features that provide ‘auto documentation’, i.e., docstrings in Python, POD in Perl, and Javadoc in Java

回答 4

阅读有关在Python代码中使用文档字符串的信息

按照Python docstring约定

函数或方法的文档字符串应总结其行为,并记录其参数,返回值,副作用,引发的异常以及何时可以调用它的限制(所有这些均适用)。应该指出可选参数。应该记录关键字参数是否是接口的一部分。

没有黄金法则,而是提供评论,这对于您团队中的其他开发人员(如果有的话)或对您自己意味着意义的评论,如果您在六个月后再回到它的话。

Read about using docstrings in your Python code.

As per the Python docstring conventions:

The docstring for a function or method should summarize its behavior and document its arguments, return value(s), side effects, exceptions raised, and restrictions on when it can be called (all if applicable). Optional arguments should be indicated. It should be documented whether keyword arguments are part of the interface.

There will be no golden rule, but rather provide comments that mean something to the other developers on your team (if you have one) or even to yourself when you come back to it six months down the road.


回答 5

我会去进行与Sphinx等文档工具集成的文档实践。

第一步是使用docstring

def add(self):
 """ Method which adds stuff
 """

I would go for a documentation practice that integrates with a documentation tool such as Sphinx.

The first step is to use a docstring:

def add(self):
 """ Method which adds stuff
 """

回答 6

除了说“使用文档字符串”外,我将走得更远。选择一个文档生成工具,例如pydoc或epydoc(我在pyparsing中使用epydoc),然后使用该工具可以识别的标记语法。在进行开发时经常运行该工具,以发现文档中的漏洞。实际上,您甚至可以从实现类之前为类的成员编写文档字符串中受益。

I would go a step further than just saying “use a docstring”. Pick a documentation generation tool, such as pydoc or epydoc (I use epydoc in pyparsing), and use the markup syntax recognized by that tool. Run that tool often while you are doing your development, to identify holes in your documentation. In fact, you might even benefit from writing the docstrings for the members of a class before implementing the class.


回答 7

docstrings

这是PyCharm中针对功能描述注释的内置建议约定:

def test_function(p1, p2, p3):
    """
    my function does blah blah blah

    :param p1: 
    :param p2: 
    :param p3: 
    :return: 
    """

Use docstrings.

This is the built-in suggested convention in PyCharm for describing function using docstring comments:

def test_function(p1, p2, p3):
    """
    test_function does blah blah blah.

    :param p1: describe about parameter p1
    :param p2: describe about parameter p2
    :param p3: describe about parameter p3
    :return: describe what it returns
    """ 
    pass

回答 8

虽然我同意这不应该是评论,但应该是大多数(所有?)答案都建议的文档字符串,但我想添加numpydoc(文档字符串样式指南)

如果这样做,您可以(1)自动生成文档,并且(2)人们可以识别出这一点,并且可以更轻松地读取代码。

While I agree that this should not be a comment, but a docstring as most (all?) answers suggest, I want to add numpydoc (a docstring style guide).

If you do it like this, you can (1) automatically generate documentation and (2) people recognize this and have an easier time to read your code.


回答 9

您可以使用三个引号来做到这一点。

您可以使用单引号:

def myfunction(para1,para2):
  '''
  The stuff inside the function
  '''

或双引号:

def myfunction(para1,para2):
  """
  The stuff inside the function
  """

You can use three quotes to do it.

You can use single quotes:

def myfunction(para1,para2):
  '''
  The stuff inside the function
  '''

Or double quotes:

def myfunction(para1,para2):
  """
  The stuff inside the function
  """

什么时候应使用Flask.g?

问题:什么时候应使用Flask.g?

g将请求上下文移动到应用程序上下文瓶0.10,这让我感到困惑的预期用途g

我的理解(对于Flask 0.9)是:

  • g 驻留在请求上下文中,即在请求开始时重新创建,直到结束时可用
  • g旨在用作“请求黑板”,在这里我可以放置与请求持续时间相关的内容(即,在请求的开始处设置一个标志,并在结束时(可能从before_request/ after_request对开始)进行处理)
  • 除了保持请求级别状态外,g还可以并且应该用于资源管理,即保持数据库连接等。

在Flask 0.10中,以下哪句话不再适用?有人可以指点我讨论这种变化原因的资源吗?在Flask 0.10中,我应该将什么用作“请求黑板”?我应该创建自己的应用程序/扩展特定于线程的本地代理并将其推送到上下文堆栈before_request吗?如果我的应用程序生存时间很长(不像请求),因此资源从未被释放,那么在应用程序上下文中资源管理的意义何在?

I saw that g will move from the request context to the app context in Flask 0.10, which made me confused about the intended use of g.

My understanding (for Flask 0.9) is that:

  • g lives in the request context, i.e., created afresh when the requests starts, and available until it ends
  • g is intended to be used as a “request blackboard”, where I can put stuff relevant for the duration of the request (i.e., set a flag at the beginning of the request and handle it at the end, possibly from a before_request/after_request pair)
  • in addition to holding request-level-state, g can and should be used for resource management, i.e., holding database connections, etc.

Which of these sentences are no longer true in Flask 0.10? Can someone point me to a resource discussing the reasons for the change? What should I use as a “request blackboard” in Flask 0.10 – should I create my own app/extension specific thread-local proxy and push it to the context stack before_request? What’s the point of resource management at the application context, if my application lives for a long while (not like a request) and thus the resources are never freed?


回答 0

高级瓶模式,如由马库斯联系,解释了一些变化到g0.10:

  • g 现在位于应用程序上下文中。
  • 每个请求都会推送一个新的应用程序上下文,从而清除旧的应用程序上下文,因此g仍可以用于按请求设置标志,而无需更改代码。
  • 调用 弹出应用程序上下文teardown_request。(Armin的演示文稿解释了这是因为创建数据库连接之类的事情是为请求设置环境的任务,不应在before_request和中处理after_request

Advanced Flask Patterns, as linked by Markus, explains some of the changes to g in 0.10:

  • g now lives in the application context.
  • Every request pushes a new application context, wiping the old one, so g can still be used to set flags per-request without change to code.
  • The application context is popped after teardown_request is called. (Armin’s presentation explains this is because things like creating DB connections are tasks which setup the environment for the request, and should not be handled inside before_request and after_request)

回答 1

作为该线程中信息的附录:我也flask.g对它的行为感到困惑,但是一些快速测试帮助我弄清了它。这是我尝试过的方法:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in first request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to xyz')
        g.foo = 'xyz'
        print('g.foo should be xyz, is: {0}'.format(g.foo))

    print('in app context, after first request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in second request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to pqr')
        g.foo = 'pqr'
        print('g.foo should be pqr, is: {0}'.format(g.foo))

    print('in app context, after second request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

这是它提供的输出:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be abc, is: abc
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in app context, after first request context
g.foo should be abc, is: xyz  

in second request context
g.foo should be abc, is: xyz
setting g.foo to pqr
g.foo should be pqr, is: pqr  

in app context, after second request context
g.foo should be abc, is: pqr

正如Y4Kman所说的那样,“每个请求都推送一个新的应用程序上下文”。而随着烧瓶文档说,应用程序上下文“将不被请求之间共享”。现在,尚未明确说明的内容(尽管我猜想这是这些语句的隐含内容),而我的测试清楚地表明,您永远不应明确创建嵌套在一个应用程序上下文中的多个请求上下文,因为flask.g(和co)没有它具有任何神奇的功能,使其可以在上下文的两个不同“级别”中起作用,并且在应用程序和请求级别独立存在不同的状态。

现实情况是,“应用程序上下文”可能app.app_context() 一个颇具误导性的名称,因为每个请求上下文,与“请求上下文”完全相同。将其视为“请求上下文精简版”,仅在需要一些通常需要请求上下文的变量但不需要访问任何请求对象的情况下才需要(例如,在数据库中运行批处理DB操作时)外壳脚本)。如果您尝试将应用程序上下文扩展为包含多个请求上下文,那么您将遇到麻烦。因此,您应该在Flask的上下文中编写如下代码,而不是上面的测试:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in first request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to xyz')
    g.foo = 'xyz'
    print('g.foo should be xyz, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in second request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to pqr')
    g.foo = 'pqr'
    print('g.foo should be pqr, is: {0}'.format(g.foo))

这将产生预期的结果:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be None, is: None
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in second request context
g.foo should be None, is: None
setting g.foo to pqr
g.foo should be pqr, is: pqr

As an addendum to the information in this thread: I’ve been a bit confused by the behavior of flask.g too, but some quick testing has helped me to clarify it. Here’s what I tried out:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in first request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to xyz')
        g.foo = 'xyz'
        print('g.foo should be xyz, is: {0}'.format(g.foo))

    print('in app context, after first request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in second request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to pqr')
        g.foo = 'pqr'
        print('g.foo should be pqr, is: {0}'.format(g.foo))

    print('in app context, after second request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

And here’s the output that it gives:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be abc, is: abc
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in app context, after first request context
g.foo should be abc, is: xyz  

in second request context
g.foo should be abc, is: xyz
setting g.foo to pqr
g.foo should be pqr, is: pqr  

in app context, after second request context
g.foo should be abc, is: pqr

As theY4Kman said above, “Every request pushes a new application context”. And as the Flask docs say, the application context “will not be shared between requests”. Now, what hasn’t been explicitly stated (although I guess it’s implied from these statements), and what my testing clearly shows, is that you should never explicitly create multiple request contexts nested inside one application context, because flask.g (and co) doesn’t have any magic whereby it functions in the two different “levels” of context, with different states existing independently at the application and request levels.

The reality is that “application context” is potentially quite a misleading name, because app.app_context() is a per-request context, exactly the same as the “request context”. Think of it as a “request context lite”, only required in the case where you need some of the variables that normally require a request context, but you don’t need access to any request object (e.g. when running batch DB operations in a shell script). If you try and extend the application context to encompass more than one request context, you’re asking for trouble. So, rather than my test above, you should instead write code like this with Flask’s contexts:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in first request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to xyz')
    g.foo = 'xyz'
    print('g.foo should be xyz, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in second request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to pqr')
    g.foo = 'pqr'
    print('g.foo should be pqr, is: {0}'.format(g.foo))

Which will give the expected results:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be None, is: None
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in second request context
g.foo should be None, is: None
setting g.foo to pqr
g.foo should be pqr, is: pqr

不区分大小写的替换

问题:不区分大小写的替换

在Python中执行不区分大小写的字符串替换的最简单方法是什么?

What’s the easiest way to do a case-insensitive string replacement in Python?


回答 0

string类型不支持此功能。您最好使用带有re.IGNORECASE选项的正则表达式子方法

>>> import re
>>> insensitive_hippo = re.compile(re.escape('hippo'), re.IGNORECASE)
>>> insensitive_hippo.sub('giraffe', 'I want a hIPpo for my birthday')
'I want a giraffe for my birthday'

The string type doesn’t support this. You’re probably best off using the regular expression sub method with the re.IGNORECASE option.

>>> import re
>>> insensitive_hippo = re.compile(re.escape('hippo'), re.IGNORECASE)
>>> insensitive_hippo.sub('giraffe', 'I want a hIPpo for my birthday')
'I want a giraffe for my birthday'

回答 1

import re
pattern = re.compile("hello", re.IGNORECASE)
pattern.sub("bye", "hello HeLLo HELLO")
# 'bye bye bye'
import re
pattern = re.compile("hello", re.IGNORECASE)
pattern.sub("bye", "hello HeLLo HELLO")
# 'bye bye bye'

回答 2

在一行中:

import re
re.sub("(?i)hello","bye", "hello HeLLo HELLO") #'bye bye bye'
re.sub("(?i)he\.llo","bye", "he.llo He.LLo HE.LLO") #'bye bye bye'

或者,使用可选的“标志”参数:

import re
re.sub("hello", "bye", "hello HeLLo HELLO", flags=re.I) #'bye bye bye'
re.sub("he\.llo", "bye", "he.llo He.LLo HE.LLO", flags=re.I) #'bye bye bye'

In a single line:

import re
re.sub("(?i)hello","bye", "hello HeLLo HELLO") #'bye bye bye'
re.sub("(?i)he\.llo","bye", "he.llo He.LLo HE.LLO") #'bye bye bye'

Or, use the optional “flags” argument:

import re
re.sub("hello", "bye", "hello HeLLo HELLO", flags=re.I) #'bye bye bye'
re.sub("he\.llo", "bye", "he.llo He.LLo HE.LLO", flags=re.I) #'bye bye bye'

回答 3

继续bFloch的回答,此功能将不改变任何一种,而是将所有旧出现的内容更改为新内容-以不区分大小写的方式。

def ireplace(old, new, text):
    idx = 0
    while idx < len(text):
        index_l = text.lower().find(old.lower(), idx)
        if index_l == -1:
            return text
        text = text[:index_l] + new + text[index_l + len(old):]
        idx = index_l + len(new) 
    return text

Continuing on bFloch’s answer, this function will change not one, but all occurrences of old with new – in a case insensitive fashion.

def ireplace(old, new, text):
    idx = 0
    while idx < len(text):
        index_l = text.lower().find(old.lower(), idx)
        if index_l == -1:
            return text
        text = text[:index_l] + new + text[index_l + len(old):]
        idx = index_l + len(new) 
    return text

回答 4

就像布莱尔·康拉德(Blair Conrad)所说的那样,string.replace不支持这一点。

使用regex re.sub,但请记住先转义替换字符串。请注意,在2.6中没有for的flags-option re.sub,因此您必须使用Embedded修饰符'(?i)'(或RE对象,请参阅Blair Conrad的答案)。另外,另一个陷阱是,如果给出了字符串,sub将在替换文本中处理反斜杠转义。为了避免这种情况,可以传入lambda。

这是一个函数:

import re
def ireplace(old, repl, text):
    return re.sub('(?i)'+re.escape(old), lambda m: repl, text)

>>> ireplace('hippo?', 'giraffe!?', 'You want a hiPPO?')
'You want a giraffe!?'
>>> ireplace(r'[binfolder]', r'C:\Temp\bin', r'[BinFolder]\test.exe')
'C:\\Temp\\bin\\test.exe'

Like Blair Conrad says string.replace doesn’t support this.

Use the regex re.sub, but remember to escape the replacement string first. Note that there’s no flags-option in 2.6 for re.sub, so you’ll have to use the embedded modifier '(?i)' (or a RE-object, see Blair Conrad’s answer). Also, another pitfall is that sub will process backslash escapes in the replacement text, if a string is given. To avoid this one can instead pass in a lambda.

Here’s a function:

import re
def ireplace(old, repl, text):
    return re.sub('(?i)'+re.escape(old), lambda m: repl, text)

>>> ireplace('hippo?', 'giraffe!?', 'You want a hiPPO?')
'You want a giraffe!?'
>>> ireplace(r'[binfolder]', r'C:\Temp\bin', r'[BinFolder]\test.exe')
'C:\\Temp\\bin\\test.exe'

回答 5

此函数同时使用str.replace()re.findall()函数。它将以不区分大小写的方式替换patternin中所有出现的情况。stringrepl

def replace_all(pattern, repl, string) -> str:
   occurences = re.findall(pattern, string, re.IGNORECASE)
   for occurence in occurences:
       string = string.replace(occurence, repl)
       return string

This function uses both the str.replace() and re.findall() functions. It will replace all occurences of pattern in string with repl in a case-insensitive way.

def replace_all(pattern, repl, string) -> str:
   occurences = re.findall(pattern, string, re.IGNORECASE)
   for occurence in occurences:
       string = string.replace(occurence, repl)
       return string

回答 6

这不需要RegularExp

def ireplace(old, new, text):
    """ 
    Replace case insensitive
    Raises ValueError if string not found
    """
    index_l = text.lower().index(old.lower())
    return text[:index_l] + new + text[index_l + len(old):] 

This doesn’t require RegularExp

def ireplace(old, new, text):
    """ 
    Replace case insensitive
    Raises ValueError if string not found
    """
    index_l = text.lower().index(old.lower())
    return text[:index_l] + new + text[index_l + len(old):] 

回答 7

关于语法细节和选项的有趣观察:

在Win32上的Python 3.7.2(tags / v3.7.2:9a3ffc0492,2018年12月23日,23:09:28)[MSC v.1916 64位(AMD64)]

import re
old = "TREEROOT treeroot TREerOot"
re.sub(r'(?i)treeroot', 'grassroot', old)

‘草根草根草根’

re.sub(r'treeroot', 'grassroot', old)

‘TREEROOT草根TREerOot’

re.sub(r'treeroot', 'grassroot', old, flags=re.I)

‘草根草根草根’

re.sub(r'treeroot', 'grassroot', old, re.I)

‘TREEROOT草根TREerOot’

因此,match表达式中的(?i)前缀或添加“ flags = re.I”作为第四个参数将导致不区分大小写的匹配。但是,仅使用“ re.I”作为第四个参数不会导致不区分大小写的匹配。

为了比较,

re.findall(r'treeroot', old, re.I)

[‘TREEROOT’,’treeroot’,’TREerOot’]

re.findall(r'treeroot', old)

[‘treeroot’]

An interesting observation about syntax details and options:

Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32

import re
old = "TREEROOT treeroot TREerOot"
re.sub(r'(?i)treeroot', 'grassroot', old)

‘grassroot grassroot grassroot’

re.sub(r'treeroot', 'grassroot', old)

‘TREEROOT grassroot TREerOot’

re.sub(r'treeroot', 'grassroot', old, flags=re.I)

‘grassroot grassroot grassroot’

re.sub(r'treeroot', 'grassroot', old, re.I)

‘TREEROOT grassroot TREerOot’

So the (?i) prefix in the match expression or adding “flags=re.I” as a fourth argument will result in a case-insensitive match. BUT, using just “re.I” as the fourth argument does not result in case-insensitive match.

For comparison,

re.findall(r'treeroot', old, re.I)

[‘TREEROOT’, ‘treeroot’, ‘TREerOot’]

re.findall(r'treeroot', old)

[‘treeroot’]


回答 8

我正在将\ t转换为转义序列(向下滚动),因此我注意到re.sub将反斜杠的转义字符转换为转义序列。

为了防止这种情况,我写了以下内容:

替换不区分大小写。

import re
    def ireplace(findtxt, replacetxt, data):
        return replacetxt.join(  re.compile(findtxt, flags=re.I).split(data)  )

另外,如果您希望将其替换为转义字符,例如此处的其他答案,这些特殊含义是将bashslash字符转换为转义序列,则只需对您的查找和解码,或替换字符串即可。在Python 3中,可能必须执行类似.decode(“ unicode_escape”)#python3的操作

findtxt = findtxt.decode('string_escape') # python2
replacetxt = replacetxt.decode('string_escape') # python2
data = ireplace(findtxt, replacetxt, data)

在Python 2.7.8中测试

希望有帮助。

I was having \t being converted to the escape sequences (scroll a bit down), so I noted that re.sub converts backslashed escaped characters to escape sequences.

To prevent that I wrote the following:

Replace case insensitive.

import re
    def ireplace(findtxt, replacetxt, data):
        return replacetxt.join(  re.compile(findtxt, flags=re.I).split(data)  )

Also, if you want it to replace with the escape characters, like the other answers here that are getting the special meaning bashslash characters converted to escape sequences, just decode your find and, or replace string. In Python 3, might have to do something like .decode(“unicode_escape”) # python3

findtxt = findtxt.decode('string_escape') # python2
replacetxt = replacetxt.decode('string_escape') # python2
data = ireplace(findtxt, replacetxt, data)

Tested in Python 2.7.8

Hope that helps.


回答 9

之前从未发布过答案,并且该线程确实很旧,但是我想出了另一种解决方案,并认为我可以得到您的回应,我在Python编程中经验不足,因此,如果它有明显的缺点,请指出来,因为它的良好学习是: )

i='I want a hIPpo for my birthday'
key='hippo'
swp='giraffe'

o=(i.lower().split(key))
c=0
p=0
for w in o:
    o[c]=i[p:p+len(w)]
    p=p+len(key+w)
    c+=1
print(swp.join(o))

never posted an answer before and this thread is really old but i came up with another sollution and figured i could get your respons, Im not seasoned in Python programming so if there are appearant drawbacks to it, please point them out since its good learning :)

i='I want a hIPpo for my birthday'
key='hippo'
swp='giraffe'

o=(i.lower().split(key))
c=0
p=0
for w in o:
    o[c]=i[p:p+len(w)]
    p=p+len(key+w)
    c+=1
print(swp.join(o))