mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge pull request #2012 from RasmusWL/python-modernise-cls-self-checks
Python: modernise cls self argument name checks
This commit is contained in:
@@ -5,14 +5,14 @@
|
||||
|
||||
|
||||
<overview>
|
||||
<p> The first argument of a class method, a new method or any metaclass method
|
||||
should be called <code>cls</code>. This makes the purpose of the argument clear to other developers.
|
||||
<p> The first parameter of a class method, a new method or any metaclass method
|
||||
should be called <code>cls</code>. This makes the purpose of the parameter clear to other developers.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Change the name of the first argument to <code>cls</code> as recommended by the style guidelines
|
||||
<p>Change the name of the first parameter to <code>cls</code> as recommended by the style guidelines
|
||||
in PEP 8.</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @name First parameter of a class method is not named 'cls'
|
||||
* @description Using an alternative name for the first argument of a class method makes code more
|
||||
* difficult to read; PEP8 states that the first argument to class methods should be 'cls'.
|
||||
* @description Using an alternative name for the first parameter of a class method makes code more
|
||||
* difficult to read; PEP8 states that the first parameter to class methods should be 'cls'.
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
* readability
|
||||
@@ -16,32 +16,33 @@ import python
|
||||
|
||||
predicate first_arg_cls(Function f) {
|
||||
exists(string argname | argname = f.getArgName(0) |
|
||||
argname = "cls" or
|
||||
argname = "cls"
|
||||
or
|
||||
/* Not PEP8, but relatively common */
|
||||
argname = "mcls"
|
||||
)
|
||||
}
|
||||
|
||||
predicate is_type_method(Function f) {
|
||||
exists(ClassObject c | c.getPyClass() = f.getScope() and c.getASuperType() = theTypeType())
|
||||
exists(ClassValue c | c.getScope() = f.getScope() and c.getASuperType() = ClassValue::type())
|
||||
}
|
||||
|
||||
predicate classmethod_decorators_only(Function f) {
|
||||
forall(Expr decorator |
|
||||
decorator = f.getADecorator() |
|
||||
((Name) decorator).getId() = "classmethod")
|
||||
forall(Expr decorator | decorator = f.getADecorator() | decorator.(Name).getId() = "classmethod")
|
||||
}
|
||||
|
||||
from Function f, string message
|
||||
where (f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and
|
||||
not first_arg_cls(f) and classmethod_decorators_only(f) and
|
||||
not f.getName() = "__new__" and
|
||||
(
|
||||
if exists(f.getArgName(0)) then
|
||||
message = "Class methods or methods of a type deriving from type should have 'cls', rather than '" +
|
||||
f.getArgName(0) + "', as their first argument."
|
||||
else
|
||||
message = "Class methods or methods of a type deriving from type should have 'cls' as their first argument."
|
||||
)
|
||||
|
||||
where
|
||||
(f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and
|
||||
not first_arg_cls(f) and
|
||||
classmethod_decorators_only(f) and
|
||||
not f.getName() = "__new__" and
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message = "Class methods or methods of a type deriving from type should have 'cls', rather than '"
|
||||
+ f.getArgName(0) + "', as their first parameter."
|
||||
else
|
||||
message = "Class methods or methods of a type deriving from type should have 'cls' as their first parameter."
|
||||
)
|
||||
select f, message
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class Point:
|
||||
def __init__(val, x, y): # first argument is mis-named 'val'
|
||||
def __init__(val, x, y): # first parameter is mis-named 'val'
|
||||
val._x = x
|
||||
val._y = y
|
||||
|
||||
|
||||
class Point2:
|
||||
def __init__(self, x, y): # first argument is correctly named 'self'
|
||||
def __init__(self, x, y): # first parameter is correctly named 'self'
|
||||
self._x = x
|
||||
self._y = y
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @name First argument of a method is not named 'self'
|
||||
* @description Using an alternative name for the first argument of an instance method makes
|
||||
* code more difficult to read; PEP8 states that the first argument to instance
|
||||
* @name First parameter of a method is not named 'self'
|
||||
* @description Using an alternative name for the first parameter of an instance method makes
|
||||
* code more difficult to read; PEP8 states that the first parameter to instance
|
||||
* methods should be 'self'.
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
@@ -16,39 +16,39 @@
|
||||
import python
|
||||
import semmle.python.libraries.Zope
|
||||
|
||||
predicate first_arg_self(Function f) {
|
||||
f.getArgName(0) = "self"
|
||||
predicate is_type_method(FunctionValue fv) {
|
||||
exists(ClassValue c | c.declaredAttribute(_) = fv and c.getASuperType() = ClassValue::type())
|
||||
}
|
||||
|
||||
predicate is_type_method(FunctionObject f) {
|
||||
exists(ClassObject c | c.lookupAttribute(_) = f and c.getASuperType() = theTypeType())
|
||||
predicate used_in_defining_scope(FunctionValue fv) {
|
||||
exists(Call c | c.getScope() = fv.getScope().getScope() and c.getFunc().pointsTo(fv))
|
||||
}
|
||||
|
||||
predicate used_in_defining_scope(FunctionObject f) {
|
||||
exists(Call c |
|
||||
c.getScope() = f.getFunction().getScope() and
|
||||
c.getFunc().refersTo(f)
|
||||
)
|
||||
}
|
||||
|
||||
from Function f, PyFunctionObject func, string message
|
||||
from Function f, FunctionValue fv, string message
|
||||
where
|
||||
exists(ClassObject cls, string name |
|
||||
cls.declaredAttribute(name) = func and cls.isNewStyle() and
|
||||
not name = "__new__" and
|
||||
not name = "__metaclass__" and
|
||||
/* declared in scope */
|
||||
f.getScope() = cls.getPyClass()
|
||||
) and
|
||||
not first_arg_self(f) and not is_type_method(func) and
|
||||
func.getFunction() = f and not f.getName() = "lambda" and
|
||||
not used_in_defining_scope(func) and
|
||||
(
|
||||
if exists(f.getArgName(0)) then
|
||||
message = "Normal methods should have 'self', rather than '" + f.getArgName(0) + "', as their first parameter."
|
||||
else
|
||||
message = "Normal methods should have at least one parameter (the first of which should be 'self')." and not f.hasVarArg()
|
||||
) and
|
||||
not func instanceof ZopeInterfaceMethod
|
||||
|
||||
exists(ClassValue cls, string name |
|
||||
cls.declaredAttribute(name) = fv and
|
||||
cls.isNewStyle() and
|
||||
not name = "__new__" and
|
||||
not name = "__metaclass__" and
|
||||
/* declared in scope */
|
||||
f.getScope() = cls.getScope()
|
||||
) and
|
||||
not f.getArgName(0) = "self" and
|
||||
not is_type_method(fv) and
|
||||
fv.getScope() = f and
|
||||
not f.getName() = "lambda" and
|
||||
not used_in_defining_scope(fv) and
|
||||
(
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message = "Normal methods should have 'self', rather than '" + f.getArgName(0) +
|
||||
"', as their first parameter."
|
||||
else
|
||||
message = "Normal methods should have at least one parameter (the first of which should be 'self')."
|
||||
) and
|
||||
not f.hasVarArg()
|
||||
) and
|
||||
not fv instanceof ZopeInterfaceMethodValue
|
||||
select f, message
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
import python
|
||||
|
||||
private import semmle.python.pointsto.PointsTo
|
||||
|
||||
/** A method that to a sub-class of `zope.interface.Interface` */
|
||||
class ZopeInterfaceMethod extends PyFunctionObject {
|
||||
deprecated class ZopeInterfaceMethod extends PyFunctionObject {
|
||||
|
||||
/** Holds if this method belongs to a class that sub-classes `zope.interface.Interface` */
|
||||
ZopeInterfaceMethod() {
|
||||
@@ -26,3 +28,31 @@ class ZopeInterfaceMethod extends PyFunctionObject {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A method that belongs to a sub-class of `zope.interface.Interface` */
|
||||
class ZopeInterfaceMethodValue extends PythonFunctionValue {
|
||||
|
||||
/** Holds if this method belongs to a class that sub-classes `zope.interface.Interface` */
|
||||
ZopeInterfaceMethodValue() {
|
||||
exists(Value interface, ClassValue owner |
|
||||
interface = Module::named("zope.interface").attr("Interface") and
|
||||
owner.declaredAttribute(_) = this and
|
||||
// `zope.interface.Interface` will be recognized as a Value by the pointsTo analysis,
|
||||
// because it is the result of instantiating a "meta" class. getASuperType only returns
|
||||
// ClassValues, so we do this little trick to make things work
|
||||
Types::getBase(owner.getASuperType(), _) = interface
|
||||
)
|
||||
}
|
||||
|
||||
override int minParameters() {
|
||||
result = super.minParameters() + 1
|
||||
}
|
||||
|
||||
override int maxParameters() {
|
||||
if exists(this.getScope().getVararg()) then
|
||||
result = super.maxParameters()
|
||||
else
|
||||
result = super.maxParameters() + 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -434,6 +434,11 @@ abstract class FunctionValue extends CallableValue {
|
||||
|
||||
abstract string getQualifiedName();
|
||||
|
||||
/** Gets the minimum number of parameters that can be correctly passed to this function */
|
||||
abstract int minParameters();
|
||||
|
||||
/** Gets the maximum number of parameters that can be correctly passed to this function */
|
||||
abstract int maxParameters();
|
||||
}
|
||||
|
||||
/** Class representing Python functions */
|
||||
@@ -447,6 +452,23 @@ class PythonFunctionValue extends FunctionValue {
|
||||
result = this.(PythonFunctionObjectInternal).getScope().getQualifiedName()
|
||||
}
|
||||
|
||||
override int minParameters() {
|
||||
exists(Function f |
|
||||
f = this.getScope() and
|
||||
result = count(f.getAnArg()) - count(f.getDefinition().getArgs().getADefault())
|
||||
)
|
||||
}
|
||||
|
||||
override int maxParameters() {
|
||||
exists(Function f |
|
||||
f = this.getScope() and
|
||||
if exists(f.getVararg()) then
|
||||
result = 2147483647 // INT_MAX
|
||||
else
|
||||
result = count(f.getAnArg())
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Class representing builtin functions, such as `len` or `print` */
|
||||
@@ -460,6 +482,13 @@ class BuiltinFunctionValue extends FunctionValue {
|
||||
result = this.(BuiltinFunctionObjectInternal).getName()
|
||||
}
|
||||
|
||||
override int minParameters() {
|
||||
none()
|
||||
}
|
||||
|
||||
override int maxParameters() {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
/** Class representing builtin methods, such as `list.append` or `set.add` */
|
||||
@@ -477,6 +506,14 @@ class BuiltinMethodValue extends FunctionValue {
|
||||
)
|
||||
}
|
||||
|
||||
override int minParameters() {
|
||||
none()
|
||||
}
|
||||
|
||||
override int maxParameters() {
|
||||
none()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A class representing sequence objects with a length and tracked items.
|
||||
@@ -560,6 +597,11 @@ module ClassValue {
|
||||
result = TBuiltinClassObject(Builtin::special("FunctionType"))
|
||||
}
|
||||
|
||||
/** Get the `ClassValue` for the `type` class. */
|
||||
ClassValue type() {
|
||||
result = TType()
|
||||
}
|
||||
|
||||
/** Get the `ClassValue` for the class of builtin functions. */
|
||||
ClassValue builtinFunction() {
|
||||
result = Value::named("len").getClass()
|
||||
|
||||
@@ -1 +1 @@
|
||||
| Function yes |
|
||||
| Function Z.yes |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
import python
|
||||
import semmle.python.libraries.Zope
|
||||
|
||||
from ZopeInterfaceMethod f
|
||||
from ZopeInterfaceMethodValue f
|
||||
select f.toString()
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
edges
|
||||
| functions_test.py:39:9:39:9 | empty mutable value | functions_test.py:40:5:40:5 | empty mutable value |
|
||||
| functions_test.py:133:15:133:15 | empty mutable value | functions_test.py:134:5:134:5 | empty mutable value |
|
||||
| functions_test.py:185:25:185:25 | empty mutable value | functions_test.py:186:5:186:5 | empty mutable value |
|
||||
| functions_test.py:188:21:188:21 | empty mutable value | functions_test.py:189:5:189:5 | empty mutable value |
|
||||
| functions_test.py:191:27:191:27 | empty mutable value | functions_test.py:192:25:192:25 | empty mutable value |
|
||||
| functions_test.py:191:27:191:27 | empty mutable value | functions_test.py:193:21:193:21 | empty mutable value |
|
||||
| functions_test.py:192:25:192:25 | empty mutable value | functions_test.py:185:25:185:25 | empty mutable value |
|
||||
| functions_test.py:193:21:193:21 | empty mutable value | functions_test.py:188:21:188:21 | empty mutable value |
|
||||
| functions_test.py:151:25:151:25 | empty mutable value | functions_test.py:152:5:152:5 | empty mutable value |
|
||||
| functions_test.py:154:21:154:21 | empty mutable value | functions_test.py:155:5:155:5 | empty mutable value |
|
||||
| functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:158:25:158:25 | empty mutable value |
|
||||
| functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:159:21:159:21 | empty mutable value |
|
||||
| functions_test.py:158:25:158:25 | empty mutable value | functions_test.py:151:25:151:25 | empty mutable value |
|
||||
| functions_test.py:159:21:159:21 | empty mutable value | functions_test.py:154:21:154:21 | empty mutable value |
|
||||
#select
|
||||
| functions_test.py:40:5:40:5 | x | functions_test.py:39:9:39:9 | empty mutable value | functions_test.py:40:5:40:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:39:9:39:9 | x | Default value |
|
||||
| functions_test.py:134:5:134:5 | x | functions_test.py:133:15:133:15 | empty mutable value | functions_test.py:134:5:134:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:133:15:133:15 | x | Default value |
|
||||
| functions_test.py:186:5:186:5 | x | functions_test.py:191:27:191:27 | empty mutable value | functions_test.py:186:5:186:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:191:27:191:27 | y | Default value |
|
||||
| functions_test.py:189:5:189:5 | x | functions_test.py:191:27:191:27 | empty mutable value | functions_test.py:189:5:189:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:191:27:191:27 | y | Default value |
|
||||
| functions_test.py:152:5:152:5 | x | functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:152:5:152:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:157:27:157:27 | y | Default value |
|
||||
| functions_test.py:155:5:155:5 | x | functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:155:5:155:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:157:27:157:27 | y | Default value |
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
| argument_names.py:17:5:17:24 | Function n_cmethod | Class methods or methods of a type deriving from type should have 'cls', rather than 'self', as their first argument. |
|
||||
| argument_names.py:32:5:32:20 | Function c_method | Class methods or methods of a type deriving from type should have 'cls', rather than 'y', as their first argument. |
|
||||
| parameter_names.py:17:5:17:24 | Function n_cmethod | Class methods or methods of a type deriving from type should have 'cls', rather than 'self', as their first parameter. |
|
||||
| parameter_names.py:22:5:22:21 | Function n_cmethod2 | Class methods or methods of a type deriving from type should have 'cls' as their first parameter. |
|
||||
| parameter_names.py:37:5:37:20 | Function c_method | Class methods or methods of a type deriving from type should have 'cls', rather than 'y', as their first parameter. |
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
| argument_names.py:45:5:45:20 | Function __init__ | Normal methods should have 'self', rather than 'x', as their first parameter. |
|
||||
| argument_names.py:48:5:48:20 | Function s_method | Normal methods should have 'self', rather than 'y', as their first parameter. |
|
||||
| om_test.py:71:5:71:19 | Function __repr__ | Normal methods should have at least one parameter (the first of which should be 'self'). |
|
||||
| parameter_names.py:50:5:50:20 | Function __init__ | Normal methods should have 'self', rather than 'x', as their first parameter. |
|
||||
| parameter_names.py:53:5:53:20 | Function s_method | Normal methods should have 'self', rather than 'y', as their first parameter. |
|
||||
| parameter_names.py:56:5:56:20 | Function s_method2 | Normal methods should have at least one parameter (the first of which should be 'self'). |
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# Using name other than 'self' for first argument in methods.
|
||||
# This shouldn't apply to classmethods (first argument should be 'cls' or similar)
|
||||
# or static methods (first argument can be anything)
|
||||
|
||||
|
||||
class Normal(object):
|
||||
|
||||
def n_ok(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def n_smethod(ok):
|
||||
pass
|
||||
|
||||
# not ok
|
||||
@classmethod
|
||||
def n_cmethod(self):
|
||||
pass
|
||||
|
||||
# this is allowed because it has a decorator other than @classmethod
|
||||
@classmethod
|
||||
@id
|
||||
def n_suppress(any_name):
|
||||
pass
|
||||
|
||||
|
||||
class Class(type):
|
||||
|
||||
def __init__(cls):
|
||||
pass
|
||||
|
||||
def c_method(y):
|
||||
pass
|
||||
|
||||
def c_ok(cls):
|
||||
pass
|
||||
|
||||
@id
|
||||
def c_suppress(any_name):
|
||||
pass
|
||||
|
||||
|
||||
class NonSelf(object):
|
||||
|
||||
def __init__(x):
|
||||
pass
|
||||
|
||||
def s_method(y):
|
||||
pass
|
||||
|
||||
def s_ok(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def s_smethod(ok):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def s_cmethod(cls):
|
||||
pass
|
||||
|
||||
def s_smethod2(ok):
|
||||
pass
|
||||
s_smethod2 = staticmethod(s_smethod2)
|
||||
|
||||
def s_cmethod2(cls):
|
||||
pass
|
||||
s_cmethod2 = classmethod(s_cmethod2)
|
||||
@@ -134,33 +134,6 @@ def augassign(x = []):
|
||||
x += ["x"]
|
||||
|
||||
|
||||
#Possible FPs for non-self. ODASA-2439
|
||||
|
||||
class C(object):
|
||||
def _func(f):
|
||||
return f
|
||||
|
||||
_func(x)
|
||||
|
||||
#or
|
||||
@_func
|
||||
def meth(self):
|
||||
pass
|
||||
|
||||
|
||||
def dont_care(arg):
|
||||
pass
|
||||
|
||||
class C(object):
|
||||
|
||||
meth = dont_care
|
||||
|
||||
class Meta(type):
|
||||
|
||||
#__new__ is an implicit class method, so the first arg is the metaclass
|
||||
def __new__(metacls, name, bases, cls_dict):
|
||||
return super(Meta, metacls).__new__(metacls, name, bases, cls_dict)
|
||||
|
||||
#ODASA 3658
|
||||
from sys import exit
|
||||
#Consistent returns
|
||||
@@ -172,14 +145,7 @@ def ok5():
|
||||
print(e)
|
||||
exit(EXIT_ERROR)
|
||||
|
||||
#ODASA-6062
|
||||
import zope.interface
|
||||
class Z(zope.interface.Interface):
|
||||
|
||||
def meth(arg):
|
||||
pass
|
||||
|
||||
Z().meth(0)
|
||||
|
||||
# indirect modification of parameter with default
|
||||
def aug_assign_argument(x):
|
||||
|
||||
119
python/ql/test/query-tests/Functions/general/parameter_names.py
Normal file
119
python/ql/test/query-tests/Functions/general/parameter_names.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# Using name other than 'self' for first parameter in methods.
|
||||
# This shouldn't apply to classmethods (first parameter should be 'cls' or similar)
|
||||
# or static methods (first parameter can be anything)
|
||||
|
||||
|
||||
class Normal(object):
|
||||
|
||||
def n_ok(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def n_smethod(ok):
|
||||
pass
|
||||
|
||||
# not ok
|
||||
@classmethod
|
||||
def n_cmethod(self):
|
||||
pass
|
||||
|
||||
# not ok
|
||||
@classmethod
|
||||
def n_cmethod2():
|
||||
pass
|
||||
|
||||
# this is allowed because it has a decorator other than @classmethod
|
||||
@classmethod
|
||||
@id
|
||||
def n_suppress(any_name):
|
||||
pass
|
||||
|
||||
|
||||
class Class(type):
|
||||
|
||||
def __init__(cls):
|
||||
pass
|
||||
|
||||
def c_method(y):
|
||||
pass
|
||||
|
||||
def c_ok(cls):
|
||||
pass
|
||||
|
||||
@id
|
||||
def c_suppress(any_name):
|
||||
pass
|
||||
|
||||
|
||||
class NonSelf(object):
|
||||
|
||||
def __init__(x):
|
||||
pass
|
||||
|
||||
def s_method(y):
|
||||
pass
|
||||
|
||||
def s_method2():
|
||||
pass
|
||||
|
||||
def s_ok(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def s_smethod(ok):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def s_cmethod(cls):
|
||||
pass
|
||||
|
||||
def s_smethod2(ok):
|
||||
pass
|
||||
s_smethod2 = staticmethod(s_smethod2)
|
||||
|
||||
def s_cmethod2(cls):
|
||||
pass
|
||||
s_cmethod2 = classmethod(s_cmethod2)
|
||||
|
||||
#Possible FPs for non-self. ODASA-2439
|
||||
|
||||
class Acceptable1(object):
|
||||
def _func(f):
|
||||
return f
|
||||
_func(x)
|
||||
|
||||
class Acceptable2(object):
|
||||
def _func(f):
|
||||
return f
|
||||
|
||||
@_func
|
||||
def meth(self):
|
||||
pass
|
||||
|
||||
# Handling methods defined in a different scope than the class it belongs to,
|
||||
# gets problmematic since we need to show the full-path from method definition
|
||||
# to actually adding it to the class. We tried to enable warnings for these in
|
||||
# September 2019, but ended up sticking to the decision from ODASA-2439 (where
|
||||
# results are both obvious and useful to the end-user).
|
||||
def dont_care(arg):
|
||||
pass
|
||||
|
||||
class Acceptable3(object):
|
||||
|
||||
meth = dont_care
|
||||
|
||||
# OK
|
||||
class Meta(type):
|
||||
|
||||
#__new__ is an implicit class method, so the first arg is the metaclass
|
||||
def __new__(metacls, name, bases, cls_dict):
|
||||
return super(Meta, metacls).__new__(metacls, name, bases, cls_dict)
|
||||
|
||||
#ODASA-6062
|
||||
import zope.interface
|
||||
class Z(zope.interface.Interface):
|
||||
|
||||
def meth(arg):
|
||||
pass
|
||||
|
||||
Z().meth(0)
|
||||
Reference in New Issue
Block a user